/**
* Plugin Name: WP Force Lowercase URLs
* Plugin URI: https://github.com/joshbuchea/wp-force-lowercase-urls/
* Description: Redirect uppercase URLs to lowercase.
* Version: 2.0.1
* Author: Josh Buchea
* Author URI: http://joshbuchea.com/
* Text Domain: wp-force-lowercase-urls
* Domain Path: /languages
*/
if (!defined('ABSPATH')) exit; // Exit if accessed directly
if (!class_exists('WPForceLowercaseURLs')) {
class WPForceLowercaseURLs
{
/**
* Initialize plugin
*/
public static function init()
{
// If page is non-admin, force lowercase URLs
if (!is_admin()) {
add_action('init', array('WPForceLowercaseURLs', 'toLower'));
}
}
/**
* Changes the requested URL to lowercase and redirects if modified
*/
public static function toLower()
{
// Grab requested URL
$url = $_SERVER['REQUEST_URI'];
$params = $_SERVER['QUERY_STRING'];
// If URL contains a period, halt (likely contains a filename and filenames are case specific)
if (preg_match('/[\.]/', $url)) {
return;
}
// If URL contains a capital letter
if (preg_match('/[A-Z]/', $url)) {
// Convert URL to lowercase
$lc_url = empty($params)
? strtolower($url)
: strtolower(substr($url, 0, strrpos($url, '?'))) . '?' . $params;
// if url was modified, re-direct
if ($lc_url !== $url) {
// 301 redirect to new lowercase URL
header('Location: ' . $lc_url, true, 301);
exit();
}
}
}
}
WPForceLowercaseURLs::init();
}
/**
* Images related
*/
// Was hard-set to 100 (no compression at all) for every generated image
// size on an image-heavy yacht-brokerage site - every thumbnail, card
// image, and hero crop was shipped at maximum file weight for no visible
// quality gain. WordPress's own default (82) is the standard, well-tested
// quality/size tradeoff; existing already-generated images will need a
// thumbnail regenerate (e.g. the "Regenerate Thumbnails" plugin) to
// actually shrink on disk.
add_filter('jpeg_quality', function () {
return 82;
}, 10, 2);
if (function_exists('add_theme_support')) {
add_theme_support('post-thumbnails');
remove_image_size('thumbnail');
remove_image_size('medium');
remove_image_size('large');
update_option('medium_size_h', 0);
update_option('medium_large_size_h', 0);
update_option('medium_large_size_w', 0);
update_option('medium_size_w', 0);
update_option('large_size_h', 0);
update_option('large_size_w', 0);
update_option('thumbnail_size_h', 0);
update_option('thumbnail_size_w', 0);
add_image_size('preload', 30, '', true); // preload
add_image_size('square', 1125, 1125, true); // preload
add_image_size('half_width', 665, '', true); // half layout thumbnail
add_image_size('full_width', 1280, '', true);
add_image_size('wide', 1440, '', true);
add_image_size('wide@2x', 2440, '', true);
add_post_type_support('destination', 'excerpt');
add_post_type_support('service_wn', 'excerpt');
add_post_type_support('shipyard', 'excerpt');
add_post_type_support('partner', 'excerpt');
add_post_type_support('vacancy', 'excerpt');
add_action('after_setup_theme', function () {
add_theme_support('html5', array('search-form'));
});
}
function get_thumbnail_from_slider($id = null, $size = 'square', $only_id = false)
{
if (!$id) :
global $post;
$slides = tr_posts_field('slides', $post->ID);
if (is_array($slides))
$slides = current($slides)['slide_image'];
endif;
if ($image_id = @array_shift(array_shift(tr_posts_field('builder'))['hero']['slides'])['slide_image']) :
$slides = $image_id;
else :
global $post;
$slides = tr_posts_field('slides', $id);
if (is_array($slides))
$slides = current($slides)['slide_image'];
endif;
if (!$slides) $slides = get_post_thumbnail_id();
if ($only_id)
return $slides;
return get_responsive_image($slides, $size, 'image-progressive');
}
function get_responsive_image($id, $size = '', $class = '', $alt = '', $link = '')
{
if (empty($id)) $id = get_the_ID();
if (empty($size)) $size = 'wide';
$html = '';
$sizes = "(min-width: 740px) 740w, 50%, 1280w";
$image = wp_get_attachment_image_url($id, $size);
$srcset = wp_get_attachment_image_srcset($id, $size);
//$preload = wp_get_attachment_image_url($id, 'preload');
$preload = wp_get_attachment_image_url($id, $size);
if (!$alt) $alt = get_post_meta($id, '_wp_attachment_image_alt', true) ?: get_the_title();
if (!$class or $class == 'swiper-lazy') {
$class = "swiper-lazy";
$html .= <<
HTML;
return $html;
} else {
$html .= <<
HTML;
return $html;
};
}
function add_custom_src_image_srcset($sources, $size_array, $image_src, $image_meta, $attachment_id)
{
$image_size_name = 'square'; // add_image_size('square500', 500, 500, true);
$breakpoint = 740;
$upload_dir = wp_upload_dir();
$img_url = $image_src;
if (isset($image_meta['sizes'][$image_size_name]))
$img_url = $upload_dir['baseurl'] . '/' . str_replace(basename($image_meta['file']), $image_meta['sizes'][@$image_size_name]['file'], $image_meta['file']);
$sources[$image_size_name] = array(
'url' => $img_url ?: $image_src,
'descriptor' => 'w',
'value' => $breakpoint,
);
return $sources;
}
add_filter('wp_calculate_image_srcset', 'add_custom_src_image_srcset', 10, 5);
/**
* Content related
*/
function the_content_split($content = '')
{
if (empty($content)) {
global $post;
$content = $post->post_content;
}
$html = '';
// $content = esc_html($content);
// $content = explode('', $content);
$content = explode('', $content);
if (!@$content[1]) $content[1] = null;
return $content;
}
function get_term_excerpt($count = 200, $term_id = '', $readmore = true)
{
$term = get_term($term_id);
$label = pll__('Read more');
$template_directory = get_template_directory_uri();
$permalink = get_term_link($term);
if (empty($term->description)) {
if (!empty($post->description)) :
$excerpt = (isset($post) ? $post->description : '');
else :
if (!empty(tr_posts_field('builder'))) :
foreach (tr_posts_field('builder') as $component) {
if (isset($component['content'])) :
$excerpt = $component['content']['content'];
elseif (isset($component['tiles'])) :
$excerpt = $component['tiles']['content'];
endif;
}
endif;
endif;
} else {
$excerpt = (isset($post) ? $post->description : '');
}
$excerpt = strip_tags(@$excerpt);
$excerpt = substr($excerpt, 0, $count);
$excerpt = substr($excerpt, 0, (int) strripos($excerpt, " "));
$excerpt = $excerpt . '...';
$readmore = <<
{$label}
HTML;
if ($readmore === true)
$excerpt .= $readmore;
return $excerpt;
}
function get_excerpt($count = 200, $post_id = '', $readmore = true)
{
$post = get_post($post_id);
$label = pll__('Read more');
$template_directory = get_template_directory_uri();
$permalink = get_permalink($post_id);
if (empty($post->post_excerpt)) {
if (!empty($post->post_content)) :
$excerpt = get_the_excerpt();
else :
if (!empty(tr_posts_field('builder'))) :
foreach (tr_posts_field('builder') as $component) {
if (isset($component['content'])) :
$excerpt = $component['content']['content'];
elseif (isset($component['tiles'])) :
$excerpt = $component['tiles']['content'];
endif;
}
endif;
endif;
} else {
$excerpt = $post->post_excerpt;
}
$excerpt = strip_tags(@$excerpt);
$excerpt = substr($excerpt, 0, $count);
$excerpt = substr($excerpt, 0, (int) strripos($excerpt, " "));
$excerpt = $excerpt . '...';
$readmore = <<
{$label}
HTML;
if ($readmore === true)
$excerpt .= $readmore;
return $excerpt;
}
/**
* Theme related
*/
function isAjax()
{
return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest';
}
function get_charter_permalink($id = null)
{
global $post;
if (!$id)
$id = pll_get_post($post->id, pll_current_language());
$link = get_post_permalink($id);
// $link = str_replace('yacht', 'yacht-charter', $link);
$link = preg_replace('(\Wyacht\W)', '/yacht-charter/', $link);
return $link;
}
function wn_get_available_link()
{
global $post;
if (get_query_var('charter') == true) {
if (tr_posts_field('available_for_sale', $post->ID)) {
$label = pll__('also available for sale', 'wn');
$url = get_post_permalink(pll_get_post($post->ID, pll_current_language()));
} else {
$label = pll__('not available for sale', 'wn');
$url = '#';
}
} else {
if (tr_posts_field('available_for_charter', $post->ID)) {
$label = pll__('also available for charter', 'wn');
$url = get_charter_permalink();
} else {
$label = pll__('not available for charter', 'wn');
$url = '#';
}
}
$html = <<
{$label}
HTML;
return $html;
}
function formatPhone($data = '')
{
if (!is_string($data)) return null;
if (preg_match('/^\+\d(\d{3})(\d{3})(\d{2})(\d{2})$/', $data, $matches)) {
$result = '+7 ' . $matches[1] . ' ' . $matches[2] . ' ' . $matches[3] . ' ' . $matches[4];
return $result;
} else {
return $data;
}
}
add_filter('date_i18n', 'russify_months', 11, 2);
/**
* Русифицирует месяца и недели в дате.
* Функция для фильтра date_i18n.
* @param строка $date Дата в принятом формате
* @param строка $req_format Формат передаваемой даты
* @return Дату в русском формате
*/
function russify_months($date, $req_format)
{
// в формате есть "строковые" неделя или месяц
if (!preg_match('/[FMlS]/', $req_format))
return $date;
$replace = array(
'Январь' => 'января', 'Февраль' => 'февраля', 'Март' => 'марта', 'Апрель' => 'апреля', 'Май' => 'мая', 'Июнь' => 'июня', 'Июль' => 'июля', 'Август' => 'августа', 'Сентябрь' => 'сентября', 'Октябрь' => 'октября', 'Ноябрь' => 'ноября', 'Декабрь' => 'декабря',
'Янв' => 'янв.', 'Фев' => 'фев.', 'Мар' => 'март', 'Апр' => 'апр.', 'Май' => 'май', 'Июн' => 'июнь', 'Июл' => 'июль', 'Авг' => 'авг.', 'Сен' => 'сен.', 'Окт' => 'окт.', 'Ноя' => 'ноя.', 'Дек' => 'дек.',
'January' => 'января', 'February' => 'февраля', 'March' => 'марта', 'April' => 'апреля', 'May' => 'мая', 'June' => 'июня', 'July' => 'июля', 'August' => 'августа', 'September' => 'сентября', 'October' => 'октября', 'November' => 'ноября', 'December' => 'декабря',
'Jan' => 'янв.', 'Feb' => 'фев.', 'Mar' => 'март.', 'Apr' => 'апр.', 'May' => 'мая', 'Jun' => 'июня', 'Jul' => 'июля', 'Aug' => 'авг.', 'Sep' => 'сен.', 'Oct' => 'окт.', 'Nov' => 'нояб.', 'Dec' => 'дек.',
'Sunday' => 'воскресенье', 'Monday' => 'понедельник', 'Tuesday' => 'вторник', 'Wednesday' => 'среда', 'Thursday' => 'четверг', 'Friday' => 'пятница', 'Saturday' => 'суббота',
'Sun' => 'вос.', 'Mon' => 'пон.', 'Tue' => 'вт.', 'Wed' => 'ср.', 'Thu' => 'чет.', 'Fri' => 'пят.', 'Sat' => 'суб.', 'th' => '', 'st' => '', 'nd' => '', 'rd' => '',
);
$date = strtr($date, $replace);
//if( function_exists('mb_strtolower') ) $date = mb_strtolower( $date );
return $date;
}
/**
* Search form
*/
add_filter('get_search_form', 'yacht_search_form');
function yacht_search_form($form)
{
$lang = pll_current_language() == 'ru' ? 'ru' : '';
$action = esc_url(home_url('/')) . $lang . '/search/';
$label = pll__('Search for', 'wn');
$label_yachts = pll__('In yachts', 'wn');
$label_other = pll__('Everywhere', 'wn');
$reader = pll__('Query for search', 'wn');
$form = '
';
return $form;
}
function get_yacht_search_query($escaped = true)
{
/**
* Filters the contents of the yacht search query variable.
* @param mixed $search Contents of the search query variable.
*/
$query = apply_filters('get_search_query', get_query_var('search_input'));
if ($escaped) {
$query = esc_attr($query);
}
return $query;
}
function pagination($query)
{
if ((!$query)) : global $wp_query;
$query = $wp_query;
else : $query = $query;
endif;
$big = 999999999;
$total = isset($query->max_num_pages) ? $query->max_num_pages : 1;
$args = array(
'format' => '?page=%#%',
'current' => max(1, get_query_var('paged')),
'prev_next' => true,
'prev_text' => '
',
'next_text' => '
',
'total' => $total
);
if (get_option('permalink_structure')) : $args['base'] = str_replace($big, '%#%', get_pagenum_link($big));
else : $args['base'] = add_query_arg('page', '%#%');
endif;
// echo previous_posts_link('Newer posts');
echo paginate_links($args);
// echo next_posts_link('Older posts');
}
/**
* Contact form shortcode for destinations list in cards
*/
add_filter('wpcf7_form_elements', 'do_shortcode');
// Добавив такой хук, мы можем создать обычный шорткод WordPress с помощью add_shortcode() и он будет работать в форме CF7:
function destinations_cards_cf7_func()
{
return "Привет! Я шоркод для Contact Form 7!";
}
add_shortcode('destinations_cards', 'destinations_cards_cf7_func');
/**
* Invalidate the filter-range / destination-region caches (see
* FilterController::clearRangesCache() and
* QueryController::clearDestinationRegionsCache()) whenever a yacht or
* destination is saved, trashed, or permanently deleted, so edits show up
* immediately instead of waiting for the transients' TTL to expire.
*/
function wn_clear_listing_caches($post_id)
{
$post_type = get_post_type($post_id);
if (!in_array($post_type, array('yacht', 'destination'), true)) {
return;
}
if (class_exists('FilterController')) {
FilterController::clearRangesCache();
}
if ($post_type === 'destination' && class_exists('QueryController')) {
QueryController::clearDestinationRegionsCache();
}
}
add_action('save_post', 'wn_clear_listing_caches');
add_action('trashed_post', 'wn_clear_listing_caches');
add_action('untrashed_post', 'wn_clear_listing_caches');
add_action('before_delete_post', 'wn_clear_listing_caches');
// specifications fields
if (function_exists('pll_register_string')):
// request form translation
pll_register_string('', 'Contacts information', 'wn', false);
pll_register_string('', 'Tell us about yourself', 'wn', false);
pll_register_string('', 'Phone number', 'wn', false);
pll_register_string('', 'Destinations', 'wn', false);
pll_register_string('', 'Where would you like to cruise?', 'wn', false);
pll_register_string('', 'Incredible destinations', 'wn', false);
pll_register_string('', 'Special enquery', 'wn', false);
pll_register_string('', 'Enter your time for departure and arrival', 'wn', false);
pll_register_string('', 'from', 'wn', false);
pll_register_string('', 'to', 'wn', false);
pll_register_string('', 'Use handles to change your weekly budget', 'wn', false);
pll_register_string('', 'Guests and beds', 'wn', false);
pll_register_string('', 'Enter the number of adults and children that will be sleeping on the yacht, please choose adults first', 'wn', false);
pll_register_string('', 'Guest', 'wn', false);
pll_register_string('', 'Children', 'wn', false);
pll_register_string('', 'No Guests', 'wn', false);
pll_register_string('', 'No kids', 'wn', false);
pll_register_string('', 'Pick your prefered bed type', 'wn', false);
pll_register_string('', 'More cabins with double beds', 'wn', false);
pll_register_string('', 'More cabins with twin beds', 'wn', false);
pll_register_string('', "Doesn't matter", 'wn', false);
pll_register_string('', "Style and speed", 'wn', false);
pll_register_string('', "What's the esthetic style of your desired charter yacht?", 'wn', false);
pll_register_string('', 'Contemporary | Minimalist | Modern', 'wn', false);
pll_register_string('', 'Classic | Conservative', 'wn', false);
pll_register_string('', "What's more important?", 'wn', false);
pll_register_string('', 'High speed (less interior & open deck spack)', 'wn', false);
pll_register_string('', 'More interior & open deck space (lower speed)', 'wn', false);
pll_register_string('', 'Special requests', 'wn', false);
pll_register_string('', "Do you want to cruise in specific locations?", 'wn', false);
pll_register_string('', "Do you have specific requirements? Any questions?", 'wn', false);
pll_register_string('', 'Your role', 'wn', false);
pll_register_string('', "What's your role in this?", 'wn', false);
pll_register_string('', 'I am chartering the yacht', 'wn', false);
pll_register_string('', 'I represent a person who is going to charter the yacht', 'wn', false);
pll_register_string('', "Privacy Policy", 'wn', false);
pll_register_string('', "Nature of enquiry", 'wn', false);
pll_register_string('', "Crew Placement", 'wn', false);
pll_register_string('', "Sales & Purchase", 'wn', false);
pll_register_string('', "Charter", 'wn', false);
pll_register_string('', "Management", 'wn', false);
// end of request form translation
// pll_register_string('third_parties', 'Мы не предоставляем Вашу личную информацию третьим лицам
// или сторонним компаниям.', 'wn', false);
pll_register_string('subscribe_to_our_newsletter', 'Subscribe to our newsletter', 'wn', false);
pll_register_string('close', 'close', 'wn', false);
pll_register_string('not_available_for_sale', 'not available for sale', 'wn', false);
pll_register_string('request_a_callback', 'Request a callback', 'wn', false);
pll_register_string('message', 'Message', 'wn', false);
pll_register_string('loading_message', 'Loading', 'wn', false);
pll_register_string('success_message', 'Thank you! we will contact you shortly!', 'wn', false);
pll_register_string('search_not_found', 'No yachts found on your request, try changing the search terms.', 'wn', false);
pll_register_string('third_parties', 'We do not share your personal information with third parties or third party companies.', 'wn', false);
pll_register_string('first_name', 'First name', 'wn', false);
pll_register_string('last_name', 'Last name', 'wn', false);
pll_register_string('your_phone_number', 'Your phone number', 'wn', false);
pll_register_string('email', 'Email', 'wn', false);
pll_register_string('submit', 'Submit', 'wn', false);
pll_register_string('read_more_privacy_policy', 'Read more about privacy policy', 'wn', false);
pll_register_string('how_can_we_help', 'How can we help?', 'wn', false);
pll_register_string('not_available_for_charter', 'not available for charter', 'wn', false);
pll_register_string('available_for_charter', 'Available for charter', 'wn', false);
pll_register_string('for_sale', 'For sale', 'wn', false);
pll_register_string('viewing', 'Viewing', 'wn', false);
pll_register_string('request_location', 'Request location', 'wn', false);
pll_register_string('search', 'Search', 'wn', false);
pll_register_string('posted_on', 'Posted on', 'wn', false);
pll_register_string('search_for_yachts', 'Search for yachts', 'wn', false);
pll_register_string('query_for_search', 'Query for search', 'wn', false);
pll_register_string('archive', 'Archive', 'wn', false);
pll_register_string('subscribe_to_newsletter', 'Subscribe to newsletter', 'wn', false);
pll_register_string('subscribe', 'Subscribe', 'wn', false);
pll_register_string('download', 'Download', 'wn', false);
pll_register_string('download_lodka_magazine', 'Download Lodka Magazine', 'wn', false);
pll_register_string('partners', 'Partners', 'wn', false);
pll_register_string('more_information', 'More information', 'wn', false);
pll_register_string('contacts_us', 'Contact us', 'wn', false);
pll_register_string('email', 'E-mail', 'wn', false);
pll_register_string('call', 'Call', 'wn', false);
pll_register_string('build_yacht', 'Build yacht', 'wn', false);
pll_register_string('charter_region', 'Charter region', 'wn', false);
pll_register_string('enquire_about', 'Enquire about', 'wn', false);
pll_register_string('available_for_sale', 'also available for sale', 'wn', false);
pll_register_string('available_for_charter', 'also available for charter', 'wn', false);
pll_register_string('lodka_magazine', 'Lodka magazine', 'wn', false);
pll_register_string('services', 'Services', 'wn', false);
pll_register_string('about', 'About', 'wn', false);
pll_register_string('about_company', 'About company', 'wn', false);
pll_register_string('all_brands', 'All brands', 'wn', false);
pll_register_string('brands', 'Brands', 'wn', false);
pll_register_string('type_yacht_name', 'Type yacht name here', 'wn', false);
pll_register_string('type', 'Type', 'wn', false);
pll_register_string('motor', 'motor', 'wn', false);
pll_register_string('sail', 'sail', 'wn', false);
pll_register_string('any', 'Any', 'wn', false);
pll_register_string('charter_destinations', 'Charter destinations', 'wn', false);
pll_register_string('exterior', 'Exterior', 'wn', false);
pll_register_string('interior', 'Interior', 'wn', false);
pll_register_string('video', 'Video', 'wn', false);
pll_register_string('360', '360 Gallery', 'wn', false);
pll_register_string('request_charter', 'Request charter', 'wn', false);
pll_register_string('yacht_description', 'Yacht description', 'wn', false);
pll_register_string('read_more', 'Read more', 'wn', false);
pll_register_string('characteristics', 'Characteristics', 'wn', false);
pll_register_string('location', 'Location', 'wn', false);
pll_register_string('key_features', 'Key features', 'wn', false);
pll_register_string('engines_and_machinery', 'Engines & machinery', 'wn', false);
pll_register_string('layout', 'Layout', 'wn', false);
pll_register_string('charter', 'Charter', 'wn', false);
pll_register_string('buy_yacht', 'Buy yacht', 'wn', false);
pll_register_string('year_refit', 'Year (refit)', 'wn', false);
pll_register_string('year_build', 'Year (build)', 'wn', false);
pll_register_string('year_build_refit', 'Year (build / refit)', 'wn', false);
pll_register_string('year', 'Year', 'wn', false);
pll_register_string('price', 'Price', 'wn', false);
pll_register_string('charter_rate', 'Charter rate', 'wn', false);
pll_register_string('per_week', 'Per week', 'wn', false);
pll_register_string('vat_paid', 'VAT Paid', 'wn', false);
pll_register_string('vat_unpaid', 'VAT Unpaid', 'wn', false);
pll_register_string('from_prefix', 'from ', 'wn', false);
pll_register_string('length', 'Length', 'wn', false);
pll_register_string('beam', 'Beam', 'wn', false);
pll_register_string('draft', 'Draft', 'wn', false);
pll_register_string('displacement', 'Displacement', 'wn', false);
pll_register_string('hull_material', 'Hull material', 'wn', false);
pll_register_string('cruising_speed', 'Cruising speed', 'wn', false);
pll_register_string('max_speed', 'Max speed', 'wn', false);
pll_register_string('berths', 'Berths', 'wn', false);
pll_register_string('guests', 'Guests', 'wn', false);
pll_register_string('cabins', 'Cabins', 'wn', false);
pll_register_string('crew', 'Crew', 'wn', false);
pll_register_string('engines', 'Engines', 'wn', false);
pll_register_string('power', 'Power', 'wn', false);
pll_register_string('moto_hours', 'Engine hours', 'wn', false);
pll_register_string('range', 'Range', 'wn', false);
pll_register_string('fuel_capacity', 'Fuel capacity', 'wn', false);
pll_register_string('water_capacity', 'Water capacity', 'wn', false);
pll_register_string('gray_water_capacity', 'Gray water capacity', 'wn', false);
pll_register_string('meters', 'm', 'wn', false);
pll_register_string('knots', 'kn', 'wn', false);
pll_register_string('hp', 'hp', 'wn', false);
pll_register_string('litres', 'l', 'wn', false);
pll_register_string('tonnes', 'tonnes', 'wn', false);
pll_register_string('mh', 'm/h', 'wn', false);
pll_register_string('nm', 'NM', 'wn', false);
pll_register_string('contact_seller', 'Contact seller', 'wn', false);
endif;
use Rakit\Validation\Validator;
/**
* Request charter form handler on server side
*
* @input $_POST
* @output {status, message}
*/
add_action('wp_ajax_requestCharter_action', 'requestCharter_action');
add_action('wp_ajax_nopriv_requestCharter_action', 'requestCharter_action');
function createValidator()
{
$validator = new Validator;
if (function_exists('pll_current_language')) {
if (pll_current_language() == 'ru') {
$validator->setMessages([
'first-name' => 'Введите ваше имя',
'last-name' => 'Введите вашу фамилию',
'email' => 'Введите вашу электронную почту',
'phone' => 'Укажите ваш номер телефона',
'acceptance-gdpr' => 'Согласитесь с политикой конфиденциальности',
'form_token' => 'Are you human?',
'enquiry_type' => 'Specify your nature of enquiry'
// etc
]);
} else {
$validator->setMessages([
'first-name' => 'Enter you first name',
'last-name' => 'Enter your last name',
'email' => 'Enter your email',
'phone' => 'Enter your phone number',
'acceptance-gdpr' => 'Accept privacy policy',
'form_token' => 'Are you human?',
'enquiry_type' => 'Specify your nature of enquiry'
// etc
]);
}
}
return $validator;
}
function requestCharter_action()
{
$data = $_POST;
// ! token check
if (wp_verify_nonce($_GET['wn_nonce'], 'wn_nonce')) {
$validator = createValidator();
$validation = $validator->make($_POST + $_FILES, [
'first-name' => 'required',
'last-name' => 'required',
'email' => 'required|email',
'acceptance-gdpr' => 'required',
'form_token' => 'required'
]);
// then validate
$validation->validate();
header('Content-type: application/json');
if ($validation->fails()) {
// not acceptable
http_response_code(206);
// handling errors
$errors = $validation->errors();
echo json_encode($errors->firstOfAll());
exit;
} else {
if (!validate_recaptcha($data['form_token'])) {
echo json_encode(['generic' => 'Are you a robot? Please try again!']);
exit;
}
unset($data['form_token']);
// OK
http_response_code(200);
// validation passes
send_formdata_mail('New charter request', $data);
}
}
}
/**
* Callback form handler on server side
*
* @input $_POST
* @output {status, message}
*/
add_action('wp_ajax_callbackForm_action', 'callbackForm_action');
add_action('wp_ajax_nopriv_callbackForm_action', 'callbackForm_action');
function callbackForm_action()
{
$data = $_POST;
// ! token check
if (wp_verify_nonce($_GET['wn_nonce'], 'wn_nonce')) {
$validator = createValidator();
$validation = $validator->make($_POST + $_FILES, [
'phone' => 'required|numeric',
'acceptance-gdpr' => 'required',
'form_token' => 'required'
]);
// then validate
$validation->validate();
header('Content-type: application/json');
if ($validation->fails()) {
// not acceptable
http_response_code(206);
// handling errors
$errors = $validation->errors();
echo json_encode($errors->firstOfAll());
exit;
} else {
if (!validate_recaptcha($data['form_token'])) {
echo json_encode(['generic' => 'Are you a robot? Please try again!']);
exit;
}
unset($data['form_token']);
// OK
http_response_code(200);
// validation passes
send_formdata_mail('New callback request', $data);
}
}
}
/**
* Contact form handler on server side
*
* @input $_POST
* @output {status, message}
*/
add_action('wp_ajax_contactForm_action', 'contactForm_action');
add_action('wp_ajax_nopriv_contactForm_action', 'contactForm_action');
function contactForm_action()
{
$data = $_POST;
// ! token check
if (wp_verify_nonce($_GET['wn_nonce'], 'wn_nonce')) {
$validator = createValidator();
$validation = $validator->make($_POST + $_FILES, [
'first-name' => 'required',
'last-name' => 'required',
'email' => 'required|email',
'acceptance-gdpr' => 'required',
'form_token' => 'required',
'enquiry_type' => 'required'
]);
// then validate
$validation->validate();
header('Content-type: application/json');
if ($validation->fails()) {
// not acceptable
http_response_code(206);
// handling errors
$errors = $validation->errors();
echo json_encode($errors->firstOfAll());
exit;
} else {
if (!validate_recaptcha($data['form_token'])) {
echo json_encode(['generic' => 'Are you a robot? Please try again!']);
exit;
}
unset($data['form_token']);
// OK
http_response_code(200);
// validation passes
send_formdata_mail('New contact form request', $data);
}
}
}
/**
* Subscribe form handler on server side
*
* @input $_POST
* @output {status, message}
*/
add_action('wp_ajax_subscribeForm_action', 'subscribeForm_action');
add_action('wp_ajax_nopriv_subscribeForm_action', 'subscribeForm_action');
function subscribeForm_action()
{
$data = $_POST;
// ! token check
if (wp_verify_nonce($_GET['wn_nonce'], 'wn_nonce')) {
$validator = createValidator();
$validation = $validator->make($_POST + $_FILES, [
'email' => 'required|email',
'acceptance-gdpr' => 'required',
'contact_type' => 'required',
'form_token' => 'required'
]);
// then validate
$validation->validate();
header('Content-type: application/json');
if ($validation->fails()) {
// not acceptable
http_response_code(206);
// handling errors
$errors = $validation->errors();
echo json_encode($errors->firstOfAll());
exit;
} else {
if (!validate_recaptcha($data['form_token'])) {
echo json_encode(['generic' => 'Are you a robot? Please try again!']);
exit;
}
unset($data['form_token']);
// OK
http_response_code(200);
// validation passes
send_formdata_mail('New subscription', $data);
}
}
}
function validate_recaptcha($token) {
$data = array(
'secret' => "6LcMWQcaAAAAAD3o-rcYTO29tg5JnFUP0ukmwns-",
'response' => $token
);
$verify = curl_init();
curl_setopt($verify, CURLOPT_URL, "https://www.google.com/recaptcha/api/siteverify");
curl_setopt($verify, CURLOPT_POST, true);
curl_setopt($verify, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($verify, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($verify, CURLOPT_RETURNTRANSFER, true);
$response = json_decode(curl_exec($verify), true);
return $response['success'] && $response['score'] >= 0.5;
}
function send_formdata_mail($subject, $data)
{
$to = false;
if (isset($data['post_type'])) {
$to = 'info@westnautical.com';
switch ($data['post_type']) {
case 'yacht':
$to = 'charter@westnautical.com';
break;
case 'vacancy':
$to = 'crew@westnautical.com';
break;
case 'service_wn':
$post = get_post($_POST['post_id']);
if ($post && stripos($post->name, 'sell') !== false) {
$to = 'sales@westnautical.com';
}
break;
}
unset($data['post_type']);
unset($data['post_id']);
}
if (isset($_POST['enquiry_type'])) {
$to = 'info@westnautical.com';
switch ($_POST['enquiry_type']) {
case 'crew':
$to = 'crew@westnautical.com';
break;
case 'sales':
$to = 'info@westnautical.com';
break;
case 'charter':
$to = 'info@westnautical.com';
break;
case 'management':
$to = 'info@westnautical.com';
break;
}
}
$message = '';
foreach ($data as $key => $value) {
$key = sanitize_text_field($key);
$value = sanitize_text_field($value);
$message .= "$key: $value\n";
}
$log_path = get_template_directory() . '/contacts/' . str_replace('@', '-', $to) . '.log';
file_put_contents($log_path, sprintf('%s: %s', date('d/m/Y H:i'), $subject) . PHP_EOL, FILE_APPEND);
file_put_contents($log_path, $message, FILE_APPEND);
file_put_contents($log_path, '=====================================================================' . PHP_EOL . PHP_EOL, FILE_APPEND);
if ($to === false) {
if (tr_options_field('system_notification_email_address')):
$to = tr_options_field('system_notification_email_address');
else:
$to = 'info@westnautical.com';
endif;
}
$header = [];
$headers[] = 'From: West Nautical ' . "\r\n";
if (isset($data['email'])) {
$headers[] = 'Reply-To:' . $data['email'];
}
$result = wp_mail($to, $subject, $message, $headers);
//$result = wp_mail('gavin@teamvalleyweb.co.uk', $subject, $message, $headers);
}
if (!defined('ABSPATH')) exit; // no direct access
use Simettric\WPQueryBuilder;
use Simettric\WPQueryBuilder\Builder;
class FilterController
{
/**
*
* FilterController for data $_GET and values
*
* @input $_GET, query_vars
* @output Range data for filter and query, current data
* @settings
* @since 1.0.0
*
*/
const DEFAULT_CURRENCY = "eur";
// Default values
public $charter = [
'range' => ['min' => 1, 'max' => 20000000]
];
public $prices = [
'eur' => [
'range' => ['min' => 1, 'max' => 20000000]
],
'gbp' => [
'range' => ['min' => 1, 'max' => 20000000]
],
'usd' => [
'range' => ['min' => 1, 'max' => 20000000]
],
'rub' => [
'range' => ['min' => 1, 'max' => 20000000]
]
];
public $length = [
'range' => ['min' => 0, 'max' => 50]
];
public $year = [
'range' => ['min' => 2000, 'max' => 2020]
];
public $location = [
'range' => [0 => 'Все местоположения']
];
public $search_input = [
'current' => ''
];
public $debug = [];
/**
* Names of the instance properties populated by the expensive block
* below (a full, unbounded `posts_per_page => -1` scan of every
* "yacht" post, followed by a get_post_meta()/taxonomy lookup per post
* per filter dimension). These describe the *available filter ranges*
* (min/max price, brand list, region list, etc.) - they only change
* when yacht/destination data changes, not per-visitor, so they are
* safe to cache across requests instead of recomputing on every single
* page view of the yacht listing / search / charter pages.
*/
const CACHED_RANGE_PROPS = ['length', 'year', 'location', 'region', 'brands', 'guests', 'types', 'charter', 'prices'];
function __construct()
{
$this->brands['range'] = [0 => pll__('All brands', 'wn')];
$this->guests['range'] = [0 => pll__('Any', 'wn')];
$this->types['range'] = [0 => pll__('Any', 'wn')];
$this->region['range'] = [0 => pll__('Any', 'wn')];
$this->guests['range'] = [0 => pll__('Any', 'wn')];
$this->currency = get_query_var('currency') ?: 'eur';
$this->debug[] = 'get query var currency';
$this->setSettings();
$cache_key = $this->getRangesCacheKey();
$cached = get_transient($cache_key);
if (is_array($cached) && count(array_diff(self::CACHED_RANGE_PROPS, array_keys($cached))) === 0) {
foreach (self::CACHED_RANGE_PROPS as $prop) {
$this->$prop = $cached[$prop];
}
$this->debug[] = 'filter ranges loaded from transient cache';
} else {
$this->query = $this::getFullQuery();
$this::setMinMax();
$this::getLocations();
$this::getRegions();
$this::getYearRange();
$this->brands['range'] = $this::getBrands();
$this->guests['range'] = $this::getGuests();
$this->types['range'] = $this::getTypes();
$this::getCharterRates();
$this::getPrices();
unset($this->query);
$this->debug[] = 'full query was unset';
$to_cache = [];
foreach (self::CACHED_RANGE_PROPS as $prop) {
$to_cache[$prop] = $this->$prop;
}
// 6 hour TTL as a safety net; save_post/delete_post hooks
// (registered in includes/init/functions.php) clear this
// immediately whenever a yacht or destination is edited, so
// the TTL is a backstop, not the primary invalidation path.
set_transient($cache_key, $to_cache, 6 * HOUR_IN_SECONDS);
}
$this::setPostData();
}
/**
* Cache key for the computed filter ranges. Ranges only vary by
* currency (prices) and current language (region/brand names go
* through Polylang translation helpers), so both are baked into the key.
*/
private function getRangesCacheKey()
{
$lang = function_exists('pll_current_language') ? pll_current_language() : 'default';
return 'wn_filter_ranges_' . md5($this->currency . '|' . $lang);
}
/**
* Clear every cached filter-range variant. Hooked to save/delete of
* "yacht" and "destination" posts (see includes/init/functions.php)
* since editing either can change the available filter ranges.
*/
public static function clearRangesCache()
{
global $wpdb;
$like = $wpdb->esc_like('_transient_wn_filter_ranges_') . '%';
$names = $wpdb->get_col($wpdb->prepare(
"SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s",
$like
));
foreach ($names as $name) {
delete_option($name);
delete_option(str_replace('_transient_', '_transient_timeout_', $name));
}
}
/*
* @input Getting post data
* @return [Array]
* @since 1.0.0
*/
private function setSettings()
{
if (is_int(tr_options_field('filter_min_year')))
$this->year['range']['min'] = intval(tr_options_field('filter_min_year'));
if (is_int(tr_options_field('filter_max_year')))
$this->year['range']['max'] = intval(tr_options_field('filter_max_year'));
}
private function setPostData()
{
$postdata = $_GET;
$this->charter['current']['min'] = isset($postdata['charter_min']) ? intval($postdata['charter_min']) : $this->charter['range']['min'];
$this->charter['current']['max'] = isset($postdata['charter_max']) ? intval($postdata['charter_max']) : $this->charter['range']['max'];
$this->prices['current']['min'] = isset($postdata['price_min']) ? intval($postdata['price_min']) : $this->prices['range']['min'];
$this->prices['current']['max'] = isset($postdata['price_max']) ? intval($postdata['price_max']) : $this->prices['range']['max'];
$this->length['current']['min'] = isset($postdata['length_min']) ? intval($postdata['length_min']) : $this->length['range']['min'];
$this->length['current']['max'] = isset($postdata['length_max']) ? intval($postdata['length_max']) : $this->length['range']['max'];
$this->year['current']['min'] = isset($postdata['year_min']) ? intval($postdata['year_min']) : $this->year['range']['min'];
$this->year['current']['max'] = isset($postdata['year_max']) ? intval($postdata['year_max']) : $this->year['range']['max'];
$this->location['current'] = isset($postdata['location']) ? $postdata['location'] : $this->location['range'][0];
$this->region['current'] = isset($postdata['region']) ? $postdata['region'] : $this->region['range'][0];
$this->brands['current'] = isset($postdata['brands']) ? $postdata['brands'] : $this->brands['range'][0];
$this->guests['current'] = isset($postdata['guests']) ? $postdata['guests'] : $this->guests['range'][0];
$this->types['current'] = isset($postdata['type']) ? $postdata['type'] : $this->types['range'][0];
$this->types['current'] = isset($postdata['type']) ? $postdata['type'] : $this->types['range'][0];
$this->search_input = isset($postdata['search_input']) ? $postdata['search_input'] : '';
$this->currency = get_query_var('currency') ?? self::DEFAULT_CURRENCY;
set_query_var('currency', $this->currency);
}
/*
* Sets mins&maxes for filter from posts
*
* @output
*/
private function getFullQuery()
{
if (!isset($this->query)) {
$builder = new Builder();
$args = $builder->addPostType('yacht')->getParameters();
$args['posts_per_page'] = '-1';
$query = new WP_Query($args);
$this->debug[] = 'get full query';
return $query;
} else {
$this->debug[] = 'full query was already set';
return $query;
}
}
private function setMinMax()
{
foreach ($this->query->posts as $key => $post) {
// $prices[$key] = intval(get_post_meta($post->ID, 'price_cc_' . $this->currency, true));
$year[$key] = intval(get_post_meta($post->ID, 'year', true));
$length[$key] = intval(str_replace(',', '.', get_post_meta($post->ID, 'length', true)));
}
$this->length['range'] = $this::roundLength($length);
$this->year['range']['max'] = tr_options_field('filter_max_year') ?: 2020;
$this->debug[] = 'min max values loaded';
}
public function getPrices()
{
foreach ($this->prices as $currency => $range) {
foreach ($this->query->posts as $key => $post) {
$values[$currency][] = intval(get_post_meta($post->ID, 'price_cc_' . $currency, true) ?: [0]);
}
$values[$currency] = array_filter($values[$currency]);
$values[$currency] = array_unique($values[$currency]);
$prices[$currency]['range']['min'] = intval(min($values[$currency])) ?? 0;
$prices[$currency]['range']['max'] = intval(max($values[$currency]));
// $prices[$currency]['range']['min'] = $this->roundPrices($prices[$currency]['range']['min'], 100);
// $prices[$currency]['range']['max'] = $this->roundPrices($prices[$currency]['range']['max'], 100);
}
$this->debug[] = 'prices in all currencies loaded';
$this->prices = $prices;
$this->prices['range'] = $prices[$this->currency]['range'];
$this->prices['current'] = $prices[$this->currency]['range'];
// echo '';
// var_dump($this->prices);
// echo '';
}
public function roundPrices($value, $profit)
{
$round_to = 20;
$round_to = intval($round_to) / 100;
$price = $value * floatval($profit) / 100;
$price = ceil($price);
$count = strlen((string) $price);
$price = round($price, floor(-$count * $round_to), PHP_ROUND_HALF_UP); // round setting
return $price;
}
public function getCharterRates()
{
foreach ($this->query->posts as $key => $post) {
$values[] = intval(get_post_meta($post->ID, 'charter_rate', true) ?: [0]);
}
$values = array_filter($values);
$values = array_unique($values);
$charter_rates['range']['min'] = intval(min($values));
$charter_rates['range']['max'] = intval(max($values));
$charter_rates['range']['min'] = $this->roundPrices($charter_rates['range']['min'], 100);
$charter_rates['range']['max'] = $this->roundPrices($charter_rates['range']['max'], 100);
$this->debug[] = 'charter rates loaded';
$this->charter = $charter_rates;
}
public function getLocations()
{
$locations = [];
foreach ($this->query->posts as $key => $post) {
$country = get_post_meta($post->ID, 'country');
$country = array_shift($country);
$locations[$key] = $country;
}
$locations = array_filter($locations);
$locations = array_unique($locations);
$this->debug[] = 'locations loaded';
$this->location['range'] = array_merge($this->location['range'], $locations);
}
public function getRegions()
{
$regions = [];
foreach ($this->query->posts as $key => $post) {
// $post_translated_id = $post->ID;
$post_translated_id = pll_get_post($post->ID);
$destinations = tr_posts_field('charter_destinations', $post_translated_id);
if (is_array($destinations)) :
foreach ($destinations as $key => $destination) {
# code...
$destination_translated_id = pll_get_post($destination) ? pll_get_post($destination) : $destination;
$regions[] = tr_posts_field('charter_region', $destination_translated_id)[0];
}
endif;
}
$regions = array_filter($regions);
$regions = array_unique($regions);
asort($regions);
$this->debug[] = 'regions loaded';
$this->region['range'] = array_merge($this->region['range'], $regions);
}
private function roundLength($length)
{
$length = array_filter($length);
if (!empty($length) & intval($length) > 0) {
$length_min = round(intval(min($length) - 1), 1, PHP_ROUND_HALF_DOWN);
$length_min = floor($length_min);
$length_max = round(intval(max($length) + 1), 1, PHP_ROUND_HALF_UP);
$length_max = ceil($length_max);
$this->debug[] = 'length rounded';
return ['min' => intval($length_min), 'max' => intval($length_max)];
} else {
$this->debug[] = 'length round function get empty array';
return $this->length['range'];
}
}
public function getBrands()
{
// TODO: get charter status
// TODO: get charter query
foreach ($this->query->posts as $key => $post) {
$values[] = intval(get_post_meta($post->ID, 'charter_rate', true) ?: [0]);
}
$charter_query = [];
foreach ($this->query->posts as $key => $post) {
$charter_status = intval(get_post_meta($post->ID, 'available_for_charter', true));
if ($charter_status == get_query_var('charter') ?? 0)
$charter_query[] = $post;
}
$terms = [];
$args = array('orderby' => 'name', 'order' => 'ASC', 'fields' => 'all');
foreach ($charter_query as $key => $post) {
$terms[] = wp_get_post_terms($post->ID, 'brands', $args);
}
$brands = [];
foreach ($terms as $term) {
$brands[] = @$term[0]->name ?? $term->name;
}
$brands = array_filter($brands);
$brands = array_unique($brands);
asort($brands);
$this->debug[] = 'brands loaded';
$this->brands['range'] = array_merge($this->brands['range'], $brands);
return $this->brands['range'];
}
public function getGuests()
{
$guests_exact = [];
foreach ($this->query->posts as $key => $post) {
$guest_field = get_post_meta($post->ID, 'guests');
// $brand = array_shift($brands[0]);
$guests_exact[$key] = @$guest_field[0] ? $guest_field[0] : 0;
}
$guests_exact = array_filter($guests_exact);
$guests_exact = array_unique($guests_exact);
foreach ($guests_exact as $value) {
if ($value < 5) :
$guests_selects[] = $value;
else :
$value = intval($value);
$value = round($value / 5) * 5;
$guests_selects[] = $value;
endif;
}
$guests_selects = array_unique($guests_selects);
asort($guests_selects);
$this->debug[] = 'brands loaded';
$this->guests['range'] = array_merge($this->guests['range'], $guests_selects);
return $this->guests['range'];
}
public function getYearRange()
{
$build_years = [];
$result_array = [];
foreach ($this->query->posts as $key => $post) {
$build_years[] = get_post_meta($post->ID, 'year');
}
foreach ($build_years as $key => $year) {
$result_array[] = intval(array_shift($year));
}
// var_dump($result_array);
$build_years = array_filter($result_array);
$build_years = array_unique($build_years);
asort($build_years);
$this->debug[] = 'years loaded';
$this->year['range']['min'] = min($build_years) > 1900 ? min($build_years) : 1990;
$this->year['range']['max'] = max($build_years) > 2000 ? max($build_years) : date('Y');
return $this->year['range'];
}
public function getTypes()
{
$boat_types = [];
foreach ($this->query->posts as $key => $post) {
$boat_type = get_post_meta($post->ID, 'boat_type');
// $boat_type = array_shift($boat_type[0]);
$boat_type = $boat_type[0];
// if ($boat_type == 'motor') $boat_type = pll__('motor', 'wn');
// if ($boat_type == 'sail') $boat_type = pll__('sail', 'wn');
$boat_types[$key] = $boat_type;
}
$boat_types = array_filter($boat_types);
$boat_types = array_unique($boat_types);
$this->debug[] = 'boat types loaded';
$this->types['range'] = array_merge($this->types['range'], $boat_types);
return $this->types['range'];
}
}
if (!defined('ABSPATH')) exit; // no direct access
use Simettric\WPQueryBuilder;
use Simettric\WPQueryBuilder\Builder;
use Simettric\WPQueryBuilder\TaxonomyQuery;
use Simettric\WPQueryBuilder\TaxonomyQueryCollection;
use Simettric\WPQueryBuilder\MetaQuery;
use Simettric\WPQueryBuilder\MetaQueryCollection;
// echo get_query_var('paged');
class QueryController extends FilterController
{
/**
*
* Yacht WP_Query class to load
* yachts with specified settings
*
* @input query_vars, $post
* @output conditions, buildQuery() function outputs WP_Query with $args
* @settings
* @since 1.0.0
*
*/
public $settings = [
'show_new' => 1,
'show_stock' => 1,
'show_superyachts' => 1,
'show_brokerage' => 1,
'show_sold' => 1,
'show_archive' => 0,
'show_charter_only' => 0,
'show_sale_only' => 0,
'pagination' => 1,
'posts_per_page' => 9,
];
public $search_input = '';
const _NEW = 0;
const _STOCK = 1;
const _BROKERAGE = 2;
const _SOLD = 3;
const _ARCHIVE = 4;
const _CONSTRUCTED = 5;
const _SUPERYACHT = 6;
function __construct($settings)
{
// Get query_vars
if ($settings) $this->settings = $settings;
$this->taxonomy = get_query_var('taxonomy') ?: '';
$this->terms = get_query_var('term') ?: '';
$this->yacht_status = get_query_var('yacht_status') ?: '2';
// Construct FilterController::Class
parent::__construct();
// Build Conditions for Query
$this->price_condition = $this::buildPrice();
$this->charterRate_condition = $this::buildCharterRate();
$this->length_condition = $this::buildLength();
$this->year_condition = $this::buildYear();
$this->location_condition = $this::buildLocation();
$this->types_condition = $this::buildTypes();
$this->brands_condition = $this::buildBrands();
$this->status_condition = $this::buildStatus();
$this->taxonomies_condition = $this::buildTaxonomies();
$this->guests_condition = $this::buildGuests();
$this->region_condition = $this::buildRegions();
$this->search_input = $this::buildSearch();
$this->sale_condition = $this::buildAvailability();
$this->charter_condition = $this::buildCharter();
// Generate WP_Query
$this->query = $this::buildQuery();
}
private function buildQuery()
{
$builder = new Builder();
$builder->addPostType('yacht');
$builder->createMainMetaQuery("AND");
if (!empty($this->status_condition)) $builder->addMetaQueryCollection($this->status_condition);
if (!empty($this->charter_condition) && $this->settings['show_charter_only']) $builder->addMetaQueryCollection($this->charter_condition);
if (!empty($this->region_condition) && $this->settings['show_charter_only']) $builder->addMetaQueryCollection($this->region_condition);
if (!empty($this->charterRate_condition) && $this->settings['show_charter_only']) {
$builder->addMetaQueryCollection($this->charterRate_condition);
/*if( $_SERVER['REMOTE_ADDR'] == '77.95.33.48' ) {
echo '';
echo 3;
print_r($this->charterRate_condition);
echo '';
}*/
}
if (!empty($this->length_condition)) $builder->addMetaQueryCollection($this->length_condition);
if (!empty($this->guests_condition)) $builder->addMetaQueryCollection($this->guests_condition);
if (!empty($this->year_condition)) $builder->addMetaQueryCollection($this->year_condition);
if (!empty($this->search_input_condition)) $builder->addMetaQueryCollection($this->search_input_condition);
if (!empty($this->location_condition) && $this->settings['show_charter_only']) $builder->addMetaQueryCollection($this->location_condition);
if (!empty($this->price_condition) && $this->settings['show_charter_only']) {
//$builder->addMetaQueryCollection($this->price_condition);
/*if( $_SERVER['REMOTE_ADDR'] == '77.95.33.48' ) {
echo '';
echo 1;
print_r($this->price_condition);
echo '';
}*/
}
else
{
$builder->addMetaQueryCollection($this->price_condition);
/*if( $_SERVER['REMOTE_ADDR'] == '77.95.33.48' ) {
echo '';
echo 2;
print_r($this->price_condition);
echo '';
}*/
}
if (!empty($this->types_condition)) $builder->addMetaQueryCollection($this->types_condition);
if (!empty($this->sale_condition) && $this->settings['show_sale_only']) $builder->addMetaQueryCollection($this->sale_condition);
$builder->createMainTaxonomyQuery("AND");
if (!empty($this->taxonomies_condition)) $builder->addTaxonomyQueryCollection($this->taxonomies_condition);
if (!empty($this->brands_condition)) $builder->addTaxonomyQueryCollection($this->brands_condition);
//search
$builder->search($this->search_input);
$args = $builder->getParameters();
if (function_exists('pll_current_language'))
$args['lang'] = pll_current_language();
// Enable pagination
if ($this->settings['pagination'] === 1) {
$args['posts_per_page'] = $this->settings['posts_per_page'] ?: 9;
$args['paged'] = get_query_var('paged');
if (isset($_GET['search-page']) && (int)$_GET['search-page'] >= 1) {
$args['paged'] = (int)$_GET['search-page'];
}
} else {
$args['posts_per_page'] = $this->settings['posts_per_page'] ?: 9;
}
// Order settings
// $args['post_status'] = 'public';
$args['meta_query'][] = array(
// 'relation' => 'AND',
// 'order_status' => array(
// 'key' => 'yacht_status',
// ),
'order_length' => array(
'key' => 'length',
'type' => 'NUMERIC'
),
);
// $args['orderby'] = array(
// // 'order_status' => @$this->settings['order_settings'] ?: 'ASC',
// 'order_length' => 'DESC',
// );
if( $this->settings['show_charter_only'] ) :
$args['orderby'] = 'relevance';
else :
$args['meta_key'] = 'price';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'DESC';
endif;
$query = new WP_Query($args);
return $query;
}
private function buildTaxonomies()
{
if (
!empty(get_query_var('term')) &&
!empty(get_query_var('taxonomy'))
) {
$taxonomies_condition = new TaxonomyQueryCollection('OR');
$taxonomies_condition
->add(TaxonomyQuery::create($this->taxonomy, 'slug', $this->terms));
return $taxonomies_condition;
} else {
return null;
}
}
private function buildCharterRate()
{
if (@$this->settings['show_charter_only'] === 1) :
$field_name = 'charter_rate';
$charterRate_condition = new MetaQueryCollection('AND');
$charterRate_condition
->add(MetaQuery::create($field_name, $this->charter['current']['min'], '>=', 'NUMERIC'))
->add(MetaQuery::create($field_name, $this->charter['current']['max'], '<=', 'NUMERIC'));
return $charterRate_condition;
endif;
}
private function buildPrice()
{
//$field_name = 'price_cc_' . $this->currency;
///$price_wrapper = new MetaQueryCollection('OR');
//$price_condition = new MetaQueryCollection('AND');
$field_name = 'price';
$price_condition = new MetaQueryCollection('AND');
$price_condition
->add(MetaQuery::create($field_name, $this->prices['current']['min'], '>=', 'NUMERIC'))
->add(MetaQuery::create($field_name, $this->prices['current']['max'], '<=', 'NUMERIC'));
// ->add(MetaQuery::create($field_name, [$this->prices['current']['min'], $this->prices['current']['max']], 'BETWEEN', 'NUMERIC'));
// ->add(MetaQuery::create($field_name, $this->prices['current']['min'], '=', 'NUMERIC'))
// ->add(MetaQuery::create($field_name, $this->prices['current']['max'], '=', 'NUMERIC'))
// ->add(MetaQuery::create($field_name, '', '=', 'NUMERIC'))
//$price_condition->add(MetaQuery::create($field_name, '', 'NOT EXISTS'));
/*if( $_SERVER['REMOTE_ADDR'] == '77.95.33.48' ) {
echo '';
var_dump($price_condition);
echo '';
}*/
//return $price_wrapper;
return $price_condition;
}
private function buildYear()
{
$year_condition = new MetaQueryCollection('OR');
$year_condition
->add(MetaQuery::create('year', [$this->year['current']['min'], $this->year['current']['max']], 'BETWEEN', 'NUMERIC'))
->add(MetaQuery::create('year', '', 'NOT EXISTS'));
return $year_condition;
}
private function buildLength()
{
$length_condition = new MetaQueryCollection('OR');
$length_condition
->add(MetaQuery::create('length', [$this->length['current']['min'], $this->length['current']['max']], 'BETWEEN', 'NUMERIC'))
->add(MetaQuery::create('length', '', 'NOT EXISTS'));
return $length_condition;
}
private function buildStatus()
{
$status_condition = new MetaQueryCollection('OR');
if ($this->settings['show_new'] === 1)
$status_condition->add(MetaQuery::create('yacht_status', self::_NEW, '='));
if ($this->settings['show_superyachts'] === 1)
$status_condition->add(MetaQuery::create('yacht_status', self::_SUPERYACHT, '='));
if ($this->settings['show_stock'] === 1)
$status_condition->add(MetaQuery::create('yacht_status', self::_STOCK, '='));
if ($this->settings['show_brokerage'] === 1)
$status_condition->add(MetaQuery::create('yacht_status', self::_BROKERAGE, '='));
if ($this->settings['show_sold'] === 1)
$status_condition->add(MetaQuery::create('yacht_status', self::_SOLD, '='));
if ($this->settings['show_archive'] === 1)
$status_condition->add(MetaQuery::create('yacht_status', self::_ARCHIVE, '='));
return $status_condition;
}
private function buildLocation()
{
if ($this->location['current'] != $this->location['range'][0]) {
$location_condition = new MetaQueryCollection('OR');
$location_condition
->add(MetaQuery::create('country', $this->location['current'], '='))
->add(MetaQuery::create('country', '', 'NOT EXISTS'));
return $location_condition;
} else {
return null;
}
}
private function buildRegions()
{
/**
* Get current region name and compares to destinations. Finds ID and put in query builder;
*/
// if ($this->region['current'] != $this->region['range'][0]) {
if (1 == 1) {
// NOTE: the condition above was originally
// `$this->region['current'] != $this->region['range'][0]`
// (i.e. "only do this when a region filter is actually
// selected"), but got hard-disabled to `1 == 1` at some point,
// so this expensive destination scan now runs unconditionally
// on every single yacht search/listing request. Restoring the
// original condition would change what WHERE clause gets built
// (an empty MetaQueryCollection is still added to the query
// builder today) and that's not something that can be safely
// verified without a live WP install, so behaviour is left
// exactly as-is here. Instead, the expensive part - scanning
// every "destination" post and reading its charter_region meta
// one post at a time - is cached, since that mapping only
// changes when destinations are edited, not per visitor.
$regions = get_transient(self::getDestinationRegionsCacheKey());
if (!is_array($regions)) {
$destinations = get_posts([
'numberposts' => -1,
'post_type' => 'destination',
]);
$regions = [];
foreach ($destinations as $key => $destination) {
$regions[$destination->ID] = tr_posts_field('charter_region', $destination->ID);
}
set_transient(self::getDestinationRegionsCacheKey(), $regions, 6 * HOUR_IN_SECONDS);
}
// [id of destination] with that region
$destinations_search = [];
foreach ($regions as $id => $destination_regions) {
if (is_array($destination_regions))
foreach ($destination_regions as $region_name) {
if ($region_name == $this->region['current']) {
$destinations_search[] = $id;
}
}
}
$region_condition = new MetaQueryCollection('OR');
foreach ($destinations_search as $key => $destination_id) {
pll_current_language() == 'en' ? $lang = 'ru' : $lang = 'en';
$destination_id_lang_fallback = pll_get_post($destination_id, $lang);
$region_condition
->add(MetaQuery::create('charter_destinations', $destination_id, 'LIKE'));
$region_condition
->add(MetaQuery::create('charter_destinations', $destination_id_lang_fallback, 'LIKE'));
}
return $region_condition;
} else {
return null;
}
}
private static function getDestinationRegionsCacheKey()
{
$lang = function_exists('pll_current_language') ? pll_current_language() : 'default';
return 'wn_destination_regions_' . md5($lang);
}
/**
* Clear the cached destination -> region-name map. Hooked to
* save/delete of "destination" posts in includes/init/functions.php.
*/
public static function clearDestinationRegionsCache()
{
global $wpdb;
$like = $wpdb->esc_like('_transient_wn_destination_regions_') . '%';
$names = $wpdb->get_col($wpdb->prepare(
"SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s",
$like
));
foreach ($names as $name) {
delete_option($name);
delete_option(str_replace('_transient_', '_transient_timeout_', $name));
}
}
private function buildBrands()
{
if (isset($this->brands['current']) && $this->brands['current'] != $this->brands['range'][0]) {
$taxonomies_condition = new TaxonomyQueryCollection('OR');
$brand = strtolower($this->brands['current']);
// var_dump($brand);
// var_dump(get_taxonomy('brands'));
$taxonomies_condition
->add(TaxonomyQuery::create('brands', 'name', $this->brands['current'], true, 'IN'));
return $taxonomies_condition;
} else {
return null;
}
}
public function pagination()
{
if (!isset($this->query)) : global $wp_query;
$query = $wp_query;
else : $query = $this->query;
endif;
$big = 999999999;
$total = isset($query->max_num_pages) ? $query->max_num_pages : 1;
$args = array(
'format' => (isset($_GET['search_input']) && !empty($_GET['search_input']) ? '?search-page=' : '?page=') . '%#%',
'current' => max(1, (isset($_GET['search-page']) && !empty($_GET['search-page']) ? (int)$_GET['search-page'] : get_query_var('paged'))),
'prev_next' => true,
'prev_text' => '
',
'next_text' => '
',
'total' => $total
);
if (!isset($_GET['search_input']) || empty($_GET['search_input'])) {
if (get_option('permalink_structure')) : $args['base'] = str_replace($big, '%#%', get_pagenum_link($big));
else : $args['base'] = add_query_arg('page', '%#%');
endif;
}
// echo previous_posts_link('Newer posts');
echo paginate_links($args);
// echo next_posts_link('Older posts');
}
private function buildGuests()
{
if (isset($this->guests['current']) && $this->guests['current'] != $this->guests['range'][0]) :
$guests_condition = new MetaQueryCollection('OR');
$guests_condition
->add(MetaQuery::create('guests', [0, $this->guests['current']], 'BETWEEN', 'NUMERIC'))
->add(MetaQuery::create('guests', '', 'NOT EXISTS'));
return $guests_condition;
endif;
}
private function buildTypes()
{
if (isset($this->types['current']) && $this->types['current'] != $this->types['range'][0]) {
$types_condition = new MetaQueryCollection('OR');
$types_condition
->add(MetaQuery::create('boat_type', $this->types['current'], '='))
->add(MetaQuery::create('boat_type', pll__($this->types['current']), '='))
->add(MetaQuery::create('boat_type', '', 'NOT EXISTS'));
return $types_condition;
} else {
return null;
}
}
private function buildSearch()
{
if (isset($this->search_input) && !empty($this->search_input)) {
return $this->search_input;
} else {
return null;
}
}
private function buildCharter()
{
if (@$this->settings['show_charter_only'] === 1) :
$charter_condition = new MetaQueryCollection('OR');
$charter_condition
->add(MetaQuery::create('available_for_charter', 0, '!='));
return $charter_condition;
else :
return null;
endif;
}
private function buildAvailability()
{
if (@$this->settings['show_sale_only'] === 1) :
$sale_condition = new MetaQueryCollection('OR');
$sale_condition->add(MetaQuery::create('available_for_sale', 1, '='));
return $sale_condition;
else :
return null;
endif;
}
}
/*========================================================================
= CUSTOM BUTTON AND FUNCTION FOR GENERATE PDF FILE =
========================================================================*/
// add_action('post_submitbox_misc_actions', 'generate_pdf_button');
// function generate_pdf_button()
// {
// if (get_post_type() == 'yacht') {
// $html = '';
// $html .= '
';
// $html .= '
';
// echo $html;
// }
// }
/*===== End of CUSTOM BUTTON AND FUNCTION FOR GENERATE PDF FILE ======*/
/*===========================================================
= PRINCESS YACHT CLASSES HELPER CLASS =
===========================================================*/
class Yacht
{
/**
*
* Settings
*
*/
// New yachts taxonomy to display in catalog
const CATALOG_TAXONOMY = 'classes';
// Currency symbol
const EURO = "€";
const DOLLAR = " $";
const RUB = " ₽";
const POUND = " £";
const DEFAULT_CURRENCY = "euro";
const NEW_YACHT = 'Яхта под заказ';
const STOCK_YACHT = 'Яхта в наличии';
const USED_YACHT = 'Брокеражная яхта';
const SOLD_YACHT = 'Продано';
const ARCHIVE_YACHT = 'В архиве';
const CONSTRUCTED_YACHT = 'Флот';
const SUPERYACHT = 'Суперъяхта';
// Declare variable types
public $post_type;
public $ID = '';
public $featured_image = '';
public $media = '';
public $post_title = '';
public $status = '';
public $status_label = '';
public $post_content = '';
public $fields = '';
public $plans = '';
public $dedicated_person = '';
public $options = '';
public $pdf = '';
public $notes = '';
public $reviews = '';
public $profile_image = '';
public $terms = '';
function __construct()
{
$this->featured_image = self::getFeaturedImage();
$this->media = new YachtMedia;
$this->post_title = get_the_title();
$this->options = new YachtOptions();
$this->status = self::getStatus();
$this->post_content = self::getPostContent();
$this->fields = new YachtFields;
$this->plans = self::getPlans();
$this->pdf = tr_posts_field('pdf');
$this->files = tr_posts_field('reviews_files');
$this->related = self::getRelated();
$this->terms = [
wp_get_post_terms($this->ID, 'series'),
wp_get_post_terms($this->ID, 'tags'),
wp_get_post_terms($this->ID, 'brands'),
];
}
public function getStatusClass($status = ''): string
{
if (empty($status)) $status = $this::getStatus();
switch ($status) {
case 0:
$status_class = 'yacht-card__new';
break;
case 1:
$status_class = 'yacht-card__stock';
break;
case 2:
$status_class = 'yacht-card__brokerage';
break;
case 3:
$status_class = 'yacht-card__sold';
break;
case 4:
$status_class = 'yacht-card__archive';
break;
case 5:
$status_class = 'yacht-card__constructed';
break;
case 6:
$status_class = 'yacht-card__superyachts';
break;
default:
$status_class = 'yacht-card__brokerage';
break;
}
return $status_class;
}
private function getRelated(): array
{
$related = [];
$related = tr_posts_field('recommendations');
$related_id = [];
if (is_array($related)) {
foreach ($related as $key => $value) {
$related_id[] = $value['select_yacht'];
}
}
return $related_id;
}
private function getPost(): object
{
global $post;
return $post;
}
private function getContent($field = ''): string
{
if ($content = tr_posts_field($field)) : return $content;
else : return '';
endif;
}
private function getPostContent(): string
{
if (tr_posts_field('post_content')) {
$post_content = tr_posts_field('post_content');
$post_content = apply_filters('the_content', $post_content);;
} else {
$post_content = '';
}
return $post_content;
}
public function getStatus(): int
{
return intval(tr_posts_field('yacht_status'));
}
public function getYachtID(): int
{
return get_the_id();
}
public function getProfileImage()
{
$image = '';
$html = '';
$alt = 'Профиль ' . get_the_title();
if ($image_id = tr_posts_field('profile_image')) {
$profile_width = tr_posts_field('profile_width') ?: '100';
} else {
$profile_width = '100';
}
$image = wp_get_attachment_image_url($image_id, 'profile_thumbnail');
$srcset = get_attachment_image_srcset($image_id);
$alt = get_post_meta($image_id, '_wp_attachment_image_alt', true) ?: $alt;
// return $image;
$html .= <<
HTML;
return $html;
}
public function getFeaturedImage($tag = 1): string
{
$id = tr_posts_field('featured_image') ?: tr_posts_field('exterior')[0];
$alt = get_post_meta($id, '_wp_attachment_image_alt', true) ?: 'Яхта ' . get_the_title() . ' | ID: ' . get_the_id();
return wp_get_attachment_image($id, 'half_width');
// if ($tag == 0) {
// return wp_get_attachment_image_url($id, 'full_width');
// } else {
// return get_responsive_image($id, 'half_width', 'yacht-featured-image', $alt, get_permalink());
// }
}
private function getPlans(): array
{
tr_posts_field('plans') ? $plans = tr_posts_field('plans') : $plans = [];
return $plans;
}
private function getHighlights(): array
{
tr_posts_field('highlights') ? $highlights = tr_posts_field('highlights') : $highlights = [];
return $highlights;
}
public function getStatusLabel($status = ''): string
{
if (!$status) $status = $this->getStatus();
switch ($status) {
case 0:
$label = $this::NEW_YACHT;
break;
case 1:
$label = $this::STOCK_YACHT;
break;
case 2:
$label = $this::USED_YACHT;
break;
case 3:
$label = $this::SOLD_YACHT;
break;
case 4:
$label = $this::ARCHIVE_YACHT;
break;
case 5:
$label = $this::CONSTRUCTED_YACHT;
break;
case 6:
$label = $this::SUPERYACHT;
break;
default:
$label = $this::USED_YACHT;
break;
}
return $label;
}
}
require_once('includes/YachtFields.php');
require_once('includes/YachtCard.php');
require_once('includes/YachtCraft.php');
require_once('includes/YachtMedia.php');
require_once('includes/YachtOptions.php');
require_once('includes/YachtProfile.php');
use Simettric\WPQueryBuilder;
use Simettric\WPQueryBuilder\Builder;
use Simettric\WPQueryBuilder\TaxonomyQuery;
use Simettric\WPQueryBuilder\TaxonomyQueryCollection;
use Simettric\WPQueryBuilder\MetaQuery;
use Simettric\WPQueryBuilder\MetaQueryCollection;
// echo get_query_var('paged');
class SearchController extends FilterController
{
/**
*
* Yacht WP_Query class to load
* yachts with specified settings
*
* @input query_vars, $post
* @output conditions, buildQuery() function outputs WP_Query with $args
* @settings
* @since 1.0.0
*
*/
public $settings = [];
public $search_input = '';
function __construct($settings)
{
// Get query_vars
if ($settings) $this->settings = $settings;
$this->search_input = get_search_query();
// Construct FilterController::Class
parent::__construct();
// Generate WP_Query
$this->query = $this::buildQuery();
}
private function buildQuery()
{
$builder = new Builder();
$post_types = array_diff_key(get_post_types(), array_flip(['yacht']));
foreach ($post_types as $post_type) {
$builder->addPostType($post_type);
}
//search
$builder->search(get_search_query());
$args = $builder->getParameters();
if (function_exists('pll_current_language'))
$args['lang'] = pll_current_language();
// Enable pagination
if ($this->settings['pagination'] === 1) {
$args['posts_per_page'] = $this->settings['posts_per_page'] ?: 9;
$args['paged'] = get_query_var('paged');
} else {
$args['posts_per_page'] = $this->settings['posts_per_page'] ?: 9;
}
$query = new WP_Query($args);
return $query;
}
public function pagination()
{
if (!isset($this->query)) : global $wp_query;
$query = $wp_query;
else : $query = $this->query;
endif;
$big = 999999999;
$total = isset($query->max_num_pages) ? $query->max_num_pages : 1;
$args = array(
'format' => '?page=%#%',
'current' => max(1, get_query_var('paged')),
'prev_next' => true,
'prev_text' => '
',
'next_text' => '
',
'total' => $total
);
if (get_option('permalink_structure')) : $args['base'] = str_replace($big, '%#%', get_pagenum_link($big));
else : $args['base'] = add_query_arg('page', '%#%');
endif;
// echo previous_posts_link('Newer posts');
echo paginate_links($args);
// echo next_posts_link('Older posts');
}
}
// use TypeRocket\Elements\Fields\Repeater;
// index.php
// $sitesetup = tr_form()->useJson();
// echo $sitesetup->open();
// $text = function () use ($sitesetup) {
// echo $sitesetup->textarea('Header scripts')->setHelp('Put your header js scripts here');
// echo $sitesetup->textarea('Footer scripts')->setHelp('Put your footer js scripts here');
// echo $sitesetup->text('Facebook URL')->setHelp('Facebook page URL');
// echo $sitesetup->text('YouTube URL')->setHelp('YouTube page URL');
// echo $sitesetup->text('Instagram URL')->setHelp('Instagram page URL');
// echo $sitesetup->text('Vimeo URL')->setHelp('Vimeo page URL');
// echo $sitesetup->editor('Main address block');
// echo $sitesetup->text('Main phone number')->setHelp('+79999999999');
// echo $sitesetup->text('Main Email address')->setHelp('info@fairline-russia.com');
// };
// $statuses = function () use ($sitesetup) {
// echo $sitesetup->text('Brokerage')->setSetting('default', 'Брокераж');
// echo $sitesetup->text('Stock')->setSetting('default', 'В наличии');
// echo $sitesetup->text('New yacht')->setSetting('default', 'Под заказ');
// echo $sitesetup->text('Sold')->setSetting('default', 'Продано');
// echo $sitesetup->text('Archive')->setSetting('default', 'Архив');
// };
// $filter = function () use ($sitesetup) {
// echo $sitesetup->text('Filter min year')->setType('number')->setSetting('default', 1990);;
// echo $sitesetup->text('Filter max year')->setType('number')->setSetting('default', date('Y') + 1);;
// };
// $texts = function () use ($sitesetup) {
// echo $sitesetup->text('News page header');
// echo $sitesetup->editor('News page text');
// echo $sitesetup->text('Constructed header');
// echo $sitesetup->editor('Constructed page text');
// echo $sitesetup->text('Related header');
// echo $sitesetup->editor('Related text');
// echo $sitesetup->text('Team header');
// echo $sitesetup->editor('Team text');
// };
// // Save
// $save = $sitesetup->submit('Save');
// $seo = function () use ($sitesetup) {
// echo $sitesetup->text('Brand title prefix')
// ->setHelp(' for brand taxonomy -> prefix BRANDNAME | postfix');
// echo $sitesetup->textarea('Brand description');
// echo $sitesetup->text('Series title prefix')
// ->setHelp(' for series taxonomy -> prefix SERIES | postfix');
// echo $sitesetup->textarea('Series description');
// echo $sitesetup->text('Tags title prefix')
// ->setHelp(' for tags taxonomy -> prefix TAG | postfix');
// echo $sitesetup->textarea('Series description');
// echo $sitesetup->text('Team SEO title');
// echo $sitesetup->text('Team SEO description');
// };
// // Layout
// tr_tabs()->setSidebar($save)
// ->addTab('Site options', $text)
// ->addTab('SEO Options', $seo)
// ->addTab('Statuses', $statuses)
// ->addTab('Filter options', $filter)
// ->addTab('Additional', $texts)
// ->render('box');
// echo $sitesetup->close();
$settings = [
'capability' => 'administrator',
'position' => 99,
'view_file' => get_stylesheet_directory() . '/app/Views/options.php'
];
$settings_page = tr_page('wn_setting', 'index', 'WN Settings', $settings);
$settings_page->setIcon('equalizer');
// tr_post_type('post')
// ->setIcon('book')
// ->setSlug('news')
// ->setTitlePlaceholder('Enter Article Title')
// ->setEditorForm(function () {
// $form = tr_form();
// echo $form->text('Name');
// });
tr_meta_box('Post settings')->addScreen('post');
function add_meta_content_post_settings()
{
$form = tr_form();
echo '';
echo $form->repeater('Slides')->setFields([
$form->text('Headline (left)'),
$form->text('Headline (right)'),
$form->image('Slide image'),
$form->text('Button text'),
$form->text('Custom link'),
$form->search('Search')->setPostType('any')
]);
echo $form->text('Subheader');
}
https://westnautical.com/post-sitemap.xml
2026-10-12T15:11:06+00:00
https://westnautical.com/page-sitemap.xml
2026-09-14T12:00:53+00:00
https://westnautical.com/service_wn-sitemap.xml
2026-10-20T14:52:37+00:00
https://westnautical.com/destination-sitemap.xml
2026-04-09T14:49:15+00:00
https://westnautical.com/vacancy-sitemap.xml
https://westnautical.com/shipyard-sitemap.xml
2020-09-03T08:56:04+00:00
https://westnautical.com/partner-sitemap.xml
2025-10-28T16:44:00+00:00
https://westnautical.com/yacht-sitemap.xml
2026-10-26T09:49:07+00:00
https://westnautical.com/category-sitemap.xml
2026-10-12T15:11:06+00:00
https://westnautical.com/post_tag-sitemap.xml
2026-05-05T14:35:12+00:00
https://westnautical.com/regions-sitemap.xml
2026-04-09T14:49:15+00:00
https://westnautical.com/brands-sitemap.xml
2026-10-26T09:49:07+00:00
https://westnautical.com/author-sitemap.xml
2026-01-20T13:32:10+00:00