Files
hoponhopofftickets/astra-child-hoponhopoff/inc/pages.php
Amir Bagheri 466ea0ac5f feat: one breadcrumb, same place and style on every inner page
- Plugin: vc_breadcrumb_trail()/vc_breadcrumb_html(); tours, region, city
  and tag pages show Home / (Region) / Page in the banner instead of the
  generic 'Tour & Travel' label; the tour page uses the same helper
- Theme: blog list shows Home / Blog; blog post title, breadcrumb and
  meta move into the banner like the other pages
- Shared style: title's colour (white in photo banners, black on About),
  softer links, current page bold and shortened if long - also on phones
- Plugin 1.9.6, theme 1.0.16

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 21:32:09 +03:30

356 lines
12 KiB
PHP

<?php
/**
* Design pages from "new front": About, Contact, FAQ, the blog list and single
* posts. Each is routed to a template in /templates by the page's address, so
* nobody has to pick a page template in the editor.
*/
defined( 'ABSPATH' ) || exit;
/**
* Page slugs each design answers to (live site uses the "-us" forms).
*/
function hoho_page_slugs() {
return apply_filters(
'hoho_page_slugs',
array(
'about' => array( 'about-us', 'about' ),
'contact' => array( 'contact-us', 'contact' ),
'faq' => array( 'faq', 'faqs' ),
'blog' => array( 'blogs', 'blog' ),
)
);
}
/**
* Which design the current request uses: about|contact|faq|blog|post, or ''.
*/
function hoho_page_kind() {
static $kind = null;
if ( null !== $kind ) {
return $kind;
}
// The query is not resolved yet: answer, but don't cache the answer.
if ( ! did_action( 'wp' ) ) {
return '';
}
$kind = '';
if ( is_singular( 'post' ) ) {
$kind = 'post';
} elseif ( is_home() && ! is_front_page() ) {
// The Posts page — but never a "Your latest posts" homepage.
$kind = 'blog';
} elseif ( is_page() ) {
foreach ( hoho_page_slugs() as $name => $slugs ) {
if ( is_page( $slugs ) ) {
$kind = $name;
break;
}
}
}
return $kind;
}
function hoho_asset( $path ) {
return get_stylesheet_directory_uri() . '/assets/' . ltrim( $path, '/' );
}
/**
* Banner image for a design page: the page's Featured Image when one is set
* (for the blog list, the Posts page's), otherwise the design's own photo.
*/
function hoho_banner_url( $fallback ) {
$page_id = ( is_home() && get_option( 'page_for_posts' ) ) ? (int) get_option( 'page_for_posts' ) : get_queried_object_id();
$url = $page_id ? get_the_post_thumbnail_url( $page_id, 'full' ) : '';
return $url ? $url : hoho_asset( 'img/' . $fallback . '.webp' );
}
/**
* Breadcrumb in the same markup as the plugin's vc_breadcrumb_html(), so
* every page shares one style. $trail: list of [label, url]; Home is added.
*/
function hoho_breadcrumb_html( array $trail, $current ) {
$trail = array_merge( array( array( __( 'Home', 'hoponhopoff' ), home_url( '/' ) ) ), $trail );
$out = '<nav class="crumbs" aria-label="' . esc_attr__( 'Breadcrumb', 'hoponhopoff' ) . '"><ol class="crumbs__list">';
foreach ( $trail as $item ) {
$out .= '<li><a href="' . esc_url( $item[1] ) . '">' . esc_html( $item[0] ) . '</a></li>';
}
return $out . '<li aria-current="page">' . esc_html( $current ) . '</li></ol></nav>';
}
/**
* URL of the blog list (the Posts page, else a page slugged blogs/blog).
*/
function hoho_blog_url() {
if ( get_option( 'page_for_posts' ) ) {
return get_permalink( get_option( 'page_for_posts' ) );
}
foreach ( hoho_page_slugs()['blog'] as $slug ) {
$page = get_page_by_path( $slug );
if ( $page ) {
return get_permalink( $page );
}
}
return home_url( '/blogs/' );
}
/**
* Social profile links (footer + contact page). Override with the
* `hoho_socials` filter.
*/
function hoho_socials() {
return apply_filters(
'hoho_socials',
array(
'whatsapp' => '#',
'instagram' => '#',
'telegram' => '#',
'facebook' => '#',
)
);
}
add_filter(
'template_include',
function ( $template ) {
$kind = hoho_page_kind();
if ( '' === $kind ) {
return $template;
}
$path = get_stylesheet_directory() . '/templates/' . $kind . '.php';
return file_exists( $path ) ? $path : $template;
},
99
);
// Full-width, no-sidebar canvas under Astra (same treatment as the plugin pages).
add_filter(
'body_class',
function ( $classes ) {
if ( hoho_page_kind() ) {
$classes[] = 'hoho-fullwidth';
}
return $classes;
}
);
add_filter(
'astra_page_layout',
function ( $layout ) {
return hoho_page_kind() ? 'no-sidebar' : $layout;
}
);
add_filter(
'astra_get_content_layout',
function ( $layout ) {
return hoho_page_kind() ? 'page-builder' : $layout;
}
);
add_action(
'wp_enqueue_scripts',
function () {
if ( ! hoho_page_kind() ) {
return;
}
$ver = wp_get_theme()->get( 'Version' );
// pages.css is the whole design stylesheet scoped to .hoho-page; the
// Astra overrides load after it so they win ties.
wp_enqueue_style( 'hoho-pages', hoho_asset( 'pages.css' ), array( 'hoho-chrome' ), $ver );
wp_enqueue_style( 'hoho-pages-wp', hoho_asset( 'pages-wp.css' ), array( 'hoho-pages' ), $ver );
wp_enqueue_script( 'hoho-pages', hoho_asset( 'pages.js' ), array(), $ver, true );
wp_localize_script(
'hoho-pages',
'hohoPages',
array(
'sending' => __( 'Sending…', 'hoponhopoff' ),
'invalid' => __( 'Please fill in your name, a valid email and your message.', 'hoponhopoff' ),
'failed' => hoho_contact_message( 'error' ),
'copied' => __( 'Link copied', 'hoponhopoff' ),
'more' => __( 'Show all reviews', 'hoponhopoff' ),
'fewer' => __( 'Show fewer reviews', 'hoponhopoff' ),
)
);
},
30
);
// Icons the design pages use that the site-wide sprite lacks.
add_action(
'wp_footer',
function () {
if ( hoho_page_kind() ) {
echo file_get_contents( get_stylesheet_directory() . '/assets/sprite-pages.svg' ); // phpcs:ignore
}
},
2
);
/* ---------------------------------------------------------------------------
* Blog
* ------------------------------------------------------------------------- */
/**
* Blog list state from the query string. `q` (not `s`) so WordPress does not
* switch to its search template; `pg` so paging works the same whether the
* list is the Posts page or an ordinary page.
*/
function hoho_blog_state() {
$q = isset( $_GET['q'] ) ? sanitize_text_field( wp_unslash( $_GET['q'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
$sort = isset( $_GET['sort'] ) ? sanitize_key( $_GET['sort'] ) : 'newest'; // phpcs:ignore WordPress.Security.NonceVerification
$pg = isset( $_GET['pg'] ) ? max( 1, absint( $_GET['pg'] ) ) : 1; // phpcs:ignore WordPress.Security.NonceVerification
if ( ! in_array( $sort, array( 'newest', 'oldest', 'title' ), true ) ) {
$sort = 'newest';
}
return compact( 'q', 'sort', 'pg' );
}
function hoho_blog_query( array $state ) {
$order = array(
'newest' => array( 'date', 'DESC' ),
'oldest' => array( 'date', 'ASC' ),
'title' => array( 'title', 'ASC' ),
);
return new WP_Query(
array(
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => 12,
'paged' => $state['pg'],
's' => $state['q'],
'orderby' => $order[ $state['sort'] ][0],
'order' => $order[ $state['sort'] ][1],
'ignore_sticky_posts' => true,
)
);
}
/**
* Imported posts start with a stray "Home" breadcrumb line (left over from the
* old page builder). Drop it from the article and from card excerpts.
*/
function hoho_strip_leading_home( $text ) {
return preg_replace( '#^\s*(?:<p>\s*Home\s*</p>|Home\b\s*)#i', '', (string) $text );
}
add_filter(
'the_content',
function ( $content ) {
return ( is_singular( 'post' ) && in_the_loop() ) ? hoho_strip_leading_home( $content ) : $content;
},
20
);
/**
* Card date chip, as in the design: relative for the last week, then a date.
*/
function hoho_time_label( $post = null ) {
$time = get_post_time( 'U', true, $post );
if ( time() - $time < WEEK_IN_SECONDS ) {
/* translators: %s: human time difference, e.g. "3 hours". */
return sprintf( __( '%s ago', 'hoponhopoff' ), human_time_diff( $time ) );
}
return get_the_date( 'Y/m/d', $post );
}
/**
* Image for a post card/hero: the featured image, else a design photo.
*/
function hoho_post_image_url( $post = null, $size = 'large' ) {
$url = get_the_post_thumbnail_url( $post, $size );
if ( $url ) {
return $url;
}
$fallbacks = array( 'tour-london', 'tour-paris', 'tour-sydney', 'tour-san-diego' );
return hoho_asset( 'img/' . $fallbacks[ get_post( $post )->ID % count( $fallbacks ) ] . '.webp' );
}
/**
* One article card (.acard). $wrap: 'li' (blog grid, wrapped in .titem) or
* 'related' (the post page's related row, where the card is the <li>).
*/
function hoho_article_card( $post, $wrap = 'li' ) {
$post = get_post( $post );
$heading = 'related' === $wrap ? 'h3' : 'h2';
ob_start();
?>
<div class="acard__media">
<img class="acard__img" src="<?php echo esc_url( hoho_post_image_url( $post, 'medium_large' ) ); ?>" alt="" loading="lazy" />
<span class="chip chip--glass acard__time"><?php echo esc_html( hoho_time_label( $post ) ); ?></span>
<div class="acard__overlay">
<<?php echo $heading; // phpcs:ignore ?> class="acard__title"><?php echo esc_html( get_the_title( $post ) ); ?></<?php echo $heading; // phpcs:ignore ?>>
<a class="ring-badge ring-badge--card" href="<?php echo esc_url( get_permalink( $post ) ); ?>" aria-label="<?php echo esc_attr__( 'Read the article', 'hoponhopoff' ); ?>">
<svg class="ring-badge__ring" viewBox="0 0 100 100" aria-hidden="true">
<text class="ring-badge__text"><textPath href="#ringPathScroll" startOffset="0">READ MORE &#183; READ MORE &#183; </textPath></text>
</svg>
<span class="ring-badge__dot"><?php echo hoho_icon( 'arrow-ne' ); // phpcs:ignore ?></span>
</a>
</div>
</div>
<p class="acard__excerpt"><?php echo esc_html( wp_trim_words( hoho_strip_leading_home( get_the_excerpt( $post ) ), 24, '&hellip;' ) ); ?></p>
<?php
$inner = ob_get_clean();
if ( 'related' === $wrap ) {
return '<li class="acard">' . $inner . '</li>';
}
return '<li class="titem"><article class="acard">' . $inner . '</article></li>';
}
/* ---------------------------------------------------------------------------
* Contact form
* ------------------------------------------------------------------------- */
function hoho_contact_message( $status ) {
return 'sent' === $status
? __( 'Thanks! Your ticket has been sent. We will reply by email.', 'hoponhopoff' )
: __( 'Sorry, your message could not be sent. Please check the fields and try again.', 'hoponhopoff' );
}
/**
* Recipient: "Contact email" on Settings → Viator Collector, else the site
* admin email.
*/
function hoho_contact_recipient() {
$settings = get_option( 'vc_settings', array() );
$email = is_array( $settings ) && ! empty( $settings['contact_email'] ) ? $settings['contact_email'] : '';
return is_email( $email ) ? $email : get_option( 'admin_email' );
}
function hoho_handle_contact() {
$ajax = wp_doing_ajax();
$ok = false;
$valid_nonce = isset( $_POST['hoho_contact_nonce'] ) && wp_verify_nonce( sanitize_key( wp_unslash( $_POST['hoho_contact_nonce'] ) ), 'hoho_contact' );
$name = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : '';
$email = isset( $_POST['email'] ) ? sanitize_email( wp_unslash( $_POST['email'] ) ) : '';
$message = isset( $_POST['message'] ) ? sanitize_textarea_field( wp_unslash( $_POST['message'] ) ) : '';
$trap = isset( $_POST['website'] ) ? trim( (string) wp_unslash( $_POST['website'] ) ) : '';
if ( '' !== $trap ) {
// A bot filled the hidden field: pretend it worked, send nothing.
$ok = true;
} elseif ( $valid_nonce && '' !== $name && is_email( $email ) && '' !== $message ) {
$subject = sprintf(
/* translators: 1: site name, 2: sender name. */
__( '[%1$s] Contact form: %2$s', 'hoponhopoff' ),
wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES ),
$name
);
$body = sprintf( "Name: %s\nEmail: %s\n\n%s\n", $name, $email, $message );
$headers = array( 'Reply-To: ' . str_replace( array( "\r", "\n" ), '', $name ) . ' <' . $email . '>' );
$ok = wp_mail( hoho_contact_recipient(), $subject, $body, $headers );
}
if ( $ajax ) {
if ( $ok ) {
wp_send_json_success( array( 'message' => hoho_contact_message( 'sent' ) ) );
}
wp_send_json_error( array( 'message' => hoho_contact_message( 'error' ) ) );
}
$back = wp_get_referer() ? wp_get_referer() : home_url( '/contact-us/' );
wp_safe_redirect( add_query_arg( 'contact', $ok ? 'sent' : 'error', remove_query_arg( 'contact', $back ) ) . '#ticketForm' );
exit;
}
foreach ( array( 'admin_post_hoho_contact', 'admin_post_nopriv_hoho_contact', 'wp_ajax_hoho_contact', 'wp_ajax_nopriv_hoho_contact' ) as $hoho_hook ) {
add_action( $hoho_hook, 'hoho_handle_contact' );
}
unset( $hoho_hook );