setAll([ 'has_dashboard', 'can_register', 'keep_stats', 'can_favourite', 'member_verified', ]) ->setCreatable([ 'piercer' => ['piercings','artwork','events'], 'tattoo_artist' => ['tattoos','artwork','events'], 'artist' => ['artwork'] ]) ->setUserSubtype('artist_type'); $artist->profile() ->setIcon('artist') ->make([ 'rewrite' => [ 'slug' => 'artist', 'with_front' => false, ], 'taxonomies' => [ 'art_form', 'art_style', 'art_theme', 'artist_type', 'city', 'colours', 'shop', 'style', 'theme', ], 'hierarchical' => false, ]) ->setAll([ 'favouritable', 'karma', 'show_feed', 'show_directory', 'approve_new', 'invitable', 'track_changes', ]); //$directory = $artist->getConfig('directory'); } function aei_artist_fields():void { if (!class_exists('JVBase\registrar\Registrar')) { return; } $artist = Registrar::getInstance('artist'); $profile = $artist->getProfile(); $fields = $profile->fields(); $fields->addField('display_name', [ 'type' => 'text', 'label' => 'display_name', 'required' => true, ]); $fields->addField('first_name', [ 'type' => 'text', 'label' => 'First Name', 'required' => true, ]); $fields->addField('phone', [ 'type' => 'phone', 'label' => 'Phone' ]); $fields->addField('email', [ 'type' => 'email', 'label' => 'Email' ]); $fields->addCommon('links'); $fields->addCommon('contact'); $fields->addField('followers', [ 'type' => 'repeater', 'label' => 'Social Followers', 'hidden' => true, 'fields' => [ 'count' => [ 'type' => 'text', 'subtype'=> 'number', 'label' => 'Count' ], 'source' => [ 'type' => 'select', 'label' => 'Source', 'options' => [ 'instagram' => 'Instagram', 'facebook' => 'Facebook', 'tiktok' => 'TikTok', 'general' => 'General', ] ], 'checked' => [ 'type' => 'date', 'label' => 'Lst Checked' ] ] ]); $fields->addField('insta_handle', [ 'type' => 'text', 'hidden' => true, 'label' => 'Instagram Handle' ]); $fields->addField('show_online', [ 'type' => 'true_false', 'label' => 'Show Online', ]); $fields->addField('featured_artist', [ 'type' => 'true_false', 'label' => 'Featured Artist', 'hint' => 'Would you like a chance to be a featured artist?', ]); $fields->addField('has_discounts', [ 'type' => 'true_false', 'label' => 'Discounts', 'description' => 'Do you offer discounted or free services to those leaving domestic violence, gangs, covering hateful imagery, or trafficking? This will NOT be posted anywhere publicly. We work with local non-profits for our New Legacy Program, and we want to know who we can refer to. Some non-profits may have discretionary funds to put towards these services.', ]); $fields->addField('discounts', [ 'type' => 'repeater', 'label' => 'What do you offer?', 'fields' => [ 'for' => [ 'type' => 'text', 'label' => 'For: ', 'hint' => 'Who is this for? Example: hateful symbolism, domestic violence, prison tattoos, gang tattoos, scars, human trafficking, etc' ], 'description' => [ 'type' => 'textarea', 'quill' => true, 'label' => 'What do you offer?', 'hint' => 'What do you offer? Discount/pro bono/sliding scale? What are the conditions?' ] ] ]); $fields->addField('top_style', [ 'type' => 'selector', 'subtype'=> 'taxonomy', 'isReference' => true, 'taxonomy' => 'style', 'max' => 3, 'label' => 'Top Styles (Max 3)', 'hint' => 'If you want to control which 3 styles show at the top of your profile, select your top favourite styles to work in. If you do not select any, it will be determined by the tattoo styles you post most often.' ]); $fields->addField('top_theme', [ 'type' => 'selector', 'subtype'=> 'taxonomy', 'isReference' => true, 'taxonomy' => 'theme', 'max' => 3, 'label' => 'Top Themes (Max 3)', 'hint' => 'If you want to control which 3 themes show at the top of your profile, select your top favourite styles to work in. If you do not select any, it will be determined by the tattoo themes you post most often.' ]); $fields->addField('city', [ 'type' => 'selector', 'subtype' => 'taxonomy', 'taxonomy' => 'city', 'label' => 'City', ]); $fields->addField('shop', [ 'type' => 'selector', 'subtype' => 'taxonomy', 'taxonomy' => 'shop', 'label' => 'Shop', ]); $fields->addField('requested_shop', [ 'type' => 'selector', 'subtype' => 'taxonomy', 'isReference' => true, 'taxonomy' => 'shop', 'label' => 'Requested Shop', 'hidden' => true, ]); //TODO These two should probably be auto-added via management settings $fields->addField('shop_owner',[ 'hidden' => true, 'type' => 'selector', 'subtype' => 'taxonomy', 'taxonomy' => 'shop', 'isReference' => 'true', 'label' => 'Shop Owner', ]); $fields->addField('shop_manager',[ 'hidden' => true, 'type' => 'selector', 'subtype' => 'taxonomy', 'taxonomy' => 'shop', 'isReference' => 'true', 'label' => 'Shop Manager', ]); $fields->modifyField('post_excerpt', 'label', 'Short Bio'); $fields->modifyField('post_excerpt', 'maxLength', 133); $fields->modifyField('post_excerpt', 'description', 'A short description of yourself. 1-3 sentences.'); $fields->modifyField('post_content', 'label', 'Bio'); $fields->modifyField('post_content', 'quill', true); $fields->modifyField('post_content', 'description', 'A longer bio for your main profile.'); $fields->addField('image_portrait', [ 'type' => 'upload', 'label' => 'Profile Image' ]); $fields->addField('artist_type', [ 'type' => 'selector', 'subtype' => 'taxonomy', 'taxonomy' => 'artist_type', 'max' => 1, 'label' => 'You are a:' ]); $fields->addCommon('wiki'); $fields->addCommon('alternate_name'); $fields->addCommon('specialties'); $fields->addCommon('rate'); $fields->addCommon('awards'); $fields->addCommon('ratings'); $fields->addCommon('reviews'); $fields->addCommon('services'); $fields->addCommon('languages'); $fields->addCommon('credentials'); $fields->addCommon('keywords'); $fields->modifyField('keywords', 'default', [ 'Edmonton tattoos', 'Edmonton tattoo artist', 'Edmonton tattooist' ]); $contact = $profile->addSection('contact') ->setIcon('chat') ->setFields([ 'display_name', 'first_name', 'phone', 'email', 'public_contact', 'admin_contact', 'show_online', 'featured_artist', ]); $profileSection = $profile->addSection('profile') ->setIcon('user') ->setFields([ 'image_portrait', 'artist_type', 'city', 'shop', 'post_excerpt', 'post_content' ]); $seo = $profile->getSEO(); $seo = $profile->addSection('seo') ->setIcon('robot') ->setFields([ 'alternate_name', 'specialties', 'rate', 'awards', 'ratings', 'reviews', 'services', 'languages', 'credentials', 'keywords' ]); } function aei_artist_schema():array { return [ 'type' => 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\ProfilePage', 'name' => '{{post_title}} | Edmonton\'s Best {{artist_type.name}}', 'mainEntity' => [ 'type' => 'JVBase\managers\SEO\render\Thing\Person', 'name' => '{{post_title}}', 'image' => '{{image_portrait}}', 'givenName' => '{{first_name', 'description' => '{{post_excerpt}}', 'memberOf' => ['@id' => get_home_url().'/#organization'], 'alternateName' => '{{alternate_name}}', 'workLocation' => '{{city.reference}}', 'worksFor' => '{{shop.name}}', 'jobTitle' => '{{artist_type.name}}', ], 'description' => '{{post_excerpt}}', ]; } add_filter('jvb_single_artit_profile_schema_output', 10, 2); function aei_artist_schema_output(ProfilePage $profile, int $ID):ProfilePage { /** * @var \JVBase\managers\SEO\render\Thing\Person\Person $person */ $person = $profile->getAbout(); $meta = Meta::forPost($ID); $public_contact = $meta->get('public_contact'); foreach ($public_contact as $c) { switch ($c) { case 'text': case 'call': $phone = $meta->get('phone'); if (!empty($phone)) { $person->setTelephone($phone); } break; case 'email': $email = $meta->get('email'); if(!empty($email)) { $person->setEmail($email); } } } $links = $meta->get('links'); $sameAs = []; foreach($links as $link) { $sameAs[] = $link['url']; } if (!empty($links)) { $person->setSameAs($sameAs); } //TODO: Rating is not a property of Person // $rating = $meta->get('average_rating'); // if (!empty($rating) || $rating === 'none') { // $total = $meta->get('total_ratings'); // $person->setAggregateRating([ // 'ratingValue' => $rating, // 'ratingCount' => $total === '' ? 1 : $total, // 'bestRating' => 5, // 'worstRating' => 1, // ]) // } $userID = get_post_meta($ID, BASE.'profile_link', true); $owns = JVB()->roles()->getOwnedTerms($userID); if (!empty($owns)) { $shops = []; foreach ($owns as $shopID) { $term = get_term($shopID); if ($term && !is_wp_error($term)) { $shopRegistrar = Registrar::getInstance($term->taxonomy); if ($shopRegistrar) { $temp = $shopRegistrar->getSEO()->schema(); $shops[] = $temp->outputReferenceSchema($shopID, $shopRegistrar->getType()); } } } if (!empty($shops)) { $person->setOwns($shops); } } $get = ['style', 'theme']; $knowsAbout = []; foreach ($get as $g) { $registrar = Registrar::getInstance($g); $tmp = $registrar->getSEO()->schema(); $top = $meta->get("top_{$g}s"); if (empty($top)) { $top = $meta->get($g); } if (is_string($top)) { $top = array_map('absint', explode(',', $top)); } foreach ($top as $t) { $knowsAbout[] = $tmp->outputReferenceSchema($t, $registrar->getType()); } } if (!empty($knowsAbout)) { $person->setKnowsAbout($knowsAbout); } /** * Specialties */ $specialties = $meta->get('specialties'); /** * Build the offers this artist creates * - first, we check each post type to see if they have any published * - Next, we get the users' * - city (or cities) * - rate * - average rating * - reviews * and attach them to the services/products * - the resulting array gets set to the Person's makesOffer */ $types = ['tattoo', 'piercing', 'artwork']; $offers = []; $cities = $meta->get('city'); $rate = $meta->get('rate'); $reviews = $meta->get('reviews'); $rating = $meta->get('aggregateRating'); $theCities = $theRate = $theReviews = $theRating = null; foreach ($types as $type){ $based = jvbCheckBase($type); $hasAny = new WP_Query([ 'post_type' => $based, 'posts_per_page' => 1, 'fields' => 'ids', 'post_author' => $userID ]); if ($hasAny->have_posts()) { $offer = new \JVBase\managers\SEO\render\Thing\Intangible\Offer(); $service = $type === 'artwork' ? new \JVBase\managers\SEO\render\Thing\Product\Product() : new \JVBase\managers\SEO\render\Thing\Intangible\Service(); if (is_null($theCities)) { $theCities = []; if (!empty($cities)) { if (is_string($cities)) { $cities = array_map('absint', explode(',', $cities)); } foreach ($cities as $termID) { $city = get_term($termID); $theCities[] = ['@type' => 'City','name' => $city->name]; } } } if (!empty($theCities)) { $service->setAreaServed($theCities); } if (is_null($theRate)) { $theRate = false; if (!empty($rate)) { $theRate = new \JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\PriceSpecification(); $theRate->setPrice($rate); $theRate->setPriceCurrency('CAD'); $theRate->setUnitText('HOUR'); } } if ($theRate) { $offer->setPriceSpecification($theRate); } if (is_null($theReviews)) { $theReviews = []; foreach ($reviews as $r) { $review = new \JVBase\managers\SEO\render\Thing\CreativeWork\Review(); if (!empty($r['name'])) { $review->setAuthor(['type' => 'JVBase\managers\SEO\render\Thing\Person\Person', 'name' => $r['name']]); } if (!empty($r['date'])) { $review->setDateCreated([$r['date']]); } if (!empty($r['review'])) { $review->setReviewBody($r['review']); } if (array_key_exists('rating', $r) && $r['rating'] !== 'none') { $rating = new \JVBase\managers\SEO\render\Thing\Intangible\Rating\Rating(); $rating->setRatingValue($r['rating']); $rating->setBestRating(5); $rating->setWorstRating(1); $review->setReviewRating($rating); } if (!empty($r['url'])) { $review->setUrl($r['url']); } $theReviews[] = $review; } } if (!empty($theReviews)) { $service->setReview($theReviews); } if (is_null($theRating)) { $theRating = false; if (!empty($rating) && $rating !== 'none') { $theRating = new \JVBase\managers\SEO\render\Thing\Intangible\Rating\AggregateRating(); $theRating->setRatingValue($rating); $count = $meta->get('total_ratings')?:1; $theRating->setRatingCount($count); } } if ($theRating) { $service->setAggregateRating($theRating); } switch ($type) { case 'tattoo': $service->setName('Custom Tattoos'); break; case 'piercing': $service->setName('Piercings'); break; case 'artwork': $service->setName('Artwork'); break; } $offer->setItemOffered($service); $offers[] = $offer; } } if (!empty($offers)) { $person->setMakesOffer($offers); } /** * Build Artist Portfolio */ $portfolio = new WP_Query([ 'post_author' => $userID, 'post_type' => [BASE.'tattoo', BASE.'piercing', BASE.'artwork'], 'meta_key' => BASE.'karma', 'orderby' => 'meta_value_num', 'order' => 'DESC', 'posts_per_page'=> 20, 'fields' => 'ids' ]); if ($portfolio->have_posts()) { $hasPart = new JVBase\inc\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage\CollectionPage(); $hasPart->setName($meta->get('post_title').'\'s Portfolio'); $hasPart->setUrl(get_the_permalink($ID)); $itemList = new JVBase\managers\SEO\render\Thing\Intangible\ItemList\ItemList(); $pos = 1; $collection = array_map(function ($ID) use ($pos) { $type = get_post_type($ID); $registrar = Registrar::getInstance($type); $schema = $registrar->getSEO()->schema(); $item = $schema->outputReferenceSchema($ID, $registrar->getType(), false); $listItem = new \JVBase\managers\SEO\render\Thing\Intangible\ListItem(); $listItem->setPosition($pos); $pos++; $listItem->setItem($item); return $listItem; }, $portfolio->posts); $itemList->setItemListElement($collection); $itemList->setItemListElement($itemList); $hasPart->setMainEntity($itemList); $profile->setHasPart($hasPart); } return $profile; } function aei_artist_meta():array { return[ 'name' => 'Best Tattoos in {{name}}', 'description' => 'Tattoos, piercings, tattoo artists, and tattoo artists in {{name}}.' ]; } function aei_artist_archive(array $defaults):array { return array_merge($defaults, [ 'name' => 'Edmonton\'s Best Tattoo Artists', ]); } function aei_artist_reference_schema(array $defaults):array { $defaults['image'] = '{{image_portrait}}'; $defaults['worksFor'] = '{{shop.name}}'; $defaults['knowsAbout'] = '{{style.name}}'; return $defaults; }