*/ /** @param string $url @return string */ public static function media_url( $url ) { $url = esc_url_raw( $url ); if ( ! $url ) { return ''; } $host = trim( (string) get_option( 'publishrank_media_host', '' ) ); if ( ! $host ) { return $url; } $parts = wp_parse_url( $url ); if ( empty( $parts['host'] ) ) { return $url; } $media_host = preg_replace( '#^https?://#i', '', $host ); $media_host = trim( (string) $media_host, '/' ); if ( ! $media_host || in_array( strtolower( $media_host ), array( 'localhost', '127.0.0.1', '::1' ), true ) ) { return $url; } return $media_host ? 'https://' . $media_host . ( isset( $parts['path'] ) ? $parts['path'] : '/' ) . ( isset( $parts['query'] ) ? '?' . $parts['query'] : '' ) : $url; } /** @param string $url @return string */ public static function cdn_url( $url ) { $url = esc_url_raw( $url ); if ( ! $url || ! get_option( 'publishrank_cdn_enabled', false ) ) { return $url; } $host = trim( (string) get_option( 'publishrank_cdn_host', '' ) ); if ( ! $host ) { return $url; } $parts = wp_parse_url( $url ); if ( empty( $parts['host'] ) ) { return $url; } $cdn_host = trim( (string) preg_replace( '#^https?://#i', '', $host ), '/' ); if ( ! $cdn_host || in_array( strtolower( $cdn_host ), array( 'localhost', '127.0.0.1', '::1' ), true ) ) { return $url; } return $cdn_host ? 'https://' . $cdn_host . ( isset( $parts['path'] ) ? $parts['path'] : '/' ) . ( isset( $parts['query'] ) ? '?' . $parts['query'] : '' ) : $url; } /** @param string $url @return string */ public static function asset_url( $url ) { return self::cdn_url( self::media_url( $url ) ); } /* --------------------------------------------------------------------- * Publication Identity — structured Organization / Person identity. * ------------------------------------------------------------------- */ /** @return string 'organization'|'person' */ public static function identity_type() { $type = sanitize_key( (string) get_option( 'publishrank_identity_type', '' ) ); return in_array( $type, array( 'organization', 'person' ), true ) ? $type : 'organization'; } /** @return bool */ public static function identity_is_person() { return 'person' === self::identity_type(); } /** Whether the connected identity and WebSite nodes have real names to publish. */ public static function identity_schema_ready() { if ( '' === self::publication_name() ) { return false; } if ( self::identity_is_person() ) { $person = self::person_identity(); return '' !== trim( (string) $person['name'] ) || '' !== self::publisher_name(); } $organization = self::organization_identity(); return '' !== trim( (string) $organization['name'] ) || '' !== self::publisher_name(); } /** * Organization identity fields. * Falls back to the legacy flat options so existing sites keep their identity. * @return array */ public static function organization_identity() { $get = static function ( $key, $legacy = '' ) { $value = get_option( $key, '' ); if ( '' === (string) $value && $legacy ) { $value = get_option( $legacy, '' ); } return is_string( $value ) ? trim( $value ) : ''; }; return array( 'name' => $get( 'publishrank_identity_name', 'publishrank_seo_publisher_name' ), 'legal_name' => $get( 'publishrank_identity_legal_name', 'publishrank_schema_legal_name' ), 'alternate_name' => $get( 'publishrank_identity_alternate_name', 'publishrank_publication_short_name' ), 'alternate_names' => self::alternate_names( $get( 'publishrank_identity_alternate_names' ) ), 'description' => $get( 'publishrank_identity_description', 'publishrank_schema_org_description' ), 'logo' => $get( 'publishrank_identity_logo', 'publishrank_seo_publisher_logo' ), 'founding_date' => $get( 'publishrank_identity_founding_date', 'publishrank_schema_founding_date' ), 'contact_email' => $get( 'publishrank_identity_contact_email', 'publishrank_contact' ), 'contact_phone' => $get( 'publishrank_identity_contact_phone', 'publishrank_schema_phone' ), 'street' => $get( 'publishrank_identity_street', 'publishrank_schema_street_address' ), 'locality' => $get( 'publishrank_identity_locality', 'publishrank_schema_address_locality' ), 'region' => $get( 'publishrank_identity_region', 'publishrank_schema_address_region' ), 'postal_code' => $get( 'publishrank_identity_postal_code', 'publishrank_schema_postal_code' ), 'country' => $get( 'publishrank_identity_country', 'publishrank_country' ), 'parent_organization' => $get( 'publishrank_identity_parent_organization' ), 'owner_name' => $get( 'publishrank_identity_owner_name' ), 'owner_url' => $get( 'publishrank_identity_owner_url' ), 'owner_logo' => $get( 'publishrank_identity_owner_logo' ), 'copyright_holder' => $get( 'publishrank_identity_copyright_holder' ), 'founder' => $get( 'publishrank_schema_founder' ), 'founding_location' => $get( 'publishrank_schema_founding_location' ), 'knows_about' => $get( 'publishrank_schema_knows_about' ), 'slogan' => $get( 'publishrank_identity_slogan' ), 'vat_id' => $get( 'publishrank_identity_vat_id' ), 'tax_id' => $get( 'publishrank_identity_tax_id' ), 'duns' => $get( 'publishrank_identity_duns' ), 'lei_code' => $get( 'publishrank_identity_lei_code' ), 'iso6523' => $get( 'publishrank_identity_iso6523' ), 'naics' => $get( 'publishrank_identity_naics' ), 'employees' => $get( 'publishrank_identity_employees' ), 'editorial_email' => $get( 'publishrank_identity_editorial_email' ), 'advertising_email' => $get( 'publishrank_identity_advertising_email' ), 'tips_email' => $get( 'publishrank_identity_tips_email' ), ); } /** * Editorial policy pages, keyed by schema.org property. Option key suffix => property. * * @return array */ public static function policy_fields() { return array( 'publishing_principles' => 'publishingPrinciples', 'ethics' => 'ethicsPolicy', 'corrections' => 'correctionsPolicy', 'masthead' => 'masthead', 'fact_checking' => 'verificationFactCheckingPolicy', 'unnamed_sources' => 'unnamedSourcesPolicy', 'feedback' => 'actionableFeedbackPolicy', 'diversity' => 'diversityPolicy', 'diversity_report' => 'diversityStaffingReport', 'ownership_funding' => 'ownershipFundingInfo', 'no_bylines' => 'noBylinesPolicy', 'mission_coverage' => 'missionCoveragePrioritiesPolicy', ); } /** * Configured policy URLs as schema.org property => URL. Empty or invalid URLs are skipped. * * @return array */ public static function publishing_principles() { $out = array(); foreach ( self::policy_fields() as $suffix => $prop ) { $value = get_option( 'publishrank_identity_policy_' . $suffix, '' ); $value = is_string( $value ) ? esc_url_raw( trim( $value ) ) : ''; if ( '' !== $value && wp_http_validate_url( $value ) ) { $out[ $prop ] = $value; } } return $out; } /** @return array */ /** @return string[] Organization aliases, one per line or comma-separated. */ public static function alternate_names( $raw ) { $names = array(); foreach ( preg_split( '/[\r\n,]+/', (string) $raw ) as $name ) { $name = sanitize_text_field( trim( $name ) ); if ( '' !== $name ) { $names[] = $name; } } return array_values( array_unique( array_slice( $names, 0, 50 ) ) ); } public static function person_identity() { $get = static function ( $key ) { $value = get_option( $key, '' ); return is_string( $value ) ? trim( $value ) : ''; }; return array( 'name' => $get( 'publishrank_identity_person_name' ), 'alternate_name' => $get( 'publishrank_identity_person_alternate_name' ), 'description' => $get( 'publishrank_identity_person_description' ), 'image' => $get( 'publishrank_identity_person_image' ), 'job_title' => $get( 'publishrank_identity_person_job_title' ), 'email' => $get( 'publishrank_identity_person_email' ), 'url' => $get( 'publishrank_identity_person_url' ), ); } /** Structured social networks. @return array */ public static function social_networks() { return array( 'x' => array( 'label' => 'X (Twitter)', 'base' => 'https://x.com/%s', ), 'facebook' => array( 'label' => 'Facebook', 'base' => 'https://www.facebook.com/%s', ), 'instagram' => array( 'label' => 'Instagram', 'base' => 'https://www.instagram.com/%s', ), 'linkedin' => array( 'label' => 'LinkedIn', 'base' => 'https://www.linkedin.com/company/%s', ), 'youtube' => array( 'label' => 'YouTube', 'base' => 'https://www.youtube.com/@%s', ), 'tiktok' => array( 'label' => 'TikTok', 'base' => 'https://www.tiktok.com/@%s', ), 'wikipedia' => array( 'label' => 'Wikipedia', 'base' => 'https://en.wikipedia.org/wiki/%s', ), 'wikidata' => array( 'label' => 'Wikidata', 'base' => 'https://www.wikidata.org/wiki/%s', ), ); } /** * SameAs / social profile URLs: structured networks + custom list, * merged with the legacy free-text list. Handles or full URLs accepted. * @return string[] */ public static function social_profiles() { $profiles = array(); $push = static function ( $url ) use ( &$profiles ) { $url = esc_url_raw( trim( (string) $url ) ); if ( $url ) { $profiles[] = $url; } }; // Legacy free-text list (backward compatibility). foreach ( preg_split( '/\r\n|\r|\n/', (string) get_option( 'publishrank_social_profiles', '' ) ) as $line ) { $push( $line ); } // Structured network profiles. foreach ( self::social_networks() as $key => $network ) { $value = trim( (string) get_option( 'publishrank_social_' . $key, '' ) ); if ( '' === $value ) { continue; } if ( preg_match( '#^https?://#i', $value ) ) { $push( $value ); continue; } $push( sprintf( $network['base'], rawurlencode( ltrim( $value, '@' ) ) ) ); } // Custom profiles. foreach ( preg_split( '/\r\n|\r|\n/', (string) get_option( 'publishrank_social_custom', '' ) ) as $line ) { $push( $line ); } return array_values( array_unique( $profiles ) ); } }