PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 10.9.3
WooCommerce v10.9.3
10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / src / Internal / ProductFeed / Storage / JsonFileFeed.php
woocommerce / src / Internal / ProductFeed / Storage Last commit date
JsonFileFeed.php 2 weeks ago
JsonFileFeed.php
309 lines
1 <?php
2 /**
3 * JSON File Feed class.
4 *
5 * @package Automattic\WooCommerce\Internal\ProductFeed
6 */
7
8 declare(strict_types=1);
9
10 namespace Automattic\WooCommerce\Internal\ProductFeed\Storage;
11
12 use Automattic\WooCommerce\Internal\Utilities\FilesystemUtil;
13 use Automattic\WooCommerce\Internal\ProductFeed\Feed\FeedInterface;
14 use Exception;
15
16 // This file works directly with local files. That's fine.
17 // phpcs:disable WordPress.WP.AlternativeFunctions
18
19 /**
20 * File-backed JSON feed storage.
21 *
22 * This class writes JSON directly to a file, entry by entry, without keeping everything in memory.
23 *
24 * @since 10.5.0
25 */
26 class JsonFileFeed implements FeedInterface {
27 public const UPLOAD_DIR = 'product-feeds';
28
29 /**
30 * The number of entries added to the feed.
31 *
32 * @var int
33 */
34 private $entry_count = 0;
35
36 /**
37 * The base name of the feed file.
38 *
39 * @var string
40 */
41 private $base_name;
42
43 /**
44 * The name of the feed file, no directory.
45 *
46 * @var string
47 */
48 private $file_name;
49
50 /**
51 * The path to the feed file.
52 *
53 * @var string
54 */
55 private $file_path;
56
57 /**
58 * The file handle.
59 *
60 * @var resource|false|null
61 */
62 private $file_handle = null;
63
64 /**
65 * Indicates if the feed file has been completed.
66 *
67 * @var bool
68 */
69 private $file_completed = false;
70
71 /**
72 * The URL of the feed file.
73 *
74 * @var string|null
75 */
76 private $file_url = null;
77
78 /**
79 * Indicates if the feed file is in a temp directory.
80 *
81 * @var bool
82 */
83 private $is_temp_filepath = false;
84
85 /**
86 * Constructor.
87 *
88 * @param string $base_name The base name of the feed file.
89 */
90 public function __construct( string $base_name ) {
91 $this->base_name = $base_name;
92 }
93
94 /**
95 * Start the feed.
96 *
97 * @return void
98 * @throws Exception If the feed directory cannot be created.
99 */
100 public function start(): void {
101 $this->entry_count = 0;
102 $this->file_completed = false;
103 $this->file_url = null;
104
105 /**
106 * Allows the current time to be overridden before a feed is stored.
107 *
108 * @param int $time The current time.
109 * @param FeedInterface $feed The feed instance.
110 * @return int The current time.
111 * @since 10.5.0
112 */
113 $current_time = apply_filters( 'woocommerce_product_feed_time', time(), $this );
114 $hash_data = $this->base_name . gmdate( 'r', $current_time );
115 $this->file_name = sprintf(
116 '%s-%s-%s.json',
117 $this->base_name,
118 gmdate( 'Y-m-d', $current_time ),
119 wp_hash( $hash_data )
120 );
121
122 // Start by trying to use a temp directory to generate the feed.
123 $this->file_path = get_temp_dir() . DIRECTORY_SEPARATOR . $this->file_name;
124 $this->file_handle = fopen( $this->file_path, 'w' );
125 if ( false === $this->file_handle ) {
126 // Fall back to immediately using the upload directory for generation.
127 $upload_dir = $this->get_upload_dir();
128 $this->file_path = $upload_dir['path'] . $this->file_name;
129 $this->file_handle = fopen( $this->file_path, 'w' );
130 } else {
131 $this->is_temp_filepath = true;
132 }
133
134 if ( false === $this->file_handle ) {
135 throw new Exception(
136 esc_html(
137 sprintf(
138 /* translators: %s: directory path */
139 __( 'Unable to open feed file for writing: %s', 'woocommerce' ),
140 $this->file_path
141 )
142 )
143 );
144 }
145
146 // Open the array.
147 fwrite( $this->file_handle, '[' );
148 }
149
150 /**
151 * Add an entry to the feed.
152 *
153 * @param array $entry The entry to add.
154 * @return void
155 */
156 public function add_entry( array $entry ): void {
157 if ( ! is_resource( $this->file_handle ) ) {
158 return;
159 }
160
161 $json = wp_json_encode( $entry );
162 if ( false === $json ) {
163 return;
164 }
165
166 if ( $this->entry_count > 0 ) {
167 fwrite( $this->file_handle, ',' );
168 }
169
170 fwrite( $this->file_handle, $json );
171 ++$this->entry_count;
172 }
173
174 /**
175 * End the feed.
176 *
177 * @return void
178 */
179 public function end(): void {
180 if ( ! is_resource( $this->file_handle ) ) {
181 return;
182 }
183
184 // Close the array and the file.
185 fwrite( $this->file_handle, ']' );
186 fclose( $this->file_handle );
187
188 // Indicate that we have a complete file.
189 $this->file_completed = true;
190 }
191
192 /**
193 * Get the number of entries that have been added to the feed.
194 *
195 * This reflects the rows actually written to the feed, which may be fewer
196 * than the number of products iterated by `ProductWalker` because the
197 * validator can silently drop entries before they reach `add_entry()`.
198 *
199 * @since 10.9.0
200 * @return int Number of entries added to the feed.
201 */
202 public function get_entry_count(): int {
203 return $this->entry_count;
204 }
205
206 /**
207 * {@inheritDoc}
208 */
209 public function get_file_path(): ?string {
210 if ( ! $this->file_completed ) {
211 return null;
212 }
213
214 return $this->file_path;
215 }
216
217 /**
218 * {@inheritDoc}
219 *
220 * @throws Exception If the feed file cannot be moved to the upload directory.
221 */
222 public function get_file_url(): ?string {
223 if ( ! $this->file_completed ) {
224 return null;
225 }
226
227 $upload_dir = $this->get_upload_dir();
228
229 // Move the file to the upload directory if it is in temp.
230 if ( $this->is_temp_filepath ) {
231 $tmp_path = $this->file_path;
232 $this->file_path = $upload_dir['path'] . $this->file_name;
233 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
234 if ( ! @copy( $tmp_path, $this->file_path ) ) {
235 $error = error_get_last();
236 $error_message = is_array( $error ) ? $error['message'] : 'Unknown error';
237 throw new Exception(
238 esc_html(
239 sprintf(
240 /* translators: %1$s: file path, %2$s: error message */
241 __( 'Unable to move feed file %1$s to upload directory: %2$s', 'woocommerce' ),
242 $this->file_path,
243 $error_message
244 )
245 )
246 );
247 }
248
249 unlink( $tmp_path );
250
251 $this->is_temp_filepath = false;
252 }
253
254 // Generate the URL.
255 $this->file_url = $upload_dir['url'] . $this->file_name;
256
257 return $this->file_url;
258 }
259
260 /**
261 * Get the upload directory for the feed.
262 *
263 * @return array {
264 * The upload directory for the feed. Both fields end with the right trailing slash.
265 *
266 * @type string $path The path to the upload directory.
267 * @type string $url The URL to the upload directory.
268 * }
269 * @throws Exception If the upload directory cannot be created.
270 */
271 private function get_upload_dir(): array {
272 // Only generate everything once.
273 static $prepared;
274 if ( isset( $prepared ) ) {
275 return $prepared;
276 }
277
278 $upload_dir = wp_upload_dir( null, true );
279 $directory_path = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . self::UPLOAD_DIR . DIRECTORY_SEPARATOR;
280
281 // Try to create the directory if it does not exist.
282 if ( ! is_dir( $directory_path ) ) {
283 FilesystemUtil::mkdir_p_not_indexable( $directory_path );
284 }
285
286 // `mkdir_p_not_indexable()` returns `void`, we have to check again.
287 if ( ! is_dir( $directory_path ) ) {
288 throw new Exception(
289 esc_html(
290 sprintf(
291 /* translators: %s: directory path */
292 __( 'Unable to create feed directory: %s', 'woocommerce' ),
293 $directory_path
294 )
295 )
296 );
297 }
298
299 $directory_url = $upload_dir['baseurl'] . '/' . self::UPLOAD_DIR . '/';
300
301 // Follow the format, returned by `wp_upload_dir()`.
302 $prepared = array(
303 'path' => $directory_path,
304 'url' => $directory_url,
305 );
306 return $prepared;
307 }
308 }
309