File: /wordpress/plugins/wp-cloud-client/1.1.2/src/BusinessProfile/FieldRenderer.php
<?php
declare(strict_types=1);
namespace VPlugins\WPCloudClient\BusinessProfile;
/**
* Pure rendering logic for business profile fields.
*/
final class FieldRenderer {
private const ADDRESS_COMPONENTS = [ 'address', 'city', 'state', 'zip', 'country' ];
private const SOCIAL_PLATFORMS = [
'facebook_url' => 'facebook',
'twitter_url' => 'twitter',
'instagram_url' => 'instagram',
'linkedin_url' => 'linkedin',
'pinterest_url' => 'pinterest',
'foursquare_url' => 'foursquare',
'youtube_url' => 'youtube',
'rss_url' => 'rss',
];
/**
* Render a single profile field by attribute name.
*
* @param array<string, mixed> $profile The business profile data.
* @param string $attr The attribute key to render.
* @return string Rendered HTML output.
*/
public function renderField( array $profile, string $attr ): string {
if ( 'full_address' === $attr ) {
return $this->renderFullAddress( $profile );
}
if ( 'images' === $attr || 'logo' === $attr ) {
return $this->renderLogo( $profile );
}
if ( 'primary' === $attr ) {
return $this->renderPrimary( $profile );
}
if ( 'services_offered' === $attr || 'services' === $attr ) {
return $this->renderServices( $profile );
}
if ( 'hours_of_operation' === $attr ) {
return $this->renderHoursOfOperation( $profile );
}
if ( 'booking_url' === $attr ) {
return $this->renderBookingUrl( $profile );
}
if ( isset( self::SOCIAL_PLATFORMS[ $attr ] ) && ! empty( $profile[ $attr ] ) ) {
return $this->renderSocialIcon( $attr, (string) $profile[ $attr ] );
}
if ( ! array_key_exists( $attr, $profile ) ) {
return '';
}
$value = $profile[ $attr ];
if ( is_array( $value ) ) {
return esc_html( implode( ', ', array_map( 'strval', $value ) ) );
}
$stringValue = (string) $value;
if ( $this->isUrl( $stringValue ) ) {
return sprintf(
'<a href="%s" target="_blank" rel="noopener noreferrer">%s</a>',
esc_url( $stringValue ),
esc_html( $stringValue ),
);
}
return esc_html( $stringValue );
}
/**
* Render the full address as a comma-separated string.
*
* @param array<string, mixed> $profile The business profile data.
* @return string Rendered address or empty string.
*/
public function renderFullAddress( array $profile ): string {
$parts = [];
foreach ( self::ADDRESS_COMPONENTS as $component ) {
if ( ! empty( $profile[ $component ] ) ) {
$parts[] = esc_html( (string) $profile[ $component ] );
}
}
if ( empty( $parts ) ) {
return '';
}
return implode( ', ', $parts );
}
/**
* Render the full address as an HTML list.
*
* @param array<string, mixed> $profile The business profile data.
* @param string $prefix CSS class prefix for styling.
* @return string Rendered HTML list.
*/
public function renderFullAddressList( array $profile, string $prefix = 'wpcc' ): string {
$address = trim( (string) ( $profile['address'] ?? '' ) );
$city = trim( (string) ( $profile['city'] ?? '' ) );
$state = trim( (string) ( $profile['state'] ?? '' ) );
$zip = trim( (string) ( $profile['zip'] ?? '' ) );
$country = trim( (string) ( $profile['country'] ?? '' ) );
$items = [];
if ( '' !== $address ) {
$items[] = sprintf( '<li>%s</li>', esc_html( $address ) );
}
$cityLine = implode( ', ', array_filter( [ $city, trim( $state . ' ' . $zip ) ] ) );
if ( '' !== $cityLine ) {
$items[] = sprintf( '<li>%s</li>', esc_html( $cityLine ) );
}
if ( '' !== $country ) {
$items[] = sprintf( '<li>%s</li>', esc_html( $country ) );
}
if ( empty( $items ) ) {
return '';
}
return sprintf(
'<ul class="%s-full-address" style="padding-left: 0; list-style: none;">%s</ul>',
esc_attr( $prefix ),
implode( '', $items ),
);
}
/**
* Render the business logo image.
*
* @param array<string, mixed> $profile The business profile data.
* @return string Rendered HTML img tag.
*/
public function renderLogo( array $profile ): string {
$src = $profile['images']['logo'] ?? '';
if ( empty( $src ) ) {
return '';
}
return sprintf(
'<img src="%s" alt="%s" style="width: 100px; height: 100px;" />',
esc_url( (string) $src ),
esc_attr__( 'Business logo', 'wp-cloud-client' ),
);
}
/**
* Render the primary business image.
*
* @param array<string, mixed> $profile The business profile data.
* @return string Rendered HTML img tag.
*/
public function renderPrimary( array $profile ): string {
$src = $profile['images']['primary'] ?? '';
if ( empty( $src ) ) {
return '';
}
return sprintf(
'<img src="%s" alt="%s" style="width: 100px; height: 100px;" />',
esc_url( (string) $src ),
esc_attr__( 'Primary business image', 'wp-cloud-client' ),
);
}
/**
* Render the services offered as an HTML list.
*
* @param array<string, mixed> $profile The business profile data.
* @param string $prefix CSS class prefix for styling.
* @return string Rendered HTML list.
*/
public function renderServices( array $profile, string $prefix = 'wpcc' ): string {
$services = $profile['services_offered'] ?? [];
if ( ! is_array( $services ) || empty( $services ) ) {
return sprintf(
'<ul class="%s-services" style="padding-left: 0; list-style: none;">'
. '<li>%s</li></ul>',
esc_attr( $prefix ),
esc_html__( 'None configured', 'wp-cloud-client' ),
);
}
$items = '';
foreach ( $services as $service ) {
$items .= sprintf( '<li>%s</li>', esc_html( (string) $service ) );
}
return sprintf(
'<ul class="%s-services" style="padding-left: 0; list-style: none;">%s</ul>',
esc_attr( $prefix ),
$items,
);
}
/**
* Render hours of operation as an HTML list.
*
* @param array<string, mixed> $profile The business profile data.
* @param string $prefix CSS class prefix for styling.
* @return string Rendered HTML list.
*/
public function renderHoursOfOperation( array $profile, string $prefix = 'wpcc' ): string {
$hours = $profile['hours_of_operation'] ?? [];
if ( is_string( $hours ) ) {
return esc_html( $hours );
}
if ( ! is_array( $hours ) || empty( $hours ) ) {
return sprintf(
'<ul class="%s-hours" style="padding-left: 0; list-style: none;">'
. '<li>%s</li></ul>',
esc_attr( $prefix ),
esc_html__( 'No hours configured', 'wp-cloud-client' ),
);
}
$dayOrder = [
'Sunday' => 0,
'Monday' => 1,
'Tuesday' => 2,
'Wednesday' => 3,
'Thursday' => 4,
'Friday' => 5,
'Saturday' => 6,
];
$grouped = [];
$plain = [];
foreach ( $hours as $timeBlock ) {
if ( ! is_array( $timeBlock ) ) {
$plain[] = esc_html( (string) $timeBlock );
continue;
}
$days = $timeBlock['day_of_week'] ?? [];
$opens = isset( $timeBlock['opens'] ) ? esc_html( (string) $timeBlock['opens'] ) : '';
$closes = isset( $timeBlock['closes'] ) ? esc_html( (string) $timeBlock['closes'] ) : '';
$description = (string) ( $timeBlock['description'] ?? '' );
if ( ! is_array( $days ) || empty( $days ) ) {
continue;
}
$dayKey = implode( ',', $days );
$sortOrder = $dayOrder[ $days[0] ] ?? 99;
if ( ! isset( $grouped[ $dayKey ] ) ) {
$grouped[ $dayKey ] = [
'days' => $days,
'ranges' => [],
'sort' => $sortOrder,
];
}
if ( '' !== $opens && '' !== $closes ) {
$range = "{$opens} - {$closes}";
if ( '' !== $description ) {
$range .= ' (' . self::translateDescription( $description ) . ')';
}
$grouped[ $dayKey ]['ranges'][] = $range;
} elseif ( '' !== $description ) {
$grouped[ $dayKey ]['ranges'][] = self::translateDescription( $description );
}
}
uasort( $grouped, static fn ( array $a, array $b ): int => ( $a['sort'] ?? 99 ) <=> ( $b['sort'] ?? 99 ) );
$items = '';
foreach ( $plain as $text ) {
$items .= sprintf( '<li>%s</li>', $text );
}
foreach ( $grouped as $entry ) {
$daysText = implode( ', ', array_map( [ self::class, 'translateDayName' ], $entry['days'] ) );
if ( ! empty( $entry['ranges'] ) ) {
$items .= sprintf( '<li>%s: %s</li>', $daysText, implode( ', ', $entry['ranges'] ) );
}
}
if ( '' === $items ) {
return '';
}
return sprintf(
'<ul class="%s-hours" style="padding-left: 0; list-style: none;">%s</ul>',
esc_attr( $prefix ),
$items,
);
}
/**
* Render the booking URL.
*
* @param array<string, mixed> $profile The business profile data.
* @return string Rendered URL or empty string.
*/
public function renderBookingUrl( array $profile ): string {
$url = (string) ( $profile['booking_url'] ?? '' );
if ( '' === $url ) {
return '';
}
if ( ! $this->isUrl( $url ) ) {
return '';
}
return esc_url( $url );
}
/**
* Render a social media icon link using SVG.
*
* @param string $attr The attribute key (e.g. 'facebook_url').
* @param string $url The social media URL.
* @return string Rendered HTML anchor with SVG icon.
*/
public function renderSocialIcon( string $attr, string $url ): string {
$platform = self::SOCIAL_PLATFORMS[ $attr ] ?? null;
if ( null === $platform || empty( $url ) ) {
return '';
}
$iconUrl = plugins_url(
'assets/business-profile/images/' . $platform . '.svg',
VPLUGINS_WPCC_FILE,
);
return sprintf(
'<a href="%s" target="_blank" rel="noopener nofollow noreferrer">'
. '<img src="%s" alt="%s" style="height: 32px; width: 32px;" />'
. '</a>',
esc_url( $url ),
esc_url( $iconUrl ),
/* translators: %s: social platform name */
esc_attr( sprintf( __( 'Link to %s', 'wp-cloud-client' ), ucfirst( $platform ) ) ),
);
}
/**
* Get the social platform name for a given attribute key.
*
* @param string $attr The attribute key.
* @return string|null The platform name or null.
*/
public function getSocialPlatform( string $attr ): ?string {
return self::SOCIAL_PLATFORMS[ $attr ] ?? null;
}
/**
* Translate a day-of-week description (e.g. "Closed", "Open 24 hours").
*
* @param string $description The description to translate.
* @return string Translated and escaped string.
*/
private static function translateDescription( string $description ): string {
$map = [
'Closed' => __( 'Closed', 'wp-cloud-client' ),
'Open 24 hours' => __( 'Open 24 hours', 'wp-cloud-client' ),
];
return esc_html( $map[ $description ] ?? $description );
}
/**
* Translate a day name using WordPress locale.
*
* @param string $day The English day name.
* @return string Translated and escaped day name.
*/
private static function translateDayName( string $day ): string {
global $wp_locale;
$dayIndex = [
'Sunday' => 0,
'Monday' => 1,
'Tuesday' => 2,
'Wednesday' => 3,
'Thursday' => 4,
'Friday' => 5,
'Saturday' => 6,
];
if ( $wp_locale && isset( $dayIndex[ $day ] ) ) {
return esc_html( $wp_locale->get_weekday( $dayIndex[ $day ] ) );
}
return esc_html( $day );
}
/**
* Check whether a string is a valid URL.
*
* @param string $value The string to check.
* @return bool True if the string is a valid URL.
*/
private function isUrl( string $value ): bool {
return false !== filter_var( $value, FILTER_VALIDATE_URL );
}
}