class-json-ld.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. <?php
  2. /**
  3. * WPSEO plugin file.
  4. *
  5. * @package WPSEO\Frontend
  6. */
  7. /**
  8. * Class WPSEO_JSON_LD
  9. *
  10. * Outputs schema code specific for Google's JSON LD stuff.
  11. *
  12. * @since 1.8
  13. */
  14. class WPSEO_JSON_LD implements WPSEO_WordPress_Integration {
  15. /**
  16. * @var array Holds the social profiles for the entity
  17. */
  18. private $profiles = array();
  19. /**
  20. * @var array Holds the data to put out
  21. */
  22. private $data = array();
  23. /**
  24. * Registers the hooks.
  25. */
  26. public function register_hooks() {
  27. add_action( 'wpseo_head', array( $this, 'json_ld' ), 90 );
  28. add_action( 'wpseo_json_ld', array( $this, 'website' ), 10 );
  29. add_action( 'wpseo_json_ld', array( $this, 'organization_or_person' ), 20 );
  30. add_action( 'wpseo_json_ld', array( $this, 'breadcrumb' ), 20 );
  31. }
  32. /**
  33. * JSON LD output function that the functions for specific code can hook into.
  34. *
  35. * @since 1.8
  36. */
  37. public function json_ld() {
  38. do_action( 'wpseo_json_ld' );
  39. }
  40. /**
  41. * Outputs code to allow Google to recognize social profiles for use in the Knowledge graph.
  42. *
  43. * @since 1.8
  44. */
  45. public function organization_or_person() {
  46. $company_or_person = WPSEO_Options::get( 'company_or_person', '' );
  47. if ( '' === $company_or_person ) {
  48. return;
  49. }
  50. $this->prepare_organization_person_markup();
  51. switch ( $company_or_person ) {
  52. case 'company':
  53. $this->organization();
  54. break;
  55. case 'person':
  56. $this->person();
  57. break;
  58. }
  59. $this->output( $company_or_person );
  60. }
  61. /**
  62. * Outputs code to allow recognition of the internal search engine.
  63. *
  64. * @since 1.5.7
  65. *
  66. * @link https://developers.google.com/structured-data/site-name
  67. */
  68. public function website() {
  69. if ( ! is_front_page() ) {
  70. return;
  71. }
  72. $this->data = array(
  73. '@context' => 'https://schema.org',
  74. '@type' => 'WebSite',
  75. '@id' => '#website',
  76. 'url' => $this->get_home_url(),
  77. 'name' => $this->get_website_name(),
  78. );
  79. $this->add_alternate_name();
  80. $this->internal_search_section();
  81. $this->output( 'website' );
  82. }
  83. /**
  84. * Outputs code to allow recognition of page's position in the site hierarchy
  85. *
  86. * @Link https://developers.google.com/search/docs/data-types/breadcrumb
  87. *
  88. * @return void
  89. */
  90. public function breadcrumb() {
  91. if ( is_front_page() || ! WPSEO_Options::get( 'breadcrumbs-enable', false ) ) {
  92. return;
  93. }
  94. $this->data = array(
  95. '@context' => 'https://schema.org',
  96. '@type' => 'BreadcrumbList',
  97. 'itemListElement' => array(),
  98. );
  99. $breadcrumbs_instance = WPSEO_Breadcrumbs::get_instance();
  100. $breadcrumbs = $breadcrumbs_instance->get_links();
  101. $broken = false;
  102. foreach ( $breadcrumbs as $index => $breadcrumb ) {
  103. if ( ! array_key_exists( 'url', $breadcrumb ) || ! array_key_exists( 'text', $breadcrumb ) ) {
  104. $broken = true;
  105. break;
  106. }
  107. $this->data['itemListElement'][] = array(
  108. '@type' => 'ListItem',
  109. 'position' => ( $index + 1 ),
  110. 'item' => array(
  111. '@id' => $breadcrumb['url'],
  112. 'name' => $breadcrumb['text'],
  113. ),
  114. );
  115. }
  116. // Only output if JSON is correctly formatted.
  117. if ( ! $broken ) {
  118. $this->output( 'breadcrumb' );
  119. }
  120. }
  121. /**
  122. * Outputs the JSON LD code in a valid JSON+LD wrapper.
  123. *
  124. * @since 1.8
  125. *
  126. * @param string $context The context of the output, useful for filtering.
  127. */
  128. private function output( $context ) {
  129. /**
  130. * Filter: 'wpseo_json_ld_output' - Allows filtering of the JSON+LD output.
  131. *
  132. * @api array $output The output array, before its JSON encoded.
  133. *
  134. * @param string $context The context of the output, useful to determine whether to filter or not.
  135. */
  136. $this->data = apply_filters( 'wpseo_json_ld_output', $this->data, $context );
  137. if ( is_array( $this->data ) && ! empty( $this->data ) ) {
  138. echo "<script type='application/ld+json'>", wp_json_encode( $this->data ), '</script>', "\n";
  139. }
  140. // Empty the $data array so we don't output it twice.
  141. $this->data = array();
  142. }
  143. /**
  144. * Schema for Organization.
  145. */
  146. private function organization() {
  147. if ( '' !== WPSEO_Options::get( 'company_name', '' ) ) {
  148. $this->data['@type'] = 'Organization';
  149. $this->data['@id'] = $this->get_home_url() . '#organization';
  150. $this->data['name'] = WPSEO_Options::get( 'company_name' );
  151. $this->data['logo'] = WPSEO_Options::get( 'company_logo', '' );
  152. return;
  153. }
  154. $this->data = false;
  155. }
  156. /**
  157. * Schema for Person.
  158. */
  159. private function person() {
  160. if ( '' !== WPSEO_Options::get( 'person_name', '' ) ) {
  161. $this->data['@type'] = 'Person';
  162. $this->data['@id'] = '#person';
  163. $this->data['name'] = WPSEO_Options::get( 'person_name' );
  164. return;
  165. }
  166. $this->data = false;
  167. }
  168. /**
  169. * Prepares the organization or person markup.
  170. */
  171. private function prepare_organization_person_markup() {
  172. $this->fetch_social_profiles();
  173. $this->data = array(
  174. '@context' => 'https://schema.org',
  175. '@type' => '',
  176. 'url' => $this->get_home_url(),
  177. 'sameAs' => $this->profiles,
  178. );
  179. }
  180. /**
  181. * Retrieve the social profiles to display in the organization output.
  182. *
  183. * @since 1.8
  184. *
  185. * @link https://developers.google.com/webmasters/structured-data/customize/social-profiles
  186. */
  187. private function fetch_social_profiles() {
  188. $social_profiles = array(
  189. 'facebook_site',
  190. 'instagram_url',
  191. 'linkedin_url',
  192. 'plus-publisher',
  193. 'myspace_url',
  194. 'youtube_url',
  195. 'pinterest_url',
  196. );
  197. foreach ( $social_profiles as $profile ) {
  198. if ( WPSEO_Options::get( $profile, '' ) !== '' ) {
  199. $this->profiles[] = WPSEO_Options::get( $profile );
  200. }
  201. }
  202. if ( WPSEO_Options::get( 'twitter_site', '' ) !== '' ) {
  203. $this->profiles[] = 'https://twitter.com/' . WPSEO_Options::get( 'twitter_site' );
  204. }
  205. }
  206. /**
  207. * Retrieves the home URL.
  208. *
  209. * @return string
  210. */
  211. private function get_home_url() {
  212. /**
  213. * Filter: 'wpseo_json_home_url' - Allows filtering of the home URL for Yoast SEO's JSON+LD output.
  214. *
  215. * @api unsigned string
  216. */
  217. return apply_filters( 'wpseo_json_home_url', WPSEO_Utils::home_url() );
  218. }
  219. /**
  220. * Returns an alternate name if one was specified in the Yoast SEO settings.
  221. */
  222. private function add_alternate_name() {
  223. if ( '' !== WPSEO_Options::get( 'alternate_website_name', '' ) ) {
  224. $this->data['alternateName'] = WPSEO_Options::get( 'alternate_website_name' );
  225. }
  226. }
  227. /**
  228. * Adds the internal search JSON LD code to the homepage if it's not disabled.
  229. *
  230. * @link https://developers.google.com/structured-data/slsb-overview
  231. *
  232. * @return void
  233. */
  234. private function internal_search_section() {
  235. /**
  236. * Filter: 'disable_wpseo_json_ld_search' - Allow disabling of the json+ld output.
  237. *
  238. * @api bool $display_search Whether or not to display json+ld search on the frontend.
  239. */
  240. if ( ! apply_filters( 'disable_wpseo_json_ld_search', false ) ) {
  241. /**
  242. * Filter: 'wpseo_json_ld_search_url' - Allows filtering of the search URL for Yoast SEO.
  243. *
  244. * @api string $search_url The search URL for this site with a `{search_term_string}` variable.
  245. */
  246. $search_url = apply_filters( 'wpseo_json_ld_search_url', $this->get_home_url() . '?s={search_term_string}' );
  247. $this->data['potentialAction'] = array(
  248. '@type' => 'SearchAction',
  249. 'target' => $search_url,
  250. 'query-input' => 'required name=search_term_string',
  251. );
  252. }
  253. }
  254. /**
  255. * Returns the website name either from Yoast SEO's options or from the site settings.
  256. *
  257. * @since 2.1
  258. *
  259. * @return string
  260. */
  261. private function get_website_name() {
  262. if ( '' !== WPSEO_Options::get( 'website_name', '' ) ) {
  263. return WPSEO_Options::get( 'website_name' );
  264. }
  265. return get_bloginfo( 'name' );
  266. }
  267. }