Jake Vanderwerf
2026-06-05 f383c3a4325bc173b6f9676274bc19694be6f9c0
content/progress.php
@@ -1,7 +1,12 @@
<?php
// /content/progress.php
use JVBase\base\SchemaHelper;
use JVBase\inc\managers\SEO\render\Thing\CreativeWork\Article\Report;
use JVBase\inc\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage\ImageGallery;
use JVBase\inc\managers\SEO\render\Thing\MedicalEntity\MedicalProcedure;
use JVBase\meta\Meta;
use JVBase\registrar\config\seo\Resolver;
use JVBase\registrar\Registrar;
if (!defined('ABSPATH')) {
    exit;
@@ -14,9 +19,11 @@
//Add fields later so we can verify taxonomies/post types exist
add_action('plugins_loaded', 'altr_progress_fields', 2);
add_filter('altr_ProgressSchemaDefault', 'altr_progress_schema');
add_filter('altr_ProgressMetaDefault', 'altr_progress_meta');
add_filter('altr_ProgressArchiveDefault', 'altr_progress_archive');
add_filter('altr_progressTitleAlts', 'altr_progress_title_alts');
function altr_progress(){
    if (!class_exists('JVBase\registrar\Registrar')) {
@@ -168,20 +175,44 @@
    $feed = $progress->config('feed');
    $feed->setCustomOrder([
            'number'    => [
            [
                    'slug' => 'number',
                'label' => 'Number of Treatments',
                'icon'  => 'hash-straight',
                'for'   => 'progress'
            ]
        ]
    );
    $directory = $progress->config('directory');
    $directory->setDirectoryExtra([
         'goal',
    $feed->setFields([
        'post_title',
        'post_date',
        'post_modified',
        'post_excerpt',
        'goal',
        'body_part',
        'skin_type',
        'age'
        'age',
        'style',
        'theme',
    ]);
//    $directory = $progress->config('directory');
//    $directory->setDirectoryExtra([
//         'goal',
//        'skin_type',
//        'age'
//    ]);
}
function altr_progressTitleAlts():array
{
    return [
        ': Before & After Laser Tattoo Removal',
        ': Before & After Tattoo Removal',
        ': Tattoo Removal Progress',
        ': Tattoo Removal Results',
        ': Before & After',
    ];
}
function altr_progress_schema():array
@@ -202,10 +233,194 @@
    ];
}
function altr_build_singular_progress_schema(int $ID):array
{
    $schema = [];
    $work = new Report();
    $meta = Meta::forPost($ID);
    $title = $meta->get('post_title');
    $id = 'case-study-'.sanitize_title($title);
    $work->setId($id);
    $work->setName($meta->get('post_title'). ' | Before & After Tattoo Removal');
    $work->setDescription($meta->get('post_excerpt'));
    $procedure = new MedicalProcedure();
    $procedure->setId('laser-tattoo-removal');
    $procedure->setName('Laser Tattoo Removal');
    if (!empty($meta->get('body_part'))) {
        $parts = array_map('absint', explode(',', $meta->get('body_part')));
        $parts = array_filter(array_map(function($ID) {
            $term = get_term($ID, BASE.'body_part');
            if ($term && !is_wp_error($term)) {
                return $term->name;
            }
            return false;
        }, $parts));
        $location = jvbCommaList($parts);
        $procedure->setBodyLocation($location);
    }
    $work->setAbout($procedure);
    $fields = ['goal', 'style', 'theme','skin_type', 'age'];
    $keywords = ['laser tattoo removal', 'PicoWay',];
    foreach ($fields as $tax) {
        $terms = $meta->get($tax);
        if (!empty($terms)) {
            $terms = array_map('absint', explode(',', $terms));
            $terms = array_filter(array_map(function($ID) use ($tax) {
                $term = get_term($ID, BASE.$tax);
                if ($term && !is_wp_error($term)) {
                    $base = match ($tax) {
                        'skin_type' => 'FitzPatrick Type ',
                        'age'   => 'removing a ',
                        default => ''
                    };
                    $suffix = $tax === 'age' ? ' old tattoo' : '';
                    $value = $term->name === 'Complete Removal' ? 'Complete Tattoo Removal' : $term->name;
                    return $base.$value.$suffix;
                }
                return false;
            }, $terms));
            $keywords = array_merge($keywords, $terms);
        }
    }
    $work->setKeywords($keywords);
    //Setup the various parts;
    $observations = [];
    $gallery = new ImageGallery();
    $gallery->setId($id.'-gallery');
    $gallery->setName('Before & After Photo Gallery');
    $galleryItems = [];
    //Grab the children and set the parent at the front
    $points = get_children(['post_parent' => $ID, 'orderby' => 'date', 'order' => 'ASC', 'post_status' => 'publish', 'fields' => 'ids']);
    array_unshift($points, $ID);
    $temporalCoverage = date('Y-m-d', strtotime($meta->get('post_date')));
    $base = sanitize_title($meta->get('post_title'));
    $last = array_key_last($points);
    foreach ($points as $index => $point) {
        $pointMeta = Meta::forPost($point);
//        $observation = new \JVBase\inc\managers\SEO\render\Thing\Intangible\Observation();
        $image = Resolver::imgIDToSchema($pointMeta->get('post_thumbnail'));
        $id = $index === 0 ? '-before-tattoo-removal' : '-treatment-'.$index;
//        $observation->setId($base.'-observation'.$id);
        $image->setId($base . $id);
        $name = $index === 0 ?
            'Before Laser Tattoo Removal' :
            'After Treatment '.$index;
        $image->setName($name);
        $description = $pointMeta->get('post_excerpt');
        $timeline = $pointMeta->get('timeline');
        if (!empty($timeline)) {
            $time = array_map('absint', explode(',', $timeline));
            $time = array_filter(array_map(function($ID) {
                $term = get_term($ID, BASE.'timeline');
                return $term->name??false;
            }, $time));
            $time = jvbCommaList($time);
            $description .= 'Photo taken '.$time.' after treatment '.$index;
        }
        $image->setDescription($description);
        $image->setDateCreated($pointMeta->get('post_date'));
        $galleryItems[] = $image;
        if ((int)$index === (int)$last) {
            $temporalCoverage .= '/'. date('Y-m-d', strtotime($pointMeta->get('post_date')));
        }
    }
    $hasPart= [];
    if (!empty ($galleryItems)) {
        $gallery->setImage($galleryItems);
        $schema[] = $gallery->outputSchema();
        $hasPart[] = ['@id' => $gallery->getId()];
    }
    //Set the temporal coverage (date start/date end)
    $work->setTemporalCoverage($temporalCoverage);
    $work->setCreator(SchemaHelper::reference('organization'));
    $output = $work->outputSchema();
    if (!empty($hasPart)) {
        $output['hasPart'] = $hasPart;
    }
    array_unshift($schema, $output);
    return $schema;
}
function altr_build_singular_progress_schema_reference(int $ID):Report
{
    $work = new Report();
    $meta = Meta::forPost($ID);
    $url = get_the_permalink($ID);
    $title = $meta->get('post_title');
    $id = 'case-study-'.sanitize_title($title);
    $work->setId($url.'/#'.$id);
    $work->setUrl($url);
    $work->setName($title.' | Before & After Tattoo Removal');
    $points = get_children(['post_parent' => $ID, 'orderby' => 'date', 'order' => 'ASC', 'post_status' => 'publish', 'fields' => 'ids']);
    $description = 'A ';
    $themes = $meta->get('theme');
    if (!empty($themes)) {
        $description .= jvbCommaList(array_filter(array_map(function($ID) {
            $term = get_term($ID, BASE.'theme');
            return $term->name??false;
        }, explode(',', $themes)))).' ';
    }
    $style = $meta->get('style');
    if (!empty($style)) {
        $description .= jvbCommaList(array_filter(array_map(function($ID) {
                $term = get_term($ID, BASE.'theme');
                return $term->name??false;
            }, explode(',', $style)))).' ';
    }
    $description .= 'tattoo before any laser tattoo removal treatments, and after '.count($points).' treatments.';
    $work->setDescription($description);
    $last = $points[array_key_last($points)];
    $lastMeta = Meta::forPost($last);
    $temporalCoverage = date('Y-m-d', strtotime($meta->get('post_date'))).'/'.
        date('Y-m-d', strtotime($lastMeta->get('post_date')));
    $work->setTemporalCoverage($temporalCoverage);
    return $work;
}
function altr_progress_meta():array
{
    return[
        'title' => '{{style.name}} {{theme.name}} Tattoo – Before & After {{number}} Laser Removal Sessions',
        'title' => '{{post_title}}',
        'description' => 'See this {{style.name}} {{theme.name}} {{age.name}}-old tattoo before and after {{number}} laser tattoo removal treatments on the {{body_part.name}}.',
    ];
}
@@ -426,6 +641,72 @@
//        'upload_title' => 'Upload Before & Afters',
//    ];
//}
function altr_render_progress_field_post_modified(?int $ID = null, bool $isTemplate = false):string
{
    $points = is_null($ID) ? [] : jvbTimelinePoints($ID, BASE.'progress');
    $value = '';
    if (!empty($points)){
        $last = $points[array_key_last($points)];
        $lastMeta = Meta::forPost($last);
        $value = $lastMeta->get('post_date');
    }
    return $isTemplate || !empty($value) ? sprintf(
         '<div class="row left"><span class="label">Last Treated:</span><time data-field="post_modified"%s>%s</time></div>',
        date('c', strtotime($value)),
        date('M Y', strtotime($value))
    ) : '';
}
function altr_render_progress_field_post_date(?int $ID = null, bool $isTemplate = false):string
{
    $meta = ($ID) ? Meta::forPost($ID) : false;
    $value = ($meta) ? $meta->get('post_date') : '';
    return $isTemplate || !empty($value) ? sprintf(
        '<div class="row left"><span class="label">Started:</span><time data-field="post_date"%s>%s</time></div>',
        date('c', strtotime($value)),
        date('M Y', strtotime($value))
    ) : '';
}
function altr_render_progress_feed_item_images(?int $ID = null, array $images = []):string
{
    $meta = ($ID) ? Meta::forPost($ID) : false;
    $points = is_null($ID) ? [] : get_children([
        'post_parent'   => $ID,
        'post_type'     => BASE.'progress',
        'post_status'   => 'publish',
        'orderby'       => 'date',
        'order'         => 'ASC',
        'fields'        => 'ids',
    ]);
    $lastImg = '';
    if (!empty($points)) {
        $last = $points[array_key_last($points)];
        $lastMeta = Meta::forPost($last);
        $lastImg = $lastMeta->get('post_thumbnail');
    }
    $defaultBefore = '<img data-field="before" width="300px" height="300px" loading="lazy" decoding="async">';
    $defaultAfter = '<img data-field="after" width="300px" height="300px" loading="lazy" decoding="async">';
    $total = count($points);
    return sprintf(
        '<span class="before-text">Before</span>
        %s%s
        <span class="after-text">After%s</span>',
        $meta ? str_replace('<img', '<img data-field="before" class="before"',jvbFormatImage($meta->get('post_thumbnail'), 'tiny', 'medium')) : $defaultBefore,
        !empty($lastImg) ? str_replace('<img', '<img data-field="after" class="after"',jvbFormatImage($lastImg, 'tiny', 'medium')) : $defaultAfter,
        empty($points) ? '' : ' '.$total.' Tx'
    );
}
add_filter('jvbFeedItemData', 'altr_progress_item_data', 10, 2);
function altr_progress_item_data(string $out, string $content) {
    if ($content === 'progress') {
        return ' data-timeline';
    }
    return $out;
}
add_filter('jvbFeedItem', 'altr_progress_item', 10, 2);
@@ -475,10 +756,12 @@
    if ($postType !== BASE.'progress') {
        return $out;
    }
    $route = JVB()->routes('content');
    global $post;
    $data = $route->formatTimeline($post);
    $timeline = $data['fields']['timeline_gallery'];
    if (!is_array($timeline)) {
        return $out;
    }
@@ -508,11 +791,11 @@
        $after = jvbIcon('logo-triangle-fill').jvbFormatImage(get_post_thumbnail_id($last),'tiny','directory-preview',false);
    }
    return '<li class="row btw">
    return '<li class="row x-btw">
        
                    <a href="'.$item['url'].'" title="More about '.$item['name'].'">
                        '.$item['name'].'</a>'.$extra.'
                        <div class="image">'.$before.$after.'</div>
                        '.$item['name'].'</a>
                        <div class="image">'.$before.$after.'</div>'.$extra.'
                </li>';
}