Completed
Pull Request — master (#11455)
by
unknown
08:58
created

WC_Structured_Data::generate_shop_data()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 11
nc 2
nop 0
dl 0
loc 16
rs 9.4285
c 1
b 0
f 0
1
<?php
2
3
if ( ! defined( 'ABSPATH' ) ) {
4
	exit; // Exit if accessed directly
5
}
6
7
/**
8
 * Structured data's handler and generator using JSON-LD format.
9
 *
10
 * @class     WC_Structured_Data
11
 * @version   2.7.0
12
 * @package   WooCommerce/Classes
13
 * @category  Class
14
 * @author    Clement Cazaud
15
 */
16
class WC_Structured_Data {
17
  
18
  /**
19
   * @var array Partially structured data
20
   */
21
  private $data;
22
23
  /**
24
   * Checks if the passed $json variable is an array and stores it into $this->data...
25
   *
26
   * @param  array $json Partially structured data
27
   * @return bool Returns false If the param $json is not an array
28
   */
29
  private function set_data( $json ) {
30
    if ( ! is_array( $json ) ) {
31
      return false;
32
    }
33
    
34
    $this->data[] = $json;
35
  }
36
  
37
  /**
38
   * Structures and returns the data...
39
   *
40
   * @return array If data is set, returns the structured data, otherwise returns empty array
41
   */
42
  private function get_data() {
43
    if ( ! $this->data ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->data of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
44
      return array();
45
    }
46
    
47
    foreach ( $this->data as $value ) {
48
      $type = isset( $value['@type'] ) ? $value['@type'] : false;
49
        
50
      if ( 'Product' === $type || 'SoftwareApplication' === $type || 'MusicAlbum' === $type ) {
51
        $products[] = $value;
52
      } elseif ( 'Review' === $type ) {
53
        $reviews[] = $value;
54
      } elseif ( 'WebSite' === $type || 'BreadcrumbList' === $type ) {
55
        $data[] = $value;
56
      }
57
    }
58
    
59
    if ( isset( $products ) ) {
60
      if ( count( $products ) > 1 ) {
61
        $data[] = array( '@graph' => $products );
62
      } else {
63
        $data[] = isset( $reviews ) ? $products[0] + array( 'review' => $reviews ) : $products[0];
64
      }
65
    }
66
 
67
    if ( ! isset( $data ) ) {
68
      return array();
69
    }
70
    
71
    $context['@context'] = apply_filters( 'woocommerce_structured_data_context', 'http://schema.org/' );
72
73
    foreach( $data as $key => $value ) {
74
      $data[ $key ] = $context + $value;
75
    }
76
77
    if ( count( $data ) > 1 ) {
78
      return $data = array( '@graph' => $data );
0 ignored issues
show
Unused Code introduced by
$data is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
79
    } else {
80
      return $data[0];
81
    }
82
  }
83
84
  /**
85
   * Contructor
86
   */
87
  public function __construct() {
88
    add_action( 'woocommerce_before_main_content', array( $this, 'generate_shop_data' ) );
89
    add_action( 'woocommerce_breadcrumb', array( $this, 'generate_breadcrumb_data' ), 10, 1 );
90
    add_action( 'woocommerce_before_shop_loop_item', array( $this, 'generate_product_category_data' ) );
91
    add_action( 'woocommerce_single_product_summary', array( $this, 'generate_product_data' ) );
92
    add_action( 'woocommerce_review_meta', array( $this, 'generate_product_review_data' ), 10, 1 );
93
    add_action( 'wp_footer', array( $this, 'enqueue_data' ) );
94
  }
95
96
  /**
97
   * Sanitizes, encodes and echoes the structured data into `wp_footer` action hook.
98
   */
99
  public function enqueue_data() {
100
    if ( $data = $this->get_data() ) {
101
      $data = $this->sanitize_data( $data );
102
103
      echo '<script type="application/ld+json">' . wp_json_encode( $data ) . '</script>';
104
    }
105
  }
106
107
  /**
108
   * Sanitizes the structured data.
109
   *
110
   * @param  array $data
111
   * @return array $sanitized_data
112
   */
113
  private function sanitize_data( $data ) {
114
    foreach ( $data as $key => $value ) {
115
      $sanitized_data[ sanitize_text_field( $key ) ] = is_array( $value ) ? $this->sanitize_data( $value ) : sanitize_text_field( $value );
116
    }
117
118
    return $sanitized_data;
119
  }
120
121
  /**
122
   * Generates the product category structured data...
123
   * Hooked into the `woocommerce_before_shop_loop_item` action hook...
124
   */
125
  public function generate_product_category_data() {
126
    if ( ! is_product_category() && ! is_shop() ) {
127
      return;
128
    }
129
    
130
    $this->generate_product_data();
131
  }
132
  
133
  /**
134
   * Generates the product structured data...
135
   * Hooked into the `woocommerce_single_product_summary` action hook...
136
   * Applies the `woocommerce_structured_data_product` filter hook for clean structured data customization...
137
   */
138
  public function generate_product_data() {
139
    global $product;
140
141
    if ( $product->is_downloadable() ) {
142
      switch ( $product->download_type ) {
143
        case 'application' :
144
          $type = "SoftwareApplication";
145
        break;
146
        case 'music' :
147
          $type = "MusicAlbum";
148
        break;
149
        default :
150
          $type = "Product";
151
        break;
152
      }
153
    } else {
154
      $type = "Product";
155
    }
156
157
    $json['@type']             = $type;
158
    $json['@id']               = get_the_permalink();
159
    $json['name']              = get_the_title();
160
    $json['description']       = get_the_excerpt();
161
    $json['url']               = get_the_permalink();
162
 
163
    if ( $product->get_rating_count() ) {
164
      $json['aggregateRating'] = array(
165
        '@type'                => 'AggregateRating',
166
        'ratingValue'          => $product->get_average_rating(),
167
        'ratingCount'          => $product->get_rating_count(),
168
        'reviewCount'          => $product->get_review_count()
169
      );
170
    }
171
172
    if ( $is_multi_variation = count( $product->get_children() ) > 1 ? true : false ) {
173
      $variations = $product->get_available_variations();
174
    } else {
175
      $variations = array( null );
176
    }
177
178
    foreach ( $variations as $variation ) {
179
      $product_variation = $is_multi_variation ? wc_get_product( $variation['variation_id'] ) : $product;
180
      
181
      $json_offers[]           = array(
182
        '@type'                => 'Offer',
183
        'priceCurrency'        => get_woocommerce_currency(),
184
        'price'                => $product_variation->get_price(),
185
        'availability'         => 'http://schema.org/' . $stock = ( $product_variation->is_in_stock() ? 'InStock' : 'OutOfStock' ),
186
        'sku'                  => $product_variation->get_sku(),
187
        'image'                => wp_get_attachment_url( $product_variation->get_image_id() ),
188
        'description'          => $is_multi_variation ? $product_variation->get_variation_description() : ''
189
      );  
190
    }
191
192
    $json['offers'] = $json_offers;
193
    
194
    $this->set_data( apply_filters( 'woocommerce_structured_data_product', $json, $product ) );
195
  }
196
197
  /**
198
   * Generates the product review structured data...
199
   * Hooked into the `woocommerce_review_meta` action hook...
200
   * Applies the `woocommerce_structured_data_product_review` filter hook for clean structured data customization...
201
   *
202
   * @param object $comment From `woocommerce_review_meta` action hook
203
   */
204
  public function generate_product_review_data( $comment ) {
205
206
    $json['@type']             = 'Review';
207
    $json['@id']               = get_the_permalink() . '#li-comment-' . get_comment_ID();
208
    $json['datePublished']     = get_comment_date( 'c' );
209
    $json['description']       = get_comment_text();
210
    $json['reviewRating']      = array(
211
      '@type'                  => 'rating',
212
      'ratingValue'            => intval( get_comment_meta( $comment->comment_ID, 'rating', true ) )
213
    );
214
    $json['author']            = array(
215
      '@type'                  => 'Person',
216
      'name'                   => get_comment_author()
217
    );
218
    
219
    $this->set_data( apply_filters( 'woocommerce_structured_data_product_review', $json, $comment ) );
220
  }
221
222
  /**
223
   * Generates the breadcrumbs structured data...
224
   * Hooked into the `woocommerce_breadcrumb` action hook...
225
   * Applies the `woocommerce_structured_data_breadcrumb` filter hook for clean structured data customization...
226
   *
227
   * @param array $args From `woocommerce_breadcrumb` action hook
228
   */
229
  public function generate_breadcrumb_data( $args ) {
230
    if ( empty( $args['breadcrumb'] ) ) {
231
      return;
232
    }
233
234
    $breadcrumb = $args['breadcrumb'];
235
    $position   = 1;
236
237
    foreach ( $breadcrumb as $key => $value ) {
238
      if ( ! empty( $value[1] ) && sizeof( $breadcrumb ) !== $key + 1 ) {
239
        $json_crumbs_item      = array(
240
          '@id'                => $value[1],
241
          'name'               => $value[0]
242
        );
243
      } else {
244
        $json_crumbs_item      = array(
245
          'name'               => $value[0]
246
        );
247
      }
248
249
      $json_crumbs[]           = array(
250
        '@type'                => 'ListItem',
251
        'position'             => $position ++,
252
        'item'                 => $json_crumbs_item
253
      );
254
    }
255
256
    $json['@type']             = 'BreadcrumbList';
257
    $json['itemListElement']   = $json_crumbs;
258
259
    $this->set_data( apply_filters( 'woocommerce_structured_data_breadcrumb', $json, $breadcrumb ) );
260
  }
261
262
  /**
263
   * Generates the shop related structured data...
264
   * Hooked into the `woocommerce_before_main_content` action hook...
265
   * Applies the `woocommerce_structured_data_shop` filter hook for clean structured data customization...
266
   */
267
  public function generate_shop_data() {
268
    if ( ! is_shop() || ! is_front_page() ) {
269
      return;
270
    }
271
272
    $json['@type']             = 'WebSite';
273
    $json['name']              = get_bloginfo( 'name' );
274
    $json['url']               = get_bloginfo( 'url' );
275
    $json['potentialAction']   = array(
276
      '@type'                  => 'SearchAction',
277
      'target'                 => get_bloginfo( 'url' ) . '/?s={search_term_string}&post_type=product',
278
      'query-input'            => 'required name=search_term_string'
279
    );
280
281
    $this->set_data( apply_filters( 'woocommerce_structured_data_shop', $json ) );
282
  }
283
}
284