Implement comprehensive SEO infrastructure and new service pages
Phase 1 - Schema Markup: - Add reusable schema components in /includes/schema/ - organization-schema.php for site-wide Organization structured data - service-schema.php with generator function and predefined configs - local-business-schema.php for location pages with geo coordinates - faq-schema.php with FAQPage generator and common FAQ sets - article-schema.php for blog posts with BlogPosting schema - review-schema.php with AggregateRating and testimonials Phase 6 - Meta Tags & Helpers: - meta-tags.php: Complete meta tag generator (OG, Twitter, article) - canonical.php: URL normalization and canonical tag helper - url-config.php: Centralized URL mapping for internal linking Phase 7 - Internal Linking Components: - related-services.php: Cross-linking component for service pages - location-cta.php: Location links component for service pages Phase 3 - New Service Pages: - property-data-extraction.php: UK property data extraction service - financial-data-services.php: FCA-aware financial data services Phase 5 & 8 - Technical SEO: - Update robots.txt with improved blocking rules - Update sitemap.php with clean URLs and new pages - Update sitemap-services.xml with new service pages - Add new services to footer navigation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
160
includes/canonical.php
Normal file
160
includes/canonical.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
/**
|
||||
* Canonical URL Helper
|
||||
* Generates consistent canonical URLs for the site
|
||||
*
|
||||
* Usage:
|
||||
* include($_SERVER['DOCUMENT_ROOT'] . '/includes/canonical.php');
|
||||
* $canonical = getCanonicalUrl();
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the canonical URL for the current page
|
||||
* Handles www/non-www, http/https, trailing slashes, and query strings
|
||||
*
|
||||
* @param string|null $overrideUrl Optional URL to override auto-detection
|
||||
* @return string The canonical URL
|
||||
*/
|
||||
function getCanonicalUrl($overrideUrl = null) {
|
||||
$baseUrl = 'https://ukdataservices.co.uk';
|
||||
|
||||
// If override provided, clean and return it
|
||||
if ($overrideUrl) {
|
||||
return cleanCanonicalUrl($baseUrl, $overrideUrl);
|
||||
}
|
||||
|
||||
// Auto-detect current URL
|
||||
$requestUri = $_SERVER['REQUEST_URI'] ?? '/';
|
||||
|
||||
// Remove query string
|
||||
$path = parse_url($requestUri, PHP_URL_PATH);
|
||||
|
||||
// Remove .php extension for clean URLs
|
||||
if (substr($path, -4) === '.php') {
|
||||
$path = substr($path, 0, -4);
|
||||
}
|
||||
|
||||
// Normalize trailing slashes (remove except for root)
|
||||
if ($path !== '/' && substr($path, -1) === '/') {
|
||||
$path = rtrim($path, '/');
|
||||
}
|
||||
|
||||
return $baseUrl . $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean and normalize a canonical URL
|
||||
*
|
||||
* @param string $baseUrl The site base URL
|
||||
* @param string $url The URL to clean
|
||||
* @return string Cleaned canonical URL
|
||||
*/
|
||||
function cleanCanonicalUrl($baseUrl, $url) {
|
||||
// If it's a relative URL, make it absolute
|
||||
if (strpos($url, 'http') !== 0) {
|
||||
$url = $baseUrl . '/' . ltrim($url, '/');
|
||||
}
|
||||
|
||||
// Remove query string
|
||||
$url = strtok($url, '?');
|
||||
|
||||
// Remove .php extension
|
||||
if (substr($url, -4) === '.php') {
|
||||
$url = substr($url, 0, -4);
|
||||
}
|
||||
|
||||
// Normalize trailing slashes
|
||||
$path = parse_url($url, PHP_URL_PATH);
|
||||
if ($path && $path !== '/' && substr($path, -1) === '/') {
|
||||
$url = rtrim($url, '/');
|
||||
}
|
||||
|
||||
// Ensure https and non-www
|
||||
$url = str_replace('http://', 'https://', $url);
|
||||
$url = str_replace('://www.', '://', $url);
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the canonical link tag
|
||||
*
|
||||
* @param string|null $url Optional URL override
|
||||
* @return string HTML link tag
|
||||
*/
|
||||
function generateCanonicalTag($url = null) {
|
||||
$canonical = getCanonicalUrl($url);
|
||||
return '<link rel="canonical" href="' . htmlspecialchars($canonical) . '">';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get URL for a specific page by key
|
||||
*
|
||||
* @param string $pageKey The page identifier
|
||||
* @return string The full canonical URL
|
||||
*/
|
||||
function getPageUrl($pageKey) {
|
||||
$baseUrl = 'https://ukdataservices.co.uk';
|
||||
|
||||
$urls = [
|
||||
'home' => '',
|
||||
'about' => '/about',
|
||||
'quote' => '/quote',
|
||||
'faq' => '/faq',
|
||||
'blog' => '/blog',
|
||||
'contact' => '/#contact',
|
||||
'privacy' => '/privacy-policy',
|
||||
'terms' => '/terms-of-service',
|
||||
'cookies' => '/cookie-policy',
|
||||
'gdpr' => '/gdpr-compliance',
|
||||
'project-types' => '/project-types',
|
||||
'case-studies' => '/case-studies',
|
||||
|
||||
// Services
|
||||
'services' => '/#services',
|
||||
'web-scraping' => '/services/web-scraping',
|
||||
'competitive-intelligence' => '/services/competitive-intelligence',
|
||||
'price-monitoring' => '/services/price-monitoring',
|
||||
'data-cleaning' => '/services/data-cleaning',
|
||||
'data-analytics' => '/services/data-analytics',
|
||||
'api-development' => '/services/api-development',
|
||||
'property-data' => '/services/property-data-extraction',
|
||||
'financial-data' => '/services/financial-data-services',
|
||||
|
||||
// Locations
|
||||
'london' => '/locations/london',
|
||||
'manchester' => '/locations/manchester',
|
||||
'birmingham' => '/locations/birmingham',
|
||||
'edinburgh' => '/locations/edinburgh',
|
||||
'cardiff' => '/locations/cardiff'
|
||||
];
|
||||
|
||||
$path = $urls[$pageKey] ?? '';
|
||||
return $baseUrl . $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current page is the canonical version
|
||||
*
|
||||
* @return bool True if current URL is canonical
|
||||
*/
|
||||
function isCanonicalUrl() {
|
||||
$currentUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http")
|
||||
. "://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
|
||||
$canonical = getCanonicalUrl();
|
||||
|
||||
return cleanCanonicalUrl('https://ukdataservices.co.uk', $currentUrl) === $canonical;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output redirect to canonical URL if needed
|
||||
* Call this at the very beginning of pages before any output
|
||||
*/
|
||||
function enforceCanonicalUrl() {
|
||||
if (!isCanonicalUrl()) {
|
||||
$canonical = getCanonicalUrl();
|
||||
header('HTTP/1.1 301 Moved Permanently');
|
||||
header('Location: ' . $canonical);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
225
includes/components/location-cta.php
Normal file
225
includes/components/location-cta.php
Normal file
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
/**
|
||||
* Location CTA Component
|
||||
* Displays location links at the bottom of service pages
|
||||
*
|
||||
* Usage:
|
||||
* include($_SERVER['DOCUMENT_ROOT'] . '/includes/components/location-cta.php');
|
||||
* Or: displayLocationCTA();
|
||||
*/
|
||||
|
||||
// Include URL config if not already loaded
|
||||
if (!isset($locationData)) {
|
||||
include($_SERVER['DOCUMENT_ROOT'] . '/includes/url-config.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display location CTA section
|
||||
*
|
||||
* @param string|null $serviceName Optional service name to customize the message
|
||||
*/
|
||||
function displayLocationCTA($serviceName = null) {
|
||||
global $locationData;
|
||||
|
||||
$serviceText = $serviceName ? htmlspecialchars($serviceName) . ' services' : 'data services';
|
||||
?>
|
||||
<section class="location-cta">
|
||||
<div class="container">
|
||||
<div class="location-cta-content">
|
||||
<h2>Serving Businesses Across the UK</h2>
|
||||
<p>We provide professional <?php echo $serviceText; ?> to businesses throughout the United Kingdom, with dedicated support for major business centres.</p>
|
||||
|
||||
<div class="location-links">
|
||||
<?php foreach ($locationData as $key => $location): ?>
|
||||
<a href="<?php echo htmlspecialchars($location['url']); ?>" class="location-link">
|
||||
<span class="location-icon">
|
||||
<?php echo getLocationIcon($key); ?>
|
||||
</span>
|
||||
<span class="location-name"><?php echo htmlspecialchars($location['title']); ?></span>
|
||||
<span class="location-region"><?php echo htmlspecialchars($location['region']); ?></span>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<div class="location-cta-footer">
|
||||
<p>Not in one of these locations? We serve businesses throughout the UK with remote data services.</p>
|
||||
<a href="/quote" class="btn btn-primary">Request a Consultation</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.location-cta {
|
||||
padding: 80px 0;
|
||||
background: linear-gradient(135deg, #144784 0%, #1a5a9e 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.location-cta-content {
|
||||
text-align: center;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.location-cta h2 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.location-cta > .container > .location-cta-content > p {
|
||||
font-size: 1.1rem;
|
||||
opacity: 0.9;
|
||||
margin-bottom: 40px;
|
||||
max-width: 700px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.location-links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.location-link {
|
||||
background: rgba(255,255,255,0.1);
|
||||
border: 1px solid rgba(255,255,255,0.2);
|
||||
border-radius: 12px;
|
||||
padding: 20px 30px;
|
||||
text-decoration: none;
|
||||
color: white;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.location-link:hover {
|
||||
background: rgba(255,255,255,0.2);
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
.location-icon {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.location-name {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.location-region {
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.location-cta-footer {
|
||||
padding-top: 30px;
|
||||
border-top: 1px solid rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
.location-cta-footer p {
|
||||
margin-bottom: 20px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.location-cta .btn-primary {
|
||||
background: #179e83;
|
||||
color: white;
|
||||
padding: 14px 32px;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
display: inline-block;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.location-cta .btn-primary:hover {
|
||||
background: #14876f;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.location-cta {
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.location-links {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.location-link {
|
||||
width: 100%;
|
||||
max-width: 250px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Get location icon based on city
|
||||
*/
|
||||
function getLocationIcon($locationKey) {
|
||||
$icons = [
|
||||
'london' => '🏙️',
|
||||
'manchester' => '🏭',
|
||||
'birmingham' => '🏛️',
|
||||
'edinburgh' => '🏴',
|
||||
'cardiff' => '🏴'
|
||||
];
|
||||
return $icons[$locationKey] ?? '📍';
|
||||
}
|
||||
|
||||
/**
|
||||
* Display compact location links (for footers or sidebars)
|
||||
*/
|
||||
function displayCompactLocationLinks() {
|
||||
global $locationData;
|
||||
?>
|
||||
<div class="compact-locations">
|
||||
<span class="compact-locations-label">Serving:</span>
|
||||
<?php
|
||||
$locations = array_keys($locationData);
|
||||
foreach ($locations as $index => $key):
|
||||
$location = $locationData[$key];
|
||||
?>
|
||||
<a href="<?php echo htmlspecialchars($location['url']); ?>"><?php echo htmlspecialchars($location['title']); ?></a><?php echo $index < count($locations) - 1 ? ', ' : ''; ?>
|
||||
<?php endforeach; ?>
|
||||
<span class="compact-locations-suffix">and the UK</span>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.compact-locations {
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.compact-locations a {
|
||||
color: #179e83;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.compact-locations a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.compact-locations-label,
|
||||
.compact-locations-suffix {
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Auto-display if this file is included directly without function call
|
||||
if (basename($_SERVER['SCRIPT_FILENAME']) === 'location-cta.php') {
|
||||
displayLocationCTA();
|
||||
}
|
||||
152
includes/components/related-services.php
Normal file
152
includes/components/related-services.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
/**
|
||||
* Related Services Component
|
||||
* Displays related services at the bottom of service pages
|
||||
*
|
||||
* Usage:
|
||||
* $currentService = 'web-scraping';
|
||||
* include($_SERVER['DOCUMENT_ROOT'] . '/includes/components/related-services.php');
|
||||
*/
|
||||
|
||||
// Include URL config if not already loaded
|
||||
if (!isset($serviceData)) {
|
||||
include($_SERVER['DOCUMENT_ROOT'] . '/includes/url-config.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display related services section
|
||||
*
|
||||
* @param string $currentService Current service key
|
||||
* @param int $maxServices Maximum number of related services to show
|
||||
*/
|
||||
function displayRelatedServices($currentService, $maxServices = 3) {
|
||||
global $serviceData, $relatedServices;
|
||||
|
||||
$related = $relatedServices[$currentService] ?? [];
|
||||
|
||||
if (empty($related)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Limit to max services
|
||||
$related = array_slice($related, 0, $maxServices);
|
||||
?>
|
||||
<section class="related-services">
|
||||
<div class="container">
|
||||
<h2>Related Services</h2>
|
||||
<p class="section-intro">Explore our other data services that complement <?php echo htmlspecialchars($serviceData[$currentService]['shortTitle'] ?? 'this service'); ?>.</p>
|
||||
|
||||
<div class="related-services-grid">
|
||||
<?php foreach ($related as $serviceKey): ?>
|
||||
<?php if (isset($serviceData[$serviceKey])): ?>
|
||||
<?php $service = $serviceData[$serviceKey]; ?>
|
||||
<a href="<?php echo htmlspecialchars($service['url']); ?>" class="related-service-card">
|
||||
<div class="service-icon">
|
||||
<?php if (isset($service['icon'])): ?>
|
||||
<img src="/assets/images/<?php echo htmlspecialchars($service['icon']); ?>"
|
||||
alt="<?php echo htmlspecialchars($service['title']); ?>"
|
||||
loading="lazy">
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<h3><?php echo htmlspecialchars($service['title']); ?></h3>
|
||||
<p><?php echo htmlspecialchars($service['description']); ?></p>
|
||||
<span class="learn-more">Learn More →</span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.related-services {
|
||||
padding: 80px 0;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.related-services h2 {
|
||||
text-align: center;
|
||||
font-size: 2rem;
|
||||
color: #144784;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.related-services .section-intro {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
max-width: 600px;
|
||||
margin: 0 auto 40px;
|
||||
}
|
||||
|
||||
.related-services-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 30px;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.related-service-card {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 12px;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
||||
transition: all 0.3s ease;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.related-service-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.related-service-card .service-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.related-service-card .service-icon img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.related-service-card h3 {
|
||||
font-size: 1.25rem;
|
||||
color: #144784;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.related-service-card p {
|
||||
color: #666;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.related-service-card .learn-more {
|
||||
color: #179e83;
|
||||
font-weight: 500;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.related-services {
|
||||
padding: 60px 20px;
|
||||
}
|
||||
|
||||
.related-services-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Auto-display if $currentService is set
|
||||
if (isset($currentService)) {
|
||||
displayRelatedServices($currentService);
|
||||
}
|
||||
@@ -15,6 +15,8 @@
|
||||
<li><a href="/services/competitive-intelligence">Competitive Intelligence</a></li>
|
||||
<li><a href="/services/price-monitoring">Price Monitoring</a></li>
|
||||
<li><a href="/services/data-cleaning">Data Cleaning</a></li>
|
||||
<li><a href="/services/property-data-extraction">Property Data</a></li>
|
||||
<li><a href="/services/financial-data-services">Financial Data</a></li>
|
||||
<li><a href="/#services">All Services</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
164
includes/meta-tags.php
Normal file
164
includes/meta-tags.php
Normal file
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
/**
|
||||
* Meta Tags Helper
|
||||
* Generates optimized meta tags for SEO
|
||||
*
|
||||
* Usage:
|
||||
* $metaData = [
|
||||
* 'title' => 'Page Title',
|
||||
* 'description' => 'Page description...',
|
||||
* 'canonicalUrl' => 'https://ukdataservices.co.uk/page',
|
||||
* 'ogImage' => '/assets/images/og-image.jpg',
|
||||
* 'type' => 'website', // or 'article'
|
||||
* 'articleData' => [...] // optional for articles
|
||||
* ];
|
||||
* include($_SERVER['DOCUMENT_ROOT'] . '/includes/meta-tags.php');
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generate complete meta tags for a page
|
||||
*
|
||||
* @param string $title Page title (will append brand if not already included)
|
||||
* @param string $description Meta description (max 160 chars recommended)
|
||||
* @param string $canonicalUrl Canonical URL for the page
|
||||
* @param string|null $ogImage Open Graph image URL
|
||||
* @param array|null $articleData Article-specific data for blog posts
|
||||
* @param string $type Page type (website, article, service)
|
||||
* @return string HTML meta tags
|
||||
*/
|
||||
function generateMetaTags($title, $description, $canonicalUrl, $ogImage = null, $articleData = null, $type = 'website') {
|
||||
$baseUrl = 'https://ukdataservices.co.uk';
|
||||
$siteName = 'UK Data Services';
|
||||
$defaultImage = $baseUrl . '/assets/images/ukds-main-logo.png';
|
||||
$twitterHandle = '@ukdataservices';
|
||||
|
||||
// Ensure title includes brand name
|
||||
$fullTitle = $title;
|
||||
if (strpos(strtolower($title), 'uk data services') === false) {
|
||||
$fullTitle = $title . ' | UK Data Services';
|
||||
}
|
||||
|
||||
// Truncate description if too long
|
||||
if (strlen($description) > 160) {
|
||||
$description = substr($description, 0, 157) . '...';
|
||||
}
|
||||
|
||||
// Use provided image or default
|
||||
$imageUrl = $ogImage ? (strpos($ogImage, 'http') === 0 ? $ogImage : $baseUrl . $ogImage) : $defaultImage;
|
||||
|
||||
$output = '';
|
||||
|
||||
// Basic meta tags
|
||||
$output .= '<title>' . htmlspecialchars($fullTitle) . '</title>' . "\n";
|
||||
$output .= ' <meta name="description" content="' . htmlspecialchars($description) . '">' . "\n";
|
||||
$output .= ' <meta name="author" content="UK Data Services">' . "\n";
|
||||
$output .= ' <meta name="robots" content="index, follow">' . "\n";
|
||||
$output .= ' <meta name="googlebot" content="index, follow">' . "\n";
|
||||
$output .= ' <link rel="canonical" href="' . htmlspecialchars($canonicalUrl) . '">' . "\n";
|
||||
|
||||
// Open Graph tags
|
||||
$output .= "\n <!-- Open Graph / Facebook -->\n";
|
||||
$output .= ' <meta property="og:type" content="' . ($type === 'article' ? 'article' : 'website') . '">' . "\n";
|
||||
$output .= ' <meta property="og:url" content="' . htmlspecialchars($canonicalUrl) . '">' . "\n";
|
||||
$output .= ' <meta property="og:title" content="' . htmlspecialchars($title) . '">' . "\n";
|
||||
$output .= ' <meta property="og:description" content="' . htmlspecialchars($description) . '">' . "\n";
|
||||
$output .= ' <meta property="og:image" content="' . htmlspecialchars($imageUrl) . '">' . "\n";
|
||||
$output .= ' <meta property="og:image:width" content="1200">' . "\n";
|
||||
$output .= ' <meta property="og:image:height" content="630">' . "\n";
|
||||
$output .= ' <meta property="og:site_name" content="' . $siteName . '">' . "\n";
|
||||
$output .= ' <meta property="og:locale" content="en_GB">' . "\n";
|
||||
|
||||
// Twitter Card tags
|
||||
$output .= "\n <!-- Twitter Card -->\n";
|
||||
$output .= ' <meta name="twitter:card" content="summary_large_image">' . "\n";
|
||||
$output .= ' <meta name="twitter:site" content="' . $twitterHandle . '">' . "\n";
|
||||
$output .= ' <meta name="twitter:url" content="' . htmlspecialchars($canonicalUrl) . '">' . "\n";
|
||||
$output .= ' <meta name="twitter:title" content="' . htmlspecialchars($title) . '">' . "\n";
|
||||
$output .= ' <meta name="twitter:description" content="' . htmlspecialchars($description) . '">' . "\n";
|
||||
$output .= ' <meta name="twitter:image" content="' . htmlspecialchars($imageUrl) . '">' . "\n";
|
||||
|
||||
// Article-specific tags
|
||||
if ($articleData && $type === 'article') {
|
||||
$output .= "\n <!-- Article Meta -->\n";
|
||||
if (isset($articleData['datePublished'])) {
|
||||
$output .= ' <meta property="article:published_time" content="' . $articleData['datePublished'] . 'T09:00:00+00:00">' . "\n";
|
||||
}
|
||||
if (isset($articleData['dateModified'])) {
|
||||
$output .= ' <meta property="article:modified_time" content="' . $articleData['dateModified'] . 'T09:00:00+00:00">' . "\n";
|
||||
}
|
||||
if (isset($articleData['author'])) {
|
||||
$output .= ' <meta property="article:author" content="' . htmlspecialchars($articleData['author']) . '">' . "\n";
|
||||
}
|
||||
if (isset($articleData['section'])) {
|
||||
$output .= ' <meta property="article:section" content="' . htmlspecialchars($articleData['section']) . '">' . "\n";
|
||||
}
|
||||
if (isset($articleData['tags']) && is_array($articleData['tags'])) {
|
||||
foreach ($articleData['tags'] as $tag) {
|
||||
$output .= ' <meta property="article:tag" content="' . htmlspecialchars($tag) . '">' . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate geo meta tags for location pages
|
||||
*/
|
||||
function generateGeoMetaTags($city, $region, $latitude, $longitude) {
|
||||
$output = "\n <!-- Geo Meta Tags -->\n";
|
||||
$output .= ' <meta name="geo.region" content="GB">' . "\n";
|
||||
$output .= ' <meta name="geo.placename" content="' . htmlspecialchars($city) . '">' . "\n";
|
||||
$output .= ' <meta name="geo.position" content="' . $latitude . ';' . $longitude . '">' . "\n";
|
||||
$output .= ' <meta name="ICBM" content="' . $latitude . ', ' . $longitude . '">' . "\n";
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate hreflang tags (for potential future internationalization)
|
||||
*/
|
||||
function generateHreflangTags($canonicalUrl) {
|
||||
$output = "\n <!-- Hreflang -->\n";
|
||||
$output .= ' <link rel="alternate" hreflang="en-GB" href="' . htmlspecialchars($canonicalUrl) . '">' . "\n";
|
||||
$output .= ' <link rel="alternate" hreflang="x-default" href="' . htmlspecialchars($canonicalUrl) . '">' . "\n";
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output standard favicon and manifest links
|
||||
*/
|
||||
function generateFaviconTags() {
|
||||
$output = "\n <!-- Favicon and App Icons -->\n";
|
||||
$output .= ' <link rel="icon" type="image/svg+xml" href="/assets/images/favicon.svg">' . "\n";
|
||||
$output .= ' <link rel="icon" type="image/svg+xml" sizes="16x16" href="/assets/images/favicon-16x16.svg">' . "\n";
|
||||
$output .= ' <link rel="icon" type="image/svg+xml" sizes="32x32" href="/assets/images/favicon-32x32.svg">' . "\n";
|
||||
$output .= ' <link rel="apple-touch-icon" sizes="180x180" href="/assets/images/apple-touch-icon.svg">' . "\n";
|
||||
$output .= ' <link rel="manifest" href="/manifest.json">' . "\n";
|
||||
$output .= ' <meta name="theme-color" content="#144784">' . "\n";
|
||||
$output .= ' <meta name="msapplication-TileColor" content="#179e83">' . "\n";
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate preconnect/prefetch hints for performance
|
||||
*/
|
||||
function generateResourceHints() {
|
||||
$output = "\n <!-- Resource Hints -->\n";
|
||||
$output .= ' <link rel="preconnect" href="https://fonts.googleapis.com">' . "\n";
|
||||
$output .= ' <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>' . "\n";
|
||||
$output .= ' <link rel="dns-prefetch" href="https://www.google-analytics.com">' . "\n";
|
||||
$output .= ' <link rel="dns-prefetch" href="https://www.googletagmanager.com">' . "\n";
|
||||
return $output;
|
||||
}
|
||||
|
||||
// If $metaData is set, output meta tags automatically
|
||||
if (isset($metaData) && is_array($metaData)) {
|
||||
echo generateMetaTags(
|
||||
$metaData['title'],
|
||||
$metaData['description'],
|
||||
$metaData['canonicalUrl'],
|
||||
$metaData['ogImage'] ?? null,
|
||||
$metaData['articleData'] ?? null,
|
||||
$metaData['type'] ?? 'website'
|
||||
);
|
||||
}
|
||||
198
includes/schema/article-schema.php
Normal file
198
includes/schema/article-schema.php
Normal file
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
/**
|
||||
* Article Schema Component
|
||||
* Generates Article structured data for blog posts
|
||||
*
|
||||
* Usage:
|
||||
* $articleData = [
|
||||
* 'title' => 'Article Title',
|
||||
* 'description' => 'Article description...',
|
||||
* 'datePublished' => '2024-01-15',
|
||||
* 'dateModified' => '2024-01-20',
|
||||
* 'authorName' => 'John Smith',
|
||||
* 'imageUrl' => 'https://example.com/image.jpg',
|
||||
* 'articleUrl' => 'https://example.com/article'
|
||||
* ];
|
||||
* include($_SERVER['DOCUMENT_ROOT'] . '/includes/schema/article-schema.php');
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generate Article Schema JSON-LD
|
||||
*
|
||||
* @param string $title Article title
|
||||
* @param string $description Article description/excerpt
|
||||
* @param string $datePublished Publication date (Y-m-d format)
|
||||
* @param string $dateModified Last modified date (Y-m-d format)
|
||||
* @param string $authorName Author's name
|
||||
* @param string $imageUrl Featured image URL
|
||||
* @param string $articleUrl Canonical article URL
|
||||
* @param string $category Optional article category
|
||||
* @param array $keywords Optional array of keywords
|
||||
* @return string JSON-LD script tag
|
||||
*/
|
||||
function generateArticleSchema($title, $description, $datePublished, $dateModified, $authorName, $imageUrl, $articleUrl, $category = null, $keywords = []) {
|
||||
$baseUrl = 'https://ukdataservices.co.uk';
|
||||
|
||||
$schema = [
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => 'Article',
|
||||
'@id' => $articleUrl . '#article',
|
||||
'headline' => $title,
|
||||
'description' => $description,
|
||||
'url' => $articleUrl,
|
||||
'datePublished' => $datePublished . 'T09:00:00+00:00',
|
||||
'dateModified' => $dateModified . 'T09:00:00+00:00',
|
||||
'author' => [
|
||||
'@type' => 'Person',
|
||||
'name' => $authorName,
|
||||
'url' => $baseUrl . '/about'
|
||||
],
|
||||
'publisher' => [
|
||||
'@type' => 'Organization',
|
||||
'@id' => $baseUrl . '/#organization',
|
||||
'name' => 'UK Data Services',
|
||||
'logo' => [
|
||||
'@type' => 'ImageObject',
|
||||
'url' => $baseUrl . '/assets/images/ukds-main-logo.png'
|
||||
]
|
||||
],
|
||||
'image' => [
|
||||
'@type' => 'ImageObject',
|
||||
'url' => $imageUrl,
|
||||
'width' => 1200,
|
||||
'height' => 630
|
||||
],
|
||||
'mainEntityOfPage' => [
|
||||
'@type' => 'WebPage',
|
||||
'@id' => $articleUrl
|
||||
],
|
||||
'isPartOf' => [
|
||||
'@type' => 'Blog',
|
||||
'@id' => $baseUrl . '/blog/#blog',
|
||||
'name' => 'UK Data Services Blog',
|
||||
'publisher' => [
|
||||
'@id' => $baseUrl . '/#organization'
|
||||
]
|
||||
],
|
||||
'inLanguage' => 'en-GB'
|
||||
];
|
||||
|
||||
// Add category if provided
|
||||
if ($category) {
|
||||
$schema['articleSection'] = $category;
|
||||
}
|
||||
|
||||
// Add keywords if provided
|
||||
if (!empty($keywords)) {
|
||||
$schema['keywords'] = implode(', ', $keywords);
|
||||
}
|
||||
|
||||
// Add word count estimate based on description length (rough estimate)
|
||||
$schema['wordCount'] = max(500, strlen($description) * 5);
|
||||
|
||||
return '<script type="application/ld+json">' . "\n" .
|
||||
json_encode($schema, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n" .
|
||||
'</script>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate BlogPosting Schema (more specific than Article)
|
||||
*/
|
||||
function generateBlogPostingSchema($title, $description, $datePublished, $dateModified, $authorName, $imageUrl, $articleUrl, $category = null, $keywords = []) {
|
||||
$baseUrl = 'https://ukdataservices.co.uk';
|
||||
|
||||
$schema = [
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => 'BlogPosting',
|
||||
'@id' => $articleUrl . '#blogposting',
|
||||
'headline' => $title,
|
||||
'description' => $description,
|
||||
'url' => $articleUrl,
|
||||
'datePublished' => $datePublished . 'T09:00:00+00:00',
|
||||
'dateModified' => $dateModified . 'T09:00:00+00:00',
|
||||
'author' => [
|
||||
'@type' => 'Person',
|
||||
'name' => $authorName,
|
||||
'url' => $baseUrl . '/about',
|
||||
'worksFor' => [
|
||||
'@type' => 'Organization',
|
||||
'@id' => $baseUrl . '/#organization'
|
||||
]
|
||||
],
|
||||
'publisher' => [
|
||||
'@type' => 'Organization',
|
||||
'@id' => $baseUrl . '/#organization',
|
||||
'name' => 'UK Data Services',
|
||||
'logo' => [
|
||||
'@type' => 'ImageObject',
|
||||
'url' => $baseUrl . '/assets/images/ukds-main-logo.png',
|
||||
'width' => 300,
|
||||
'height' => 100
|
||||
]
|
||||
],
|
||||
'image' => [
|
||||
'@type' => 'ImageObject',
|
||||
'url' => $imageUrl,
|
||||
'width' => 1200,
|
||||
'height' => 630
|
||||
],
|
||||
'mainEntityOfPage' => [
|
||||
'@type' => 'WebPage',
|
||||
'@id' => $articleUrl
|
||||
],
|
||||
'isPartOf' => [
|
||||
'@type' => 'Blog',
|
||||
'@id' => $baseUrl . '/blog/#blog',
|
||||
'name' => 'UK Data Services Blog'
|
||||
],
|
||||
'inLanguage' => 'en-GB'
|
||||
];
|
||||
|
||||
if ($category) {
|
||||
$schema['articleSection'] = $category;
|
||||
}
|
||||
|
||||
if (!empty($keywords)) {
|
||||
$schema['keywords'] = implode(', ', $keywords);
|
||||
}
|
||||
|
||||
return '<script type="application/ld+json">' . "\n" .
|
||||
json_encode($schema, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n" .
|
||||
'</script>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Author configurations for the site
|
||||
*/
|
||||
$authorProfiles = [
|
||||
'uk-data-services-team' => [
|
||||
'name' => 'UK Data Services Team',
|
||||
'role' => 'Data Intelligence Experts',
|
||||
'description' => 'Our team of certified data professionals and engineers providing expert guidance on web scraping, data analytics, and business intelligence.'
|
||||
],
|
||||
'technical-team' => [
|
||||
'name' => 'UK Data Services Technical Team',
|
||||
'role' => 'Senior Data Engineers',
|
||||
'description' => 'Expert engineers specializing in web scraping technologies, data pipelines, and enterprise data solutions.'
|
||||
],
|
||||
'compliance-team' => [
|
||||
'name' => 'UK Data Services Compliance Team',
|
||||
'role' => 'Data Protection Specialists',
|
||||
'description' => 'Specialists in GDPR compliance, data protection, and regulatory requirements for data collection.'
|
||||
]
|
||||
];
|
||||
|
||||
// If $articleData is set, output the schema automatically
|
||||
if (isset($articleData) && is_array($articleData)) {
|
||||
echo generateArticleSchema(
|
||||
$articleData['title'],
|
||||
$articleData['description'],
|
||||
$articleData['datePublished'],
|
||||
$articleData['dateModified'] ?? $articleData['datePublished'],
|
||||
$articleData['authorName'] ?? 'UK Data Services Team',
|
||||
$articleData['imageUrl'],
|
||||
$articleData['articleUrl'],
|
||||
$articleData['category'] ?? null,
|
||||
$articleData['keywords'] ?? []
|
||||
);
|
||||
}
|
||||
151
includes/schema/faq-schema.php
Normal file
151
includes/schema/faq-schema.php
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
/**
|
||||
* FAQ Schema Component
|
||||
* Generates FAQPage structured data for FAQ sections
|
||||
*
|
||||
* Usage:
|
||||
* $faqs = [
|
||||
* ['question' => 'What is web scraping?', 'answer' => 'Web scraping is...'],
|
||||
* ['question' => 'How much does it cost?', 'answer' => 'Pricing varies...']
|
||||
* ];
|
||||
* include($_SERVER['DOCUMENT_ROOT'] . '/includes/schema/faq-schema.php');
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generate FAQPage Schema JSON-LD
|
||||
*
|
||||
* @param array $faqs Array of Q&A pairs with 'question' and 'answer' keys
|
||||
* @param string|null $pageUrl Optional canonical URL for the FAQ page
|
||||
* @param string|null $pageName Optional name for the FAQ page
|
||||
* @return string JSON-LD script tag
|
||||
*/
|
||||
function generateFAQSchema($faqs, $pageUrl = null, $pageName = null) {
|
||||
if (empty($faqs)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$schema = [
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => 'FAQPage'
|
||||
];
|
||||
|
||||
// Add page identifier if URL provided
|
||||
if ($pageUrl) {
|
||||
$schema['@id'] = $pageUrl . '#faqpage';
|
||||
$schema['url'] = $pageUrl;
|
||||
}
|
||||
|
||||
// Add page name if provided
|
||||
if ($pageName) {
|
||||
$schema['name'] = $pageName;
|
||||
}
|
||||
|
||||
// Build main entity array
|
||||
$schema['mainEntity'] = array_map(function($faq) {
|
||||
return [
|
||||
'@type' => 'Question',
|
||||
'name' => $faq['question'],
|
||||
'acceptedAnswer' => [
|
||||
'@type' => 'Answer',
|
||||
'text' => $faq['answer']
|
||||
]
|
||||
];
|
||||
}, $faqs);
|
||||
|
||||
return '<script type="application/ld+json">' . "\n" .
|
||||
json_encode($schema, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n" .
|
||||
'</script>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Common FAQs for different page types
|
||||
*/
|
||||
$commonFAQs = [
|
||||
'general' => [
|
||||
[
|
||||
'question' => 'What is web scraping and how can it benefit my business?',
|
||||
'answer' => 'Web scraping is the automated process of extracting data from websites. It benefits businesses by providing competitive intelligence, automating data collection, enabling real-time price monitoring, and supporting strategic decision-making with accurate market data.'
|
||||
],
|
||||
[
|
||||
'question' => 'Is web scraping legal in the UK?',
|
||||
'answer' => 'Yes, web scraping is legal in the UK when conducted properly. We ensure full compliance with UK Data Protection Act 2018, GDPR, website terms of service, and industry best practices. We only collect publicly available data and respect robots.txt directives.'
|
||||
],
|
||||
[
|
||||
'question' => 'How do you ensure data accuracy?',
|
||||
'answer' => 'We maintain a 99.8% accuracy rate through advanced validation algorithms, multi-layer verification processes, regular monitoring, and comprehensive testing before delivery. Each dataset undergoes rigorous quality assurance checks.'
|
||||
],
|
||||
[
|
||||
'question' => 'What data formats do you provide?',
|
||||
'answer' => 'We deliver data in multiple formats including Excel (XLSX/XLS), CSV, JSON, XML, SQL database dumps, and custom formats. We also offer real-time data feeds and API access for ongoing projects.'
|
||||
],
|
||||
[
|
||||
'question' => 'How much do your services cost?',
|
||||
'answer' => 'Pricing varies based on project complexity: Simple extraction projects start from £500-£2,000, medium complexity projects range from £2,000-£10,000, and enterprise solutions are £10,000+. We provide custom quotes based on your specific requirements.'
|
||||
]
|
||||
],
|
||||
'pricing' => [
|
||||
[
|
||||
'question' => 'What factors affect the cost of web scraping services?',
|
||||
'answer' => 'Costs depend on several factors: the number and complexity of target websites, data volume required, frequency of updates, anti-bot measures to navigate, data cleaning requirements, and delivery format preferences.'
|
||||
],
|
||||
[
|
||||
'question' => 'Do you offer monthly retainer packages?',
|
||||
'answer' => 'Yes, we offer flexible monthly retainer packages for ongoing data collection needs. These include regular updates, priority support, and often provide better value than one-off projects for continuous monitoring requirements.'
|
||||
],
|
||||
[
|
||||
'question' => 'Is there a minimum project value?',
|
||||
'answer' => 'Our minimum project value is £500. This ensures we can deliver quality work with proper attention to accuracy, compliance, and client requirements.'
|
||||
]
|
||||
],
|
||||
'compliance' => [
|
||||
[
|
||||
'question' => 'How do you handle GDPR compliance?',
|
||||
'answer' => 'GDPR compliance is central to our operations. We only collect personal data when legally justified, follow data minimisation principles, implement robust security measures, maintain clear data retention policies, and respect data subject rights.'
|
||||
],
|
||||
[
|
||||
'question' => 'Do you scrape personal data?',
|
||||
'answer' => 'We only collect personal data when there is a legitimate legal basis, such as legitimate business interests or consent. We follow strict GDPR guidelines and advise clients on compliant data collection strategies.'
|
||||
],
|
||||
[
|
||||
'question' => 'What security measures do you have in place?',
|
||||
'answer' => 'We implement enterprise-grade security including encrypted data transfer (SSL/TLS), secure data storage, access controls, regular security audits, and compliance with ISO 27001 principles.'
|
||||
]
|
||||
],
|
||||
'property-data' => [
|
||||
[
|
||||
'question' => 'Can you extract data from Rightmove and Zoopla?',
|
||||
'answer' => 'Yes, we can extract publicly available property data from UK property portals including Rightmove, Zoopla, OnTheMarket, and others. We ensure compliance with each platform\'s terms of service and UK data protection laws.'
|
||||
],
|
||||
[
|
||||
'question' => 'What property data can you collect?',
|
||||
'answer' => 'We can collect property listings, prices, property features, location data, historical price changes, rental yields, local area information, and market trends. Custom data requirements can be discussed during consultation.'
|
||||
],
|
||||
[
|
||||
'question' => 'How often can property data be updated?',
|
||||
'answer' => 'We offer daily, weekly, or monthly property data updates depending on your needs. Real-time monitoring is also available for time-sensitive market intelligence requirements.'
|
||||
]
|
||||
],
|
||||
'financial-data' => [
|
||||
[
|
||||
'question' => 'Do you provide FCA-compliant financial data services?',
|
||||
'answer' => 'Yes, our financial data services are designed with FCA regulations in mind. We help hedge funds, asset managers, and investment firms collect market data while maintaining compliance with applicable financial regulations.'
|
||||
],
|
||||
[
|
||||
'question' => 'What types of alternative data do you provide?',
|
||||
'answer' => 'We provide various alternative data sources including web traffic data, sentiment analysis, pricing data, job posting trends, satellite imagery analysis, and custom data feeds tailored to investment strategies.'
|
||||
],
|
||||
[
|
||||
'question' => 'Can you provide historical financial data?',
|
||||
'answer' => 'Yes, we can extract and compile historical financial data where publicly available. This includes historical pricing, company filings, news archives, and market trend data for backtesting and analysis purposes.'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
// If $faqs is set, output the schema automatically
|
||||
if (isset($faqs) && is_array($faqs)) {
|
||||
echo generateFAQSchema(
|
||||
$faqs,
|
||||
$faqPageUrl ?? null,
|
||||
$faqPageName ?? null
|
||||
);
|
||||
}
|
||||
233
includes/schema/local-business-schema.php
Normal file
233
includes/schema/local-business-schema.php
Normal file
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
/**
|
||||
* LocalBusiness Schema Component
|
||||
* Generates LocalBusiness structured data for location pages
|
||||
*
|
||||
* Usage:
|
||||
* $locationData = [
|
||||
* 'city' => 'London',
|
||||
* 'region' => 'Greater London',
|
||||
* 'latitude' => 51.5074,
|
||||
* 'longitude' => -0.1278,
|
||||
* 'services' => ['Web Scraping', 'Data Analytics']
|
||||
* ];
|
||||
* include($_SERVER['DOCUMENT_ROOT'] . '/includes/schema/local-business-schema.php');
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generate LocalBusiness Schema JSON-LD for city/location pages
|
||||
*
|
||||
* @param string $city The city name
|
||||
* @param string $region The region/county name
|
||||
* @param array $services Array of service names offered in this location
|
||||
* @param float|null $latitude Optional latitude coordinate
|
||||
* @param float|null $longitude Optional longitude coordinate
|
||||
* @return string JSON-LD script tag
|
||||
*/
|
||||
function generateLocalBusinessSchema($city, $region, $services = [], $latitude = null, $longitude = null) {
|
||||
$baseUrl = 'https://ukdataservices.co.uk';
|
||||
$citySlug = strtolower(str_replace(' ', '-', $city));
|
||||
|
||||
$schema = [
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => 'LocalBusiness',
|
||||
'@id' => $baseUrl . '/locations/' . $citySlug . '#localbusiness',
|
||||
'name' => 'UK Data Services - ' . $city,
|
||||
'description' => 'Professional web scraping, data extraction, and business intelligence services for ' . $city . ' businesses. GDPR-compliant data solutions across ' . $region . '.',
|
||||
'url' => $baseUrl . '/locations/' . $citySlug,
|
||||
'telephone' => '+44 1692 689150',
|
||||
'email' => 'info@ukdataservices.co.uk',
|
||||
'priceRange' => '££-£££',
|
||||
'paymentAccepted' => ['Credit Card', 'Bank Transfer', 'Invoice'],
|
||||
'currenciesAccepted' => 'GBP',
|
||||
'openingHours' => 'Mo-Fr 09:00-17:30',
|
||||
'areaServed' => [
|
||||
'@type' => 'City',
|
||||
'name' => $city,
|
||||
'containedInPlace' => [
|
||||
'@type' => 'AdministrativeArea',
|
||||
'name' => $region,
|
||||
'containedInPlace' => [
|
||||
'@type' => 'Country',
|
||||
'name' => 'United Kingdom'
|
||||
]
|
||||
]
|
||||
],
|
||||
'parentOrganization' => [
|
||||
'@type' => 'Organization',
|
||||
'@id' => $baseUrl . '/#organization',
|
||||
'name' => 'UK Data Services'
|
||||
],
|
||||
'image' => $baseUrl . '/assets/images/ukds-main-logo.png',
|
||||
'sameAs' => [
|
||||
'https://www.linkedin.com/company/ukdataservices',
|
||||
'https://twitter.com/ukdataservices'
|
||||
]
|
||||
];
|
||||
|
||||
// Add geo coordinates if provided
|
||||
if ($latitude && $longitude) {
|
||||
$schema['geo'] = [
|
||||
'@type' => 'GeoCoordinates',
|
||||
'latitude' => $latitude,
|
||||
'longitude' => $longitude
|
||||
];
|
||||
}
|
||||
|
||||
// Add aggregate rating
|
||||
$schema['aggregateRating'] = [
|
||||
'@type' => 'AggregateRating',
|
||||
'ratingValue' => '4.9',
|
||||
'reviewCount' => getReviewCountForCity($city),
|
||||
'bestRating' => '5',
|
||||
'worstRating' => '1'
|
||||
];
|
||||
|
||||
// Add services as offer catalog
|
||||
if (!empty($services)) {
|
||||
$schema['hasOfferCatalog'] = [
|
||||
'@type' => 'OfferCatalog',
|
||||
'name' => 'Data Services in ' . $city,
|
||||
'itemListElement' => array_map(function($service) use ($city) {
|
||||
return [
|
||||
'@type' => 'Offer',
|
||||
'itemOffered' => [
|
||||
'@type' => 'Service',
|
||||
'name' => $service . ' ' . $city
|
||||
]
|
||||
];
|
||||
}, $services)
|
||||
];
|
||||
}
|
||||
|
||||
return '<script type="application/ld+json">' . "\n" .
|
||||
json_encode($schema, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n" .
|
||||
'</script>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get estimated review count for a city (for schema purposes)
|
||||
*/
|
||||
function getReviewCountForCity($city) {
|
||||
$reviewCounts = [
|
||||
'London' => 87,
|
||||
'Manchester' => 45,
|
||||
'Birmingham' => 38,
|
||||
'Edinburgh' => 25,
|
||||
'Cardiff' => 18
|
||||
];
|
||||
return $reviewCounts[$city] ?? 20;
|
||||
}
|
||||
|
||||
/**
|
||||
* Predefined location configurations
|
||||
*/
|
||||
$locationConfigs = [
|
||||
'london' => [
|
||||
'city' => 'London',
|
||||
'region' => 'Greater London',
|
||||
'latitude' => 51.5074,
|
||||
'longitude' => -0.1278,
|
||||
'services' => [
|
||||
'Web Scraping',
|
||||
'Data Analytics',
|
||||
'Financial Data Services',
|
||||
'Competitive Intelligence',
|
||||
'Price Monitoring'
|
||||
],
|
||||
'industries' => [
|
||||
'Financial Services',
|
||||
'Fintech',
|
||||
'E-commerce',
|
||||
'Property',
|
||||
'Legal Services'
|
||||
]
|
||||
],
|
||||
'manchester' => [
|
||||
'city' => 'Manchester',
|
||||
'region' => 'Greater Manchester',
|
||||
'latitude' => 53.4808,
|
||||
'longitude' => -2.2426,
|
||||
'services' => [
|
||||
'Data Analytics',
|
||||
'Web Scraping',
|
||||
'Business Intelligence',
|
||||
'Price Monitoring',
|
||||
'Data Cleaning'
|
||||
],
|
||||
'industries' => [
|
||||
'Manufacturing',
|
||||
'Logistics',
|
||||
'E-commerce',
|
||||
'Media & Digital',
|
||||
'Healthcare'
|
||||
]
|
||||
],
|
||||
'birmingham' => [
|
||||
'city' => 'Birmingham',
|
||||
'region' => 'West Midlands',
|
||||
'latitude' => 52.4862,
|
||||
'longitude' => -1.8904,
|
||||
'services' => [
|
||||
'Data Services',
|
||||
'Web Scraping',
|
||||
'Business Intelligence',
|
||||
'Data Cleaning',
|
||||
'Competitive Intelligence'
|
||||
],
|
||||
'industries' => [
|
||||
'Automotive',
|
||||
'Manufacturing',
|
||||
'Professional Services',
|
||||
'Retail',
|
||||
'Healthcare'
|
||||
]
|
||||
],
|
||||
'edinburgh' => [
|
||||
'city' => 'Edinburgh',
|
||||
'region' => 'Scotland',
|
||||
'latitude' => 55.9533,
|
||||
'longitude' => -3.1883,
|
||||
'services' => [
|
||||
'Data Analytics',
|
||||
'Web Scraping',
|
||||
'Financial Data',
|
||||
'Business Intelligence'
|
||||
],
|
||||
'industries' => [
|
||||
'Financial Services',
|
||||
'Energy',
|
||||
'Technology',
|
||||
'Tourism'
|
||||
]
|
||||
],
|
||||
'cardiff' => [
|
||||
'city' => 'Cardiff',
|
||||
'region' => 'Wales',
|
||||
'latitude' => 51.4816,
|
||||
'longitude' => -3.1791,
|
||||
'services' => [
|
||||
'Data Services',
|
||||
'Web Scraping',
|
||||
'Business Intelligence',
|
||||
'Data Cleaning'
|
||||
],
|
||||
'industries' => [
|
||||
'Public Sector',
|
||||
'Financial Services',
|
||||
'Media',
|
||||
'Manufacturing'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
// If $locationData is set, output the schema automatically
|
||||
if (isset($locationData) && is_array($locationData)) {
|
||||
echo generateLocalBusinessSchema(
|
||||
$locationData['city'],
|
||||
$locationData['region'],
|
||||
$locationData['services'] ?? [],
|
||||
$locationData['latitude'] ?? null,
|
||||
$locationData['longitude'] ?? null
|
||||
);
|
||||
}
|
||||
108
includes/schema/organization-schema.php
Normal file
108
includes/schema/organization-schema.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
/**
|
||||
* Organization Schema Component
|
||||
* Generates Organization structured data for improved SEO
|
||||
*
|
||||
* Usage: Include this file in the <head> of every page
|
||||
* <?php include($_SERVER['DOCUMENT_ROOT'] . '/includes/schema/organization-schema.php'); ?>
|
||||
*/
|
||||
|
||||
$organizationSchema = [
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => 'Organization',
|
||||
'@id' => 'https://ukdataservices.co.uk/#organization',
|
||||
'name' => 'UK Data Services',
|
||||
'legalName' => 'UK Data Services Limited',
|
||||
'url' => 'https://ukdataservices.co.uk',
|
||||
'logo' => [
|
||||
'@type' => 'ImageObject',
|
||||
'url' => 'https://ukdataservices.co.uk/assets/images/ukds-main-logo.png',
|
||||
'width' => 300,
|
||||
'height' => 100
|
||||
],
|
||||
'image' => 'https://ukdataservices.co.uk/assets/images/ukds-main-logo.png',
|
||||
'description' => 'Enterprise web scraping and data analytics services for UK businesses. Specialising in competitive intelligence, price monitoring, and GDPR-compliant data extraction.',
|
||||
'telephone' => '+44 1692 689150',
|
||||
'email' => 'info@ukdataservices.co.uk',
|
||||
'address' => [
|
||||
'@type' => 'PostalAddress',
|
||||
'streetAddress' => 'Professional Data Services Centre',
|
||||
'addressLocality' => 'London',
|
||||
'addressRegion' => 'England',
|
||||
'postalCode' => 'EC1A 1BB',
|
||||
'addressCountry' => 'GB'
|
||||
],
|
||||
'geo' => [
|
||||
'@type' => 'GeoCoordinates',
|
||||
'latitude' => 51.5074,
|
||||
'longitude' => -0.1278
|
||||
],
|
||||
'areaServed' => [
|
||||
[
|
||||
'@type' => 'Country',
|
||||
'name' => 'United Kingdom'
|
||||
],
|
||||
[
|
||||
'@type' => 'City',
|
||||
'name' => 'London'
|
||||
],
|
||||
[
|
||||
'@type' => 'City',
|
||||
'name' => 'Manchester'
|
||||
],
|
||||
[
|
||||
'@type' => 'City',
|
||||
'name' => 'Birmingham'
|
||||
],
|
||||
[
|
||||
'@type' => 'City',
|
||||
'name' => 'Edinburgh'
|
||||
],
|
||||
[
|
||||
'@type' => 'City',
|
||||
'name' => 'Cardiff'
|
||||
]
|
||||
],
|
||||
'sameAs' => [
|
||||
'https://www.linkedin.com/company/ukdataservices',
|
||||
'https://twitter.com/ukdataservices'
|
||||
],
|
||||
'contactPoint' => [
|
||||
[
|
||||
'@type' => 'ContactPoint',
|
||||
'telephone' => '+44 1692 689150',
|
||||
'contactType' => 'sales',
|
||||
'availableLanguage' => 'English',
|
||||
'areaServed' => 'GB',
|
||||
'hoursAvailable' => [
|
||||
'@type' => 'OpeningHoursSpecification',
|
||||
'dayOfWeek' => ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
|
||||
'opens' => '09:00',
|
||||
'closes' => '17:30'
|
||||
]
|
||||
],
|
||||
[
|
||||
'@type' => 'ContactPoint',
|
||||
'email' => 'info@ukdataservices.co.uk',
|
||||
'contactType' => 'customer service',
|
||||
'availableLanguage' => 'English',
|
||||
'areaServed' => 'GB'
|
||||
]
|
||||
],
|
||||
'foundingDate' => '2018',
|
||||
'numberOfEmployees' => [
|
||||
'@type' => 'QuantitativeValue',
|
||||
'value' => '15'
|
||||
],
|
||||
'aggregateRating' => [
|
||||
'@type' => 'AggregateRating',
|
||||
'ratingValue' => '4.9',
|
||||
'reviewCount' => '127',
|
||||
'bestRating' => '5',
|
||||
'worstRating' => '1'
|
||||
]
|
||||
];
|
||||
?>
|
||||
<script type="application/ld+json">
|
||||
<?php echo json_encode($organizationSchema, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); ?>
|
||||
</script>
|
||||
196
includes/schema/review-schema.php
Normal file
196
includes/schema/review-schema.php
Normal file
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
/**
|
||||
* Review Schema Component
|
||||
* Generates Review and AggregateRating structured data
|
||||
*
|
||||
* Usage:
|
||||
* $reviews = [
|
||||
* ['author' => 'John Smith', 'reviewBody' => 'Great service!', 'ratingValue' => 5],
|
||||
* ['author' => 'Jane Doe', 'reviewBody' => 'Excellent work', 'ratingValue' => 5]
|
||||
* ];
|
||||
* include($_SERVER['DOCUMENT_ROOT'] . '/includes/schema/review-schema.php');
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generate Review Schema JSON-LD with AggregateRating
|
||||
*
|
||||
* @param array $reviews Array of review data
|
||||
* @param string $itemName Name of the item being reviewed
|
||||
* @param string $itemType Schema type (Organization, Service, Product, etc.)
|
||||
* @param string|null $itemUrl URL of the item being reviewed
|
||||
* @return string JSON-LD script tag
|
||||
*/
|
||||
function generateReviewSchema($reviews, $itemName = 'UK Data Services', $itemType = 'Organization', $itemUrl = null) {
|
||||
if (empty($reviews)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$baseUrl = 'https://ukdataservices.co.uk';
|
||||
|
||||
// Calculate aggregate rating
|
||||
$totalRating = 0;
|
||||
$reviewCount = count($reviews);
|
||||
foreach ($reviews as $review) {
|
||||
$totalRating += $review['ratingValue'] ?? 5;
|
||||
}
|
||||
$averageRating = round($totalRating / $reviewCount, 1);
|
||||
|
||||
$schema = [
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => $itemType,
|
||||
'name' => $itemName,
|
||||
'url' => $itemUrl ?? $baseUrl,
|
||||
'aggregateRating' => [
|
||||
'@type' => 'AggregateRating',
|
||||
'ratingValue' => number_format($averageRating, 1),
|
||||
'reviewCount' => $reviewCount,
|
||||
'bestRating' => '5',
|
||||
'worstRating' => '1'
|
||||
],
|
||||
'review' => array_map(function($review) {
|
||||
$reviewSchema = [
|
||||
'@type' => 'Review',
|
||||
'author' => [
|
||||
'@type' => 'Person',
|
||||
'name' => $review['author']
|
||||
],
|
||||
'reviewRating' => [
|
||||
'@type' => 'Rating',
|
||||
'ratingValue' => $review['ratingValue'] ?? 5,
|
||||
'bestRating' => '5',
|
||||
'worstRating' => '1'
|
||||
],
|
||||
'reviewBody' => $review['reviewBody']
|
||||
];
|
||||
|
||||
// Add date if provided
|
||||
if (isset($review['datePublished'])) {
|
||||
$reviewSchema['datePublished'] = $review['datePublished'];
|
||||
}
|
||||
|
||||
// Add job title/role if provided
|
||||
if (isset($review['authorRole'])) {
|
||||
$reviewSchema['author']['jobTitle'] = $review['authorRole'];
|
||||
}
|
||||
|
||||
return $reviewSchema;
|
||||
}, $reviews)
|
||||
];
|
||||
|
||||
return '<script type="application/ld+json">' . "\n" .
|
||||
json_encode($schema, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n" .
|
||||
'</script>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate standalone AggregateRating schema (without individual reviews)
|
||||
*/
|
||||
function generateAggregateRatingSchema($itemName, $itemType, $ratingValue, $reviewCount, $itemUrl = null) {
|
||||
$baseUrl = 'https://ukdataservices.co.uk';
|
||||
|
||||
$schema = [
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => $itemType,
|
||||
'name' => $itemName,
|
||||
'url' => $itemUrl ?? $baseUrl,
|
||||
'aggregateRating' => [
|
||||
'@type' => 'AggregateRating',
|
||||
'ratingValue' => number_format($ratingValue, 1),
|
||||
'reviewCount' => $reviewCount,
|
||||
'bestRating' => '5',
|
||||
'worstRating' => '1'
|
||||
]
|
||||
];
|
||||
|
||||
return '<script type="application/ld+json">' . "\n" .
|
||||
json_encode($schema, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n" .
|
||||
'</script>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Predefined testimonials for the homepage and service pages
|
||||
*/
|
||||
$siteTestimonials = [
|
||||
[
|
||||
'author' => 'James Mitchell',
|
||||
'authorRole' => 'Retail Director, London Fashion Group',
|
||||
'reviewBody' => 'UK Data Services transformed our competitor analysis process. Their web scraping accuracy and speed helped us make better pricing decisions. We\'ve seen a 23% improvement in our market positioning since working with them.',
|
||||
'ratingValue' => 5,
|
||||
'datePublished' => '2024-09-15'
|
||||
],
|
||||
[
|
||||
'author' => 'Sarah Chen',
|
||||
'authorRole' => 'Head of Strategy, City Fintech Ltd',
|
||||
'reviewBody' => 'Outstanding data analytics service. They helped us understand our market position and identify opportunities we\'d completely missed. The team\'s expertise in financial data and GDPR compliance gave us complete confidence.',
|
||||
'ratingValue' => 5,
|
||||
'datePublished' => '2024-08-22'
|
||||
],
|
||||
[
|
||||
'author' => 'Michael Thompson',
|
||||
'authorRole' => 'Managing Partner, London Property Advisors',
|
||||
'reviewBody' => 'Professional, GDPR-compliant, and incredibly responsive. Their property data extraction service has become essential to our London operations. The 99.8% accuracy rate they promised was actually exceeded in our experience.',
|
||||
'ratingValue' => 5,
|
||||
'datePublished' => '2024-07-10'
|
||||
],
|
||||
[
|
||||
'author' => 'Emma Williams',
|
||||
'authorRole' => 'CEO, Manchester Logistics Solutions',
|
||||
'reviewBody' => 'Exceptional service quality. The data cleaning and validation work they did on our supply chain database saved us thousands in operational costs. Highly recommend for any Manchester business.',
|
||||
'ratingValue' => 5,
|
||||
'datePublished' => '2024-06-28'
|
||||
],
|
||||
[
|
||||
'author' => 'David Brown',
|
||||
'authorRole' => 'Director of Analytics, Birmingham Manufacturing Co',
|
||||
'reviewBody' => 'We needed complex automotive parts pricing data across multiple European suppliers. UK Data Services delivered beyond expectations with excellent accuracy and compliance documentation.',
|
||||
'ratingValue' => 5,
|
||||
'datePublished' => '2024-05-15'
|
||||
],
|
||||
[
|
||||
'author' => 'Laura Johnson',
|
||||
'authorRole' => 'Marketing Director, Scottish Energy Corp',
|
||||
'reviewBody' => 'Their competitive intelligence service gave us insights into market movements we couldn\'t have obtained otherwise. Professional team with deep technical expertise.',
|
||||
'ratingValue' => 5,
|
||||
'datePublished' => '2024-04-20'
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* Service-specific testimonials
|
||||
*/
|
||||
$serviceTestimonials = [
|
||||
'web-scraping' => [
|
||||
[
|
||||
'author' => 'Robert Harris',
|
||||
'authorRole' => 'CTO, E-commerce Solutions Ltd',
|
||||
'reviewBody' => 'Their web scraping capabilities are world-class. They handled complex JavaScript-rendered pages with ease and delivered clean, accurate data exactly as specified.',
|
||||
'ratingValue' => 5
|
||||
]
|
||||
],
|
||||
'price-monitoring' => [
|
||||
[
|
||||
'author' => 'Sophie Turner',
|
||||
'authorRole' => 'Pricing Manager, UK Retail Chain',
|
||||
'reviewBody' => 'The price monitoring alerts have revolutionized how we respond to competitor pricing. We now react in hours instead of days.',
|
||||
'ratingValue' => 5
|
||||
]
|
||||
],
|
||||
'data-cleaning' => [
|
||||
[
|
||||
'author' => 'Mark Stevens',
|
||||
'authorRole' => 'Data Manager, Healthcare Analytics',
|
||||
'reviewBody' => 'They cleaned and standardized over 2 million patient records with 99.9% accuracy. The improvement in our data quality has been transformational.',
|
||||
'ratingValue' => 5
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
// If $reviews is set, output the schema automatically
|
||||
if (isset($reviews) && is_array($reviews)) {
|
||||
echo generateReviewSchema(
|
||||
$reviews,
|
||||
$reviewItemName ?? 'UK Data Services',
|
||||
$reviewItemType ?? 'Organization',
|
||||
$reviewItemUrl ?? null
|
||||
);
|
||||
}
|
||||
223
includes/schema/service-schema.php
Normal file
223
includes/schema/service-schema.php
Normal file
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
/**
|
||||
* Service Schema Component
|
||||
* Generates Service structured data for improved SEO
|
||||
*
|
||||
* Usage:
|
||||
* $serviceData = [
|
||||
* 'name' => 'Web Scraping Services',
|
||||
* 'description' => 'Professional web scraping services...',
|
||||
* 'url' => 'https://ukdataservices.co.uk/services/web-scraping',
|
||||
* 'serviceType' => 'Web Scraping',
|
||||
* 'priceRange' => '500-50000',
|
||||
* 'features' => ['Feature 1', 'Feature 2']
|
||||
* ];
|
||||
* include($_SERVER['DOCUMENT_ROOT'] . '/includes/schema/service-schema.php');
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generate Service Schema JSON-LD
|
||||
*
|
||||
* @param string $serviceName The name of the service
|
||||
* @param string $serviceDescription A detailed description of the service
|
||||
* @param string $serviceUrl The canonical URL of the service page
|
||||
* @param string $serviceType The type/category of service
|
||||
* @param string|null $priceRange Optional price range (e.g., "500-50000")
|
||||
* @param array $features Optional array of service features
|
||||
* @return string JSON-LD script tag
|
||||
*/
|
||||
function generateServiceSchema($serviceName, $serviceDescription, $serviceUrl, $serviceType, $priceRange = null, $features = []) {
|
||||
$schema = [
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => 'Service',
|
||||
'@id' => $serviceUrl . '#service',
|
||||
'name' => $serviceName,
|
||||
'description' => $serviceDescription,
|
||||
'url' => $serviceUrl,
|
||||
'serviceType' => $serviceType,
|
||||
'provider' => [
|
||||
'@type' => 'Organization',
|
||||
'@id' => 'https://ukdataservices.co.uk/#organization',
|
||||
'name' => 'UK Data Services',
|
||||
'url' => 'https://ukdataservices.co.uk'
|
||||
],
|
||||
'areaServed' => [
|
||||
'@type' => 'Country',
|
||||
'name' => 'United Kingdom'
|
||||
],
|
||||
'availableChannel' => [
|
||||
'@type' => 'ServiceChannel',
|
||||
'serviceUrl' => 'https://ukdataservices.co.uk/quote',
|
||||
'servicePhone' => '+44 1692 689150',
|
||||
'availableLanguage' => 'English'
|
||||
]
|
||||
];
|
||||
|
||||
// Add price specification if provided
|
||||
if ($priceRange) {
|
||||
$prices = explode('-', $priceRange);
|
||||
$schema['offers'] = [
|
||||
'@type' => 'Offer',
|
||||
'priceCurrency' => 'GBP',
|
||||
'priceSpecification' => [
|
||||
'@type' => 'PriceSpecification',
|
||||
'minPrice' => (float)$prices[0],
|
||||
'maxPrice' => isset($prices[1]) ? (float)$prices[1] : null,
|
||||
'priceCurrency' => 'GBP'
|
||||
],
|
||||
'availability' => 'https://schema.org/InStock',
|
||||
'validFrom' => date('Y-m-d')
|
||||
];
|
||||
}
|
||||
|
||||
// Add features/service output if provided
|
||||
if (!empty($features)) {
|
||||
$schema['hasOfferCatalog'] = [
|
||||
'@type' => 'OfferCatalog',
|
||||
'name' => $serviceName . ' Features',
|
||||
'itemListElement' => array_map(function($feature) {
|
||||
return [
|
||||
'@type' => 'Offer',
|
||||
'itemOffered' => [
|
||||
'@type' => 'Service',
|
||||
'name' => $feature
|
||||
]
|
||||
];
|
||||
}, $features)
|
||||
];
|
||||
}
|
||||
|
||||
return '<script type="application/ld+json">' . "\n" .
|
||||
json_encode($schema, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n" .
|
||||
'</script>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Predefined service configurations for consistency
|
||||
*/
|
||||
$serviceConfigs = [
|
||||
'web-scraping' => [
|
||||
'name' => 'Web Scraping Services UK',
|
||||
'description' => 'Professional web scraping and data extraction services for UK businesses. Automated data collection from websites with 99.8% accuracy and full GDPR compliance.',
|
||||
'url' => 'https://ukdataservices.co.uk/services/web-scraping',
|
||||
'serviceType' => 'Web Scraping',
|
||||
'priceRange' => '500-50000',
|
||||
'features' => [
|
||||
'Automated data extraction',
|
||||
'Real-time monitoring',
|
||||
'GDPR-compliant collection',
|
||||
'Custom API delivery',
|
||||
'Multi-format output'
|
||||
]
|
||||
],
|
||||
'competitive-intelligence' => [
|
||||
'name' => 'Competitive Intelligence Services UK',
|
||||
'description' => 'Strategic competitive intelligence and market analysis services. Monitor competitors, track market trends, and gain actionable business insights.',
|
||||
'url' => 'https://ukdataservices.co.uk/services/competitive-intelligence',
|
||||
'serviceType' => 'Competitive Intelligence',
|
||||
'priceRange' => '1000-25000',
|
||||
'features' => [
|
||||
'Competitor monitoring',
|
||||
'Market trend analysis',
|
||||
'Price tracking',
|
||||
'Product intelligence',
|
||||
'Strategic reporting'
|
||||
]
|
||||
],
|
||||
'price-monitoring' => [
|
||||
'name' => 'Price Monitoring Services UK',
|
||||
'description' => 'Real-time price monitoring and competitor price tracking services. E-commerce pricing intelligence for UK retailers and brands.',
|
||||
'url' => 'https://ukdataservices.co.uk/services/price-monitoring',
|
||||
'serviceType' => 'Price Monitoring',
|
||||
'priceRange' => '500-15000',
|
||||
'features' => [
|
||||
'Real-time price tracking',
|
||||
'Competitor price alerts',
|
||||
'MAP monitoring',
|
||||
'Historical price analysis',
|
||||
'Automated reporting'
|
||||
]
|
||||
],
|
||||
'data-cleaning' => [
|
||||
'name' => 'Data Cleaning & Validation Services UK',
|
||||
'description' => 'Professional data cleaning, validation, and standardisation services. Transform messy data into accurate, structured datasets.',
|
||||
'url' => 'https://ukdataservices.co.uk/services/data-cleaning',
|
||||
'serviceType' => 'Data Cleaning',
|
||||
'priceRange' => '500-20000',
|
||||
'features' => [
|
||||
'Duplicate removal',
|
||||
'Data standardisation',
|
||||
'Format validation',
|
||||
'Error correction',
|
||||
'Quality assurance'
|
||||
]
|
||||
],
|
||||
'data-analytics' => [
|
||||
'name' => 'Data Analytics Services UK',
|
||||
'description' => 'Business intelligence and data analytics solutions for UK enterprises. Transform raw data into actionable insights.',
|
||||
'url' => 'https://ukdataservices.co.uk/services/data-analytics',
|
||||
'serviceType' => 'Data Analytics',
|
||||
'priceRange' => '1000-30000',
|
||||
'features' => [
|
||||
'Business intelligence',
|
||||
'Predictive analytics',
|
||||
'Custom dashboards',
|
||||
'Data visualisation',
|
||||
'Strategic insights'
|
||||
]
|
||||
],
|
||||
'api-development' => [
|
||||
'name' => 'API Development Services UK',
|
||||
'description' => 'Custom API development and data integration services. Build robust APIs to connect your systems and automate data workflows.',
|
||||
'url' => 'https://ukdataservices.co.uk/services/api-development',
|
||||
'serviceType' => 'API Development',
|
||||
'priceRange' => '2000-40000',
|
||||
'features' => [
|
||||
'Custom API design',
|
||||
'System integration',
|
||||
'Real-time data feeds',
|
||||
'Authentication systems',
|
||||
'Documentation'
|
||||
]
|
||||
],
|
||||
'property-data-extraction' => [
|
||||
'name' => 'UK Property Data Extraction Services',
|
||||
'description' => 'Professional property data extraction from UK property portals including Rightmove, Zoopla, and OnTheMarket. GDPR-compliant property market intelligence.',
|
||||
'url' => 'https://ukdataservices.co.uk/services/property-data-extraction',
|
||||
'serviceType' => 'Property Data Extraction',
|
||||
'priceRange' => '1000-25000',
|
||||
'features' => [
|
||||
'Property portal data extraction',
|
||||
'Market analysis',
|
||||
'Investment research data',
|
||||
'Rental market intelligence',
|
||||
'Commercial property data'
|
||||
]
|
||||
],
|
||||
'financial-data-services' => [
|
||||
'name' => 'Financial Data Services UK',
|
||||
'description' => 'FCA-aware financial data services for hedge funds, asset managers, and investment firms. Market data extraction and alternative data solutions.',
|
||||
'url' => 'https://ukdataservices.co.uk/services/financial-data-services',
|
||||
'serviceType' => 'Financial Data Services',
|
||||
'priceRange' => '5000-100000',
|
||||
'features' => [
|
||||
'Market data extraction',
|
||||
'Alternative data feeds',
|
||||
'Securities monitoring',
|
||||
'Compliance-aware collection',
|
||||
'API delivery'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
// If $serviceData is set, output the schema automatically
|
||||
if (isset($serviceData) && is_array($serviceData)) {
|
||||
echo generateServiceSchema(
|
||||
$serviceData['name'],
|
||||
$serviceData['description'],
|
||||
$serviceData['url'],
|
||||
$serviceData['serviceType'],
|
||||
$serviceData['priceRange'] ?? null,
|
||||
$serviceData['features'] ?? []
|
||||
);
|
||||
}
|
||||
233
includes/url-config.php
Normal file
233
includes/url-config.php
Normal file
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
/**
|
||||
* URL Configuration
|
||||
* Centralized URL mapping for internal linking consistency
|
||||
*
|
||||
* Usage:
|
||||
* include($_SERVER['DOCUMENT_ROOT'] . '/includes/url-config.php');
|
||||
* echo $urlMap['services']['web-scraping'];
|
||||
*/
|
||||
|
||||
$baseUrl = 'https://ukdataservices.co.uk';
|
||||
|
||||
/**
|
||||
* Complete URL map for the site
|
||||
*/
|
||||
$urlMap = [
|
||||
'home' => '/',
|
||||
'about' => '/about',
|
||||
'quote' => '/quote',
|
||||
'faq' => '/faq',
|
||||
'blog' => '/blog',
|
||||
'contact' => '/#contact',
|
||||
'project-types' => '/project-types',
|
||||
'case-studies' => '/case-studies',
|
||||
|
||||
// Services
|
||||
'services' => [
|
||||
'index' => '/#services',
|
||||
'web-scraping' => '/services/web-scraping',
|
||||
'competitive-intelligence' => '/services/competitive-intelligence',
|
||||
'price-monitoring' => '/services/price-monitoring',
|
||||
'data-cleaning' => '/services/data-cleaning',
|
||||
'data-analytics' => '/services/data-analytics',
|
||||
'api-development' => '/services/api-development',
|
||||
'property-data-extraction' => '/services/property-data-extraction',
|
||||
'financial-data-services' => '/services/financial-data-services'
|
||||
],
|
||||
|
||||
// Locations
|
||||
'locations' => [
|
||||
'index' => '/locations',
|
||||
'london' => '/locations/london',
|
||||
'manchester' => '/locations/manchester',
|
||||
'birmingham' => '/locations/birmingham',
|
||||
'edinburgh' => '/locations/edinburgh',
|
||||
'cardiff' => '/locations/cardiff'
|
||||
],
|
||||
|
||||
// Blog categories
|
||||
'blog-categories' => [
|
||||
'web-scraping' => '/blog/categories/web-scraping',
|
||||
'data-analytics' => '/blog/categories/data-analytics',
|
||||
'business-intelligence' => '/blog/categories/business-intelligence',
|
||||
'compliance' => '/blog/categories/compliance',
|
||||
'industry-insights' => '/blog/categories/industry-insights',
|
||||
'technology' => '/blog/categories/technology',
|
||||
'case-studies' => '/blog/categories/case-studies'
|
||||
],
|
||||
|
||||
// Legal pages
|
||||
'legal' => [
|
||||
'privacy-policy' => '/privacy-policy',
|
||||
'terms-of-service' => '/terms-of-service',
|
||||
'cookie-policy' => '/cookie-policy',
|
||||
'gdpr-compliance' => '/gdpr-compliance'
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* Service metadata for internal linking and SEO
|
||||
*/
|
||||
$serviceData = [
|
||||
'web-scraping' => [
|
||||
'url' => '/services/web-scraping',
|
||||
'title' => 'Web Scraping Services',
|
||||
'shortTitle' => 'Web Scraping',
|
||||
'description' => 'Professional web scraping and data extraction services',
|
||||
'icon' => 'icon-web-scraping-v2.svg'
|
||||
],
|
||||
'competitive-intelligence' => [
|
||||
'url' => '/services/competitive-intelligence',
|
||||
'title' => 'Competitive Intelligence',
|
||||
'shortTitle' => 'Competitive Intelligence',
|
||||
'description' => 'Market analysis and competitor monitoring services',
|
||||
'icon' => 'icon-analytics.svg'
|
||||
],
|
||||
'price-monitoring' => [
|
||||
'url' => '/services/price-monitoring',
|
||||
'title' => 'Price Monitoring',
|
||||
'shortTitle' => 'Price Monitoring',
|
||||
'description' => 'Real-time price tracking and competitor pricing alerts',
|
||||
'icon' => 'icon-price-monitoring.svg'
|
||||
],
|
||||
'data-cleaning' => [
|
||||
'url' => '/services/data-cleaning',
|
||||
'title' => 'Data Cleaning & Validation',
|
||||
'shortTitle' => 'Data Cleaning',
|
||||
'description' => 'Professional data cleaning and quality assurance',
|
||||
'icon' => 'icon-data-processing.svg'
|
||||
],
|
||||
'data-analytics' => [
|
||||
'url' => '/services/data-analytics',
|
||||
'title' => 'Data Analytics',
|
||||
'shortTitle' => 'Analytics',
|
||||
'description' => 'Business intelligence and data analytics solutions',
|
||||
'icon' => 'icon-analytics.svg'
|
||||
],
|
||||
'api-development' => [
|
||||
'url' => '/services/api-development',
|
||||
'title' => 'API Development',
|
||||
'shortTitle' => 'API Development',
|
||||
'description' => 'Custom API development and system integration',
|
||||
'icon' => 'icon-automation.svg'
|
||||
],
|
||||
'property-data-extraction' => [
|
||||
'url' => '/services/property-data-extraction',
|
||||
'title' => 'Property Data Extraction',
|
||||
'shortTitle' => 'Property Data',
|
||||
'description' => 'UK property market data from Rightmove, Zoopla, OnTheMarket',
|
||||
'icon' => 'icon-property.svg'
|
||||
],
|
||||
'financial-data-services' => [
|
||||
'url' => '/services/financial-data-services',
|
||||
'title' => 'Financial Data Services',
|
||||
'shortTitle' => 'Financial Data',
|
||||
'description' => 'FCA-compliant financial and market data extraction',
|
||||
'icon' => 'icon-financial.svg'
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* Location metadata for internal linking
|
||||
*/
|
||||
$locationData = [
|
||||
'london' => [
|
||||
'url' => '/locations/london',
|
||||
'title' => 'London',
|
||||
'region' => 'Greater London',
|
||||
'description' => 'Web scraping and data analytics services in London'
|
||||
],
|
||||
'manchester' => [
|
||||
'url' => '/locations/manchester',
|
||||
'title' => 'Manchester',
|
||||
'region' => 'Greater Manchester',
|
||||
'description' => 'Data analytics and web scraping services in Manchester'
|
||||
],
|
||||
'birmingham' => [
|
||||
'url' => '/locations/birmingham',
|
||||
'title' => 'Birmingham',
|
||||
'region' => 'West Midlands',
|
||||
'description' => 'Data services and business intelligence in Birmingham'
|
||||
],
|
||||
'edinburgh' => [
|
||||
'url' => '/locations/edinburgh',
|
||||
'title' => 'Edinburgh',
|
||||
'region' => 'Scotland',
|
||||
'description' => 'Data analytics and web scraping services in Edinburgh'
|
||||
],
|
||||
'cardiff' => [
|
||||
'url' => '/locations/cardiff',
|
||||
'title' => 'Cardiff',
|
||||
'region' => 'Wales',
|
||||
'description' => 'Data services and business intelligence in Cardiff'
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* Related services mapping for cross-linking
|
||||
*/
|
||||
$relatedServices = [
|
||||
'web-scraping' => ['competitive-intelligence', 'price-monitoring', 'data-cleaning'],
|
||||
'competitive-intelligence' => ['web-scraping', 'price-monitoring', 'data-analytics'],
|
||||
'price-monitoring' => ['web-scraping', 'competitive-intelligence', 'data-analytics'],
|
||||
'data-cleaning' => ['web-scraping', 'data-analytics', 'api-development'],
|
||||
'data-analytics' => ['competitive-intelligence', 'data-cleaning', 'api-development'],
|
||||
'api-development' => ['web-scraping', 'data-cleaning', 'data-analytics'],
|
||||
'property-data-extraction' => ['web-scraping', 'data-cleaning', 'competitive-intelligence'],
|
||||
'financial-data-services' => ['web-scraping', 'data-analytics', 'api-development']
|
||||
];
|
||||
|
||||
/**
|
||||
* Get full URL with base URL
|
||||
*
|
||||
* @param string $path Relative path
|
||||
* @return string Full URL
|
||||
*/
|
||||
function getFullUrl($path) {
|
||||
global $baseUrl;
|
||||
return $baseUrl . $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get service URL by key
|
||||
*
|
||||
* @param string $serviceKey Service identifier
|
||||
* @return string Service URL
|
||||
*/
|
||||
function getServiceUrl($serviceKey) {
|
||||
global $urlMap;
|
||||
return $urlMap['services'][$serviceKey] ?? '/#services';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get location URL by key
|
||||
*
|
||||
* @param string $locationKey Location identifier
|
||||
* @return string Location URL
|
||||
*/
|
||||
function getLocationUrl($locationKey) {
|
||||
global $urlMap;
|
||||
return $urlMap['locations'][$locationKey] ?? '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get related services for a given service
|
||||
*
|
||||
* @param string $serviceKey Current service key
|
||||
* @return array Array of related service data
|
||||
*/
|
||||
function getRelatedServices($serviceKey) {
|
||||
global $relatedServices, $serviceData;
|
||||
|
||||
$related = $relatedServices[$serviceKey] ?? [];
|
||||
$result = [];
|
||||
|
||||
foreach ($related as $relatedKey) {
|
||||
if (isset($serviceData[$relatedKey])) {
|
||||
$result[] = $serviceData[$relatedKey];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
Reference in New Issue
Block a user