Completed
Pull Request — master (#11455)
by
unknown
07:40
created

WC_Structured_Data::generate_product_data()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 45
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

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