- Route pages by address to theme templates (about-us, contact-us, faq, blogs, posts) - Scoped design CSS (pages.css) + Astra/WordPress fixes (pages-wp.css), page JS - Contact form emails the new Settings -> Viator Collector -> Contact email (fallback: admin email), Reply-To the visitor, honeypot spam trap - Blog list shows real posts with search, sort and paging; posts use the article design with comments as reviews and related articles - Design images converted to WebP (~15MB PNG -> ~1MB) - Theme 1.0.10, plugin 1.9.1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
302 lines
10 KiB
PHP
302 lines
10 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, '/' );
|
|
}
|
|
|
|
/**
|
|
* 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,
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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 · READ MORE · </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( get_the_excerpt( $post ), 24, '…' ) ); ?></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 );
|