/** * Plugin Name: Escort Listing (UM first, Trusted next) + Dynamic Meta * Description: [escortlisting] that renders Ultimate Member users first, then trusted API profiles, then normal profiles. Keeps badges & webcam status, ensures trailing slash profile URLs, and adds a random meta description (Yoast) using county/region. * Version: 2.0.0 * Author: You */ if ( ! defined('ABSPATH') ) exit; /* ========================= Helpers: maps ========================= */ function el_county_map() { return [ '294' => 'Guernsey','295' => 'Jersey','206' => 'Derbyshire','209' => 'Leicestershire','208' => 'Lincolnshire', '211' => 'Northamptonshire','207' => 'Nottinghamshire','210' => 'Rutland','250' => 'Bedfordshire', '251' => 'Cambridgeshire','247' => 'Essex','248' => 'Hertfordshire','249' => 'Luton','253' => 'Norfolk', '252' => 'Peterborough','246' => 'Southend','254' => 'Suffolk','245' => 'Thurrock','280' => 'County Durham', '281' => 'Darlington','276' => 'Gateshead','283' => 'Hartlepool','285' => 'Middlesbrough','275' => 'Newcastle Upon Tyne', '277' => 'North Tyneside','274' => 'Northumberland','284' => 'Redcar and Cleveland','278' => 'South Tyneside', '282' => 'Stockton-on-Tees','279' => 'Sunderland','290' => 'East Riding of Yorkshire','291' => 'Hull', '293' => 'North East Lincolnshire','292' => 'North Lincolnshire','288' => 'North Yorkshire','286' => 'South Yorkshire', '287' => 'West Yorkshire','289' => 'York','255' => 'Cheshire','256' => 'Cumbria','257' => 'Greater Manchester', '258' => 'Lancashire','259' => 'Merseyside','168' => 'County Antrim','169' => 'County Armagh','170' => 'County Down', '171' => 'County Fermanagh','172' => 'County Londonderry','173' => 'County Tyrone','174' => 'Berkshire', '175' => 'Buckinghamshire','176' => 'East Sussex','177' => 'Hampshire','178' => 'Isle of Wight','179' => 'Kent', '180' => 'Oxfordshire','181' => 'Surrey','182' => 'West Sussex','160' => 'Anglesey','142' => 'Blaenau Gwent', '148' => 'Bridgend','141' => 'Caerphilly','146' => 'Cardiff','152' => 'Carmarthenshire','153' => 'Ceredigion', '158' => 'Conwy','157' => 'Denbighshire','156' => 'Flintshire','159' => 'Gwynedd','140' => 'Merthyr Tydfil', '144' => 'Monmouthshire','150' => 'Neath Port Talbot','145' => 'Newport','161' => 'Pembrokeshire','154' => 'Powys', '149' => 'Rhondda Cynon Taff','151' => 'Swansea','143' => 'Torfaen','147' => 'Vale of Glamorgan','155' => 'Wrexham', '125' => 'Aberdeen','124' => 'Aberdeenshire','123' => 'Angus','129' => 'Argyll and Bute','120' => 'Clackmannanshire', '135' => 'Dumfries and Galloway','122' => 'Dundee','133' => 'East Ayrshire','111' => 'East Dunbartonshire', '119' => 'East Lothian','113' => 'East Renfrewshire','117' => 'Edinburgh','115' => 'Falkirk','121' => 'Fife', '112' => 'Glasgow','127' => 'Highland','108' => 'Inverclyde','118' => 'Midlothian','126' => 'Moray', '132' => 'North Ayrshire','114' => 'North Lanarkshire','138' => 'Orkney Islands','130' => 'Perth and Kinross', '109' => 'Renfrewshire','137' => 'Scottish Borders','139' => 'Shetland Islands','134' => 'South Ayrshire', '136' => 'South Lanarkshire','131' => 'Stirling','110' => 'West Dunbartonshire','116' => 'West Lothian', '128' => 'Western Isles','266' => 'Birmingham','267' => 'Coventry','268' => 'Dudley','260' => 'Herefordshire', '269' => 'Sandwell','261' => 'Shropshire','270' => 'Solihull','262' => 'Staffordshire','263' => 'Stoke-on-Trent', '264' => 'Telford and Wrekin','271' => 'Walsall','265' => 'Warwickshire','272' => 'Wolverhampton','273' => 'Worcestershire' ]; } function el_region_map() { return [ '14' => 'Channel Islands','8' => 'East Midlands','9' => 'East of England','1' => 'London', '6' => 'North East','5' => 'North West','12' => 'Northern Ireland','10' => 'Scotland', '2' => 'South East','3' => 'South West','11' => 'Wales','4' => 'West Midlands','7' => 'Yorkshire & the Humber' ]; } /* ========================= Helpers: API, files, slugs ========================= */ function el_fetch_api( $url, $headers ) { $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, CURLOPT_HTTPHEADER => $headers, ]); $body = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($code == 429) { error_log("HTTP 429 for API request {$url} from IP ".sanitize_text_field($_SERVER['REMOTE_ADDR'] ?? 'unknown')); return null; } $dec = json_decode($body); return (json_last_error() === JSON_ERROR_NONE) ? $dec : null; } function el_load_favourites() { // Child first then parent (so you can override in child theme) $paths = []; if ( function_exists('get_stylesheet_directory') ) $paths[] = get_stylesheet_directory().'/favorites.txt'; if ( function_exists('get_template_directory') ) $paths[] = get_template_directory().'/favorites.txt'; $fav_user_ids = []; $fav_nicks = []; foreach ($paths as $p) { if (!file_exists($p)) continue; $lines = file($p, FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES); foreach ($lines as $line) { $line = urldecode(trim($line)); if ($line === '') continue; if (ctype_digit($line)) $fav_user_ids[$line] = true; else $fav_nicks[$line] = true; } break; } return [$fav_user_ids, $fav_nicks]; } function el_load_blocked() { $paths = []; if ( function_exists('get_stylesheet_directory') ) $paths[] = get_stylesheet_directory().'/blocked_profiles.txt'; if ( function_exists('get_template_directory') ) $paths[] = get_template_directory().'/blocked_profiles.txt'; $blocked = []; foreach ($paths as $p) { if (!file_exists($p)) continue; $lines = file($p, FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES); foreach ($lines as $line) { list($u,$note)=array_pad(explode('|',$line,2),2,''); $u=trim($u); if($u!=='') $blocked[$u]=trim($note); } break; } return $blocked; } /** * Build a username slug that preserves '+' and '~', replaces all whitespace with '+', * lowercases, strips disallowed chars, and returns a trailing-slash URL to /escort// */ function el_profile_url_from_nick( $nickname ) { $nick = urldecode((string)$nickname); if ( function_exists('remove_accents') ) { $nick = remove_accents($nick); } // Convert spaces to + $nick = preg_replace('/\s+/u', '+', trim($nick)); // Lowercase $nick = mb_strtolower($nick, 'UTF-8'); // Allow unreserved chars, sub-delims, and Unicode ellipsis … $nick = preg_replace('/[^a-z0-9\-\._~!\$&\'\(\)\*\+,;=\:@#\x{2026}]+/u', '', $nick); // Ensure trailing slash return trailingslashit( home_url( '/escort/'.$nick ) ); } /* ========================= Shortcode ========================= */ function escortlisting_func($atts) { $atts = shortcode_atts([ 'pagenumber' => '1', 'sex' => '2', 'country' => '158', 'regionid' => '#', 'iswebcam' => '#', 'isescort' => 'true', 'isphonechat' => '#', 'profilesperpage' => '50', 'orderby' => '#', 'countyid' => '#' ], $atts, 'escortlisting'); // Allow GET override for pagination if ( isset($_GET['pagenumber']) ) $atts['pagenumber'] = sanitize_text_field($_GET['pagenumber']); $county_map = el_county_map(); $region_map = el_region_map(); $county_name = ($atts['countyid'] !== '#' && $atts['countyid'] !== '' && isset($county_map[$atts['countyid']])) ? $county_map[$atts['countyid']] : null; $region_name = (!$county_name && isset($region_map[$atts['regionid']])) ? $region_map[$atts['regionid']] : null; // Include UM profiles only when location is scoped and first page and not "registerdate" order (as you requested earlier) $include_um = (($county_name || $region_name) && $atts['pagenumber'] === '1' && ($atts['orderby'] !== 'registerdate')); $base_api = "https://api.adultwork.com/v1/search/searchProfiles"; $common_params = [ 'CountryID' => esc_attr($atts['country']), 'GenderIDs' => esc_attr($atts['sex']), 'RegionID' => esc_attr($atts['regionid']), 'IsWebcam' => esc_attr($atts['iswebcam']), 'IsEscort' => esc_attr($atts['isescort']), 'IsPhoneChat' => esc_attr($atts['isphonechat']), 'OrderBy' => esc_attr($atts['orderby']), 'CountyID' => esc_attr($atts['countyid']), 'IsVerified' => 'true', ]; $url_list = $base_api . '?' . http_build_query( array_merge( $common_params, [ 'PageNumber' => esc_attr($atts['pagenumber']), 'ProfilesPerPage' => esc_attr($atts['profilesperpage']), ]) ); $headers = [ 'cache-control: max-age=0', 'x-apikey: 13Afzx6nG7JSaKrD4F5fxjeSyzbNhQt9TNuKExSSL6Y', 'x-apisecret: Z2yXoPpiHXC1smqPiNoVV4t3XspdLu9aLl93Iwx_MwI', ]; /* ---- API cache with fallback ---- */ $tkey = 'escortlisting_' . md5($url_list); $is_fb = false; $Profiles = get_transient($tkey); $cache_ts = null; $cache_exp = null; if ( false === $Profiles ) { $Profiles = el_fetch_api($url_list, $headers); if ( is_null($Profiles) || !isset($Profiles->Profiles) || !is_array($Profiles->Profiles) ) { $Profiles = get_transient($tkey.'_fallback'); $is_fb = true; if ( false === $Profiles ) $Profiles = (object)['Profiles'=>[]]; else { $cache_ts = get_option('_transient_timeout_'.$tkey.'_fallback') - 48*HOUR_IN_SECONDS; $cache_exp = get_option('_transient_timeout_'.$tkey.'_fallback') - time(); } } else { set_transient($tkey, $Profiles, 24*HOUR_IN_SECONDS); set_transient($tkey.'_fallback', $Profiles, 48*HOUR_IN_SECONDS); $cache_ts = time(); $cache_exp = 24*HOUR_IN_SECONDS; } } else { $cache_ts = get_option('_transient_timeout_'.$tkey) - 24*HOUR_IN_SECONDS; $cache_exp = get_option('_transient_timeout_'.$tkey) - time(); } /* ---- favourites / blocked ---- */ list($fav_by_id, $fav_by_nick) = el_load_favourites(); $blocked = el_load_blocked(); /* ---- split API profiles into trusted / normal, and skip blocked & no images ---- */ $fallback_img = 'https://www.ukescorts.directory/wp-content/uploads/2019/11/LOCKED-3.jpg'; $trusted = []; $normal = []; if ( isset($Profiles->Profiles) && is_array($Profiles->Profiles) ) { foreach ($Profiles->Profiles as $p) { // Must have at least one square image $has_img = !empty($p->ProfileSquareURL) || !empty($p->Image1SquareURL) || !empty($p->Image2SquareURL) || !empty($p->Image3SquareURL); if (!$has_img) continue; $nick_raw = urldecode($p->Nickname); // Blocked? if ( isset($blocked[$p->UserID]) || isset($blocked[$nick_raw]) ) continue; // Determine "trusted" by favourites $is_trusted = isset($fav_by_id[(string)$p->UserID]) || isset($fav_by_nick[$nick_raw]); $url = el_profile_url_from_nick($nick_raw); $record = ['profile'=>$p, 'nick'=>$nick_raw, 'url'=>$url, 'is_fav'=>$is_trusted]; if ($is_trusted) $trusted[] = $record; else $normal[] = $record; } } /* ---- Ultimate Member users (appear FIRST) ---- */ $um_users = []; if ( $include_um && function_exists('get_users') ) { try { $meta_query = ['relation' => 'AND']; $region_query = ['relation' => 'OR']; $has_region = false; if ($county_name) { $has_region = true; $region_query[] = ['key'=>'UMCounty','value'=>$county_name,'compare'=>'=']; } if ($region_name) { $has_region = true; $region_query[] = ['key'=>'UMRegion','value'=>$region_name,'compare'=>'=']; } if ($has_region) { $meta_query[] = $region_query; // Pull candidates by location only; we'll filter status/visibility in PHP (handles serialized meta safely) $candidates = get_users([ 'meta_query' => $meta_query, 'role' => 'um_independent-escort', 'number' => -1, 'fields' => 'all', ]); $bad_statuses = ['inactive','membership_inactive','disabled']; foreach ($candidates as $u) { // 1) Try direct meta keys first $status = get_user_meta($u->ID, '_um_account_status', true); if ($status === '') $status = get_user_meta($u->ID, 'account_status', true); $hide_in_members = null; $dir_data = get_user_meta($u->ID, 'um_member_directory_data', true); // 2) If needed, extract from serialized directory data if ($status === '' || $status === null || $status === false || $hide_in_members === null) { $arr = maybe_unserialize($dir_data); if (is_array($arr)) { if ($status === '' || $status === null || $status === false) { if (isset($arr['account_status']) && is_string($arr['account_status'])) { $status = $arr['account_status']; } } if ($hide_in_members === null && isset($arr['hide_in_members'])) { $hide_in_members = (int) $arr['hide_in_members']; } } } // Normalize $status_norm = is_string($status) ? strtolower(trim($status)) : ''; $hidden = (int)$hide_in_members === 1; // Exclude hidden or inactive-style statuses if ($hidden) continue; if ($status_norm !== '' && in_array($status_norm, $bad_statuses, true)) continue; // Passed filters — include $last = get_user_meta($u->ID, '_um_last_login', true); $u->last_login_timestamp = $last ? strtotime($last) : 0; $um_users[] = $u; } // Sort by most recent login usort($um_users, function($a,$b){ return $b->last_login_timestamp - $a->last_login_timestamp; }); } } catch (\Throwable $e) { $um_users = []; } } /* ---- Build output: UM FIRST, then Trusted, then Normal ---- */ $html = '
'; // Optional: adverts (only if helper functions exist) $total_slots = count($um_users) + count($trusted) + count($normal); $show_ads = ($atts['pagenumber'] === '1' && $atts['orderby'] !== 'registerdate' && $total_slots > 20); $ad1 = $ad2 = null; if ($show_ads && $total_slots > 2) { $ad1 = rand(0, $total_slots - 1); do { $ad2 = rand(0, $total_slots - 1); } while ($ad2 === $ad1); } $pos = 0; // Renderers (split up to avoid “temporary expression” errors) $render_api = function($item) use ($fallback_img){ $p = $item['profile']; $nick_raw = $item['nick']; $url = $item['url']; $is_fav = $item['is_fav']; $is_new = !empty($p->MemberSince) && strtotime($p->MemberSince) >= strtotime('-7 days'); $is_onl = !empty($p->LastLoggedIn) && ( time() - strtotime($p->LastLoggedIn) ) <= 120; $img = ''; if (!empty($p->ProfileSquareURL)) $img = $p->ProfileSquareURL; elseif (!empty($p->Image1SquareURL)) $img = $p->Image1SquareURL; elseif (!empty($p->Image2SquareURL)) $img = $p->Image2SquareURL; elseif (!empty($p->Image3SquareURL)) $img = $p->Image3SquareURL; $img = $img ? str_replace('/40/', '/240/', $img) : $fallback_img; $out = '
'; if ($is_fav) $out .= '
👑 Trusted
'; if ($is_new) $out .= '
New
'; $out .= ''; $out .= ''.esc_attr($nick_raw).' uk escorts directory'; $out .= ''; $out .= '
'; $out .= '

'.esc_html($nick_raw).'

'; if (isset($p->Age)) $out .= '

Age: '.esc_html($p->Age).'

'; if (isset($p->LocationZipCode)) $out .= '

Postcode: '.esc_html($p->LocationZipCode).'

'; if (isset($p->Town)) $out .= '

Town: '.esc_html($p->Town).'

'; if (!empty($p->Summary)) $out .= '

'.esc_html($p->Summary).'

'; if ($is_onl) $out .= '

Online Now

'; if (!empty($p->AvailableTodayEscort)) $out .= '
Available Today
'; if (isset($p->DirectCam->ModeID, $p->DirectCam->DirectCamLink) && $p->DirectCam->DirectCamLink) { $mode = trim((string)($p->DirectCam->Mode ?? '')); if ($mode !== '' && stripos($mode, 'offline') === false) { $out .= ''; } } $out .= '
'; return $out; }; $render_um = function($u) use ($fallback_img){ $profile_url = trailingslashit( um_user_profile_url( $u->ID ) ); $img = get_avatar_url( $u->ID, ['size'=>240] ); $town_meta = get_user_meta( $u->ID, 'UMTown', true ) ?: get_user_meta( $u->ID, 'UMCounty', true ); $town = is_array($town_meta) ? implode(', ', $town_meta) : ( $town_meta ?: 'N/A' ); $summary = get_user_meta( $u->ID, 'description', true ) ?: 'N/A'; $verified = get_user_meta( $u->ID, '_um_verified', true ); $out = '
'; $out .= '
⭐ Featured Escort
'; $out .= ''; $out .= ''.esc_attr($u->display_name).''; $out .= ''; $out .= '
'; $out .= '

'.esc_html($u->display_name).'

'; $out .= '

Town: '.esc_html($town).'

'; $out .= '

'.esc_html($summary).'

'; if ($verified) $out .= '
Verified ✔
'; $out .= '
'; return $out; }; // Render UM users FIRST foreach ($um_users as $u) { if ($pos === $ad1 && function_exists('get_advert_html')) $html .= get_advert_html(); if ($pos === $ad2 && function_exists('get_advert_html2')) $html .= get_advert_html2(); $html .= $render_um($u); $pos++; } // Then Trusted favourites from API foreach ($trusted as $rec) { if ($pos === $ad1 && function_exists('get_advert_html')) $html .= get_advert_html(); if ($pos === $ad2 && function_exists('get_advert_html2')) $html .= get_advert_html2(); $html .= $render_api($rec); $pos++; } // Then normal API profiles foreach ($normal as $rec) { if ($pos === $ad1 && function_exists('get_advert_html')) $html .= get_advert_html(); if ($pos === $ad2 && function_exists('get_advert_html2')) $html .= get_advert_html2(); $html .= $render_api($rec); $pos++; } $html .= '
'; // Optional: Admin cache box if ( current_user_can('administrator') && is_user_logged_in() ) { $html .= '
' .'
Cache Status (Admin Only)
' .'
'; } return $html; } add_shortcode('escortlisting', 'escortlisting_func'); /* ========================= Yoast Meta Description (random variant) ========================= */ add_filter('wpseo_metadesc', function($desc){ global $post; if ( ! $post ) return $desc; if ( ! has_shortcode($post->post_content, 'escortlisting') ) return $desc; // Pull county/region from FIRST shortcode instance or GET as fallback $countyid = ''; $regionid = ''; $pattern = get_shortcode_regex(['escortlisting']); if ( preg_match_all('/'.$pattern.'/s', $post->post_content, $m) && !empty($m[2]) ) { foreach ($m[2] as $i => $tag) { if ($tag === 'escortlisting') { $atts = shortcode_parse_atts($m[3][$i]); if (!empty($atts['countyid'])) $countyid = sanitize_text_field($atts['countyid']); if (!empty($atts['regionid'])) $regionid = sanitize_text_field($atts['regionid']); break; } } } if ( empty($countyid) && isset($_GET['countyid']) ) $countyid = sanitize_text_field($_GET['countyid']); if ( empty($regionid) && isset($_GET['regionid']) ) $regionid = sanitize_text_field($_GET['regionid']); $county_map = el_county_map(); $region_map = el_region_map(); $county_name = ($countyid && isset($county_map[$countyid])) ? $county_map[$countyid] : ''; $region_name = (!$county_name && $regionid && isset($region_map[$regionid])) ? $region_map[$regionid] : ''; $loc = $county_name ?: $region_name; if ($loc !== '') { $variants = [ sprintf('Escort listings in %s - featuring independent, verified and featured profiles.', $loc), sprintf('Find top-rated escort listings in %s with verified profiles and featured escorts.', $loc), sprintf('Discover trusted escort listings in %s – verified and featured profiles waiting for you.', $loc), ]; return $variants[array_rand($variants)]; } return $desc; }, 20); Site is undergoing maintenance

UK Escorts Directory

Undergoing Maintenance