PluginProbe
Sharing Image / 3.3
Sharing Image v3.3
3.10 trunk 2.0 2.0.0 2.0.1 2.0.10 2.0.11 2.0.12 2.0.13 2.0.14 2.0.15 2.0.16 2.0.17 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 3.0 3.1 3.2 3.3 All 29 releases
sharing-image / classes / class-generator.php

class-generator.php in Sharing Image 3.3, at classes/class-generator.php

579 lines 14.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Poster generator class.
4 *
5 * @package sharing-image
6 * @author Anton Lukin
7 */
8
9 namespace Sharing_Image;
10
11 use Exception;
12 use WP_Error;
13 use PosterEditor\PosterEditor;
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 die;
17 }
18
19 /**
20 * Generator class.
21 *
22 * @class Generator
23 */
24 class Generator {
25 /**
26 * Check requred template values.
27 *
28 * @param array $template List of template settings.
29 *
30 * @return bool Whether all required values isset.
31 */
32 public static function check_required( $template ) {
33 $required = array( 'width', 'height', 'fill' );
34
35 // Count intersected values.
36 $prepared = count( self::prepare_args( $template, $required ) );
37
38 if ( count( $required ) === $prepared ) {
39 return true;
40 }
41
42 return false;
43 }
44
45 /**
46 * Prepare template before creating poster.
47 * Used to fill fieldset texts and background image.
48 *
49 * @param array $template List of template data.
50 * @param array $fieldset Optional. Fieldset data from widget.
51 * @param string $index Optional. Template index from editor.
52 * @param int $screen_id Optional. Post or term ID from admin screen.
53 * @param string $context Optional. Screen ID context field. Can be settings, post or term.
54 *
55 * @return array List of template data.
56 */
57 public static function prepare_template( $template, $fieldset = array(), $index = null, $screen_id = 0, $context = 'settings' ) {
58 $layers = array();
59
60 if ( isset( $template['layers'] ) ) {
61 $layers = $template['layers'];
62 }
63
64 foreach ( $layers as $key => &$layer ) {
65 if ( 'settings' === $context && ! empty( $template['debug'] ) ) {
66 $layer['debug'] = true;
67 }
68
69 if ( empty( $layer['type'] ) || empty( $layer['dynamic'] ) ) {
70 continue;
71 }
72
73 switch ( $layer['type'] ) {
74 case 'image':
75 $layer = self::prepare_image_layer( $layer, $fieldset, $key, $context );
76 break;
77
78 case 'text':
79 $layer = self::prepare_text_layer( $layer, $fieldset, $key, $context );
80 break;
81 }
82 }
83
84 $template['layers'] = $layers;
85
86 /**
87 * Filters template before generation.
88 *
89 * @param array $template List of template data.
90 * @param array $fieldset Fieldset data from widget or sidebars.
91 * @param string $index Template index from editor.
92 * @param int $screen_id Post or term ID from admin screen.
93 * @param string $context Screen ID context field. Can be settings, post or term.
94 */
95 return apply_filters( 'sharing_image_prepare_template', $template, $fieldset, $index, $screen_id, $context );
96 }
97
98 /**
99 * Create poster using template data.
100 *
101 * @param array $template List of template options.
102 * @param string $path Optional. File path to save.
103 *
104 * @return void|WP_Error WP_Error on failure.
105 */
106 public static function create_poster( $template, $path = null ) {
107 try {
108 $poster = new PosterEditor();
109
110 // Set color canvas options.
111 $options = array(
112 'color' => $template['fill'],
113 'opacity' => 0,
114 );
115
116 // Create empty poster.
117 $poster->canvas( $template['width'], $template['height'], $options );
118
119 if ( ! empty( $template['layers'] ) ) {
120 $poster = self::append_layers( $poster, $template['layers'] );
121 }
122
123 if ( is_null( $path ) ) {
124 return $poster->show( Config::get_file_format(), Config::get_quality() );
125 }
126
127 $poster->save( $path, Config::get_quality() );
128 } catch ( Exception $e ) {
129 return new WP_Error( 'generate', $e->getMessage() );
130 }
131 }
132
133 /**
134 * Get upload file path and url.
135 *
136 * @return array Server file path and url to uploaded image.
137 */
138 public static function get_upload_file() {
139 // Get uploads directory url and path array.
140 list( $path, $url ) = Config::get_upload_dir();
141
142 // Create random file name with proper extension.
143 $name = wp_unique_filename( $path, uniqid() . '.' . Config::get_file_format() );
144
145 $file = array(
146 trailingslashit( $path ) . $name,
147 trailingslashit( $url ) . $name,
148 );
149
150 /**
151 * Filters upload file path and url
152 *
153 * @param array $file Server file path and url to image.
154 * @param string $name Unique file name.
155 */
156 return apply_filters( 'sharing_image_get_upload_file', $file, $name );
157 }
158
159 /**
160 * Append layers to image.
161 *
162 * @param PosterEditor $poster Instance of PosterEditor class.
163 * @param array $layers List of layers options.
164 *
165 * @return PosterEditor PosterEditor instance.
166 */
167 private static function append_layers( $poster, $layers ) {
168 $boundary = array();
169
170 foreach ( $layers as $layer ) {
171 if ( empty( $layer['type'] ) ) {
172 continue;
173 }
174
175 switch ( $layer['type'] ) {
176 case 'filter':
177 $poster = self::draw_filter( $poster, $layer );
178 break;
179
180 case 'rectangle':
181 $poster = self::draw_rectangle( $poster, $layer, $boundary );
182 break;
183
184 case 'image':
185 $poster = self::draw_image( $poster, $layer, $boundary );
186 break;
187
188 case 'text':
189 $poster = self::draw_text( $poster, $layer, $boundary );
190 break;
191 }
192 }
193
194 return $poster;
195 }
196
197 /**
198 * Prepare image layer template.
199 *
200 * @param array $layer Image layer data.
201 * @param array $fieldset Fieldset data from widget or sidebar.
202 * @param string $key Layer key.
203 * @param string $context Screen ID context field. Can be settings, post or term.
204 *
205 * @return array List of image layer data.
206 */
207 private static function prepare_image_layer( $layer, $fieldset, $key, $context ) {
208 if ( 'settings' !== $context ) {
209 unset( $layer['attachment'] );
210 }
211
212 if ( ! empty( $fieldset[ $key ] ) ) {
213 $layer['attachment'] = $fieldset[ $key ];
214 }
215
216 return $layer;
217 }
218
219 /**
220 * Prepare text layer template.
221 *
222 * @param array $layer Text layer data.
223 * @param array $fieldset Fieldset data from widget or sidebar.
224 * @param string $key Layer key.
225 * @param string $context Screen ID context field. Can be settings, post or term.
226 *
227 * @return array List of text layer data.
228 */
229 private static function prepare_text_layer( $layer, $fieldset, $key, $context ) {
230 $layer['content'] = null;
231
232 if ( isset( $layer['sample'] ) && 'settings' === $context ) {
233 $layer['content'] = $layer['sample'];
234 }
235
236 if ( isset( $fieldset[ $key ] ) ) {
237 $layer['content'] = $fieldset[ $key ];
238 }
239
240 return $layer;
241 }
242
243 /**
244 * Draw filter layer
245 *
246 * @param PosterEditor $poster Instance of PosterEditor class.
247 * @param array $layer Filter layer options.
248 *
249 * @return PosterEditor PosterEditor instance.
250 */
251 private static function draw_filter( $poster, $layer ) {
252 if ( ! empty( $layer['grayscale'] ) ) {
253 $poster->grayscale();
254 }
255
256 if ( ! empty( $layer['blur'] ) ) {
257 $poster->blur();
258 }
259
260 if ( isset( $layer['contrast'] ) ) {
261 $poster->contrast( $layer['contrast'] );
262 }
263
264 if ( isset( $layer['brightness'] ) ) {
265 $poster->brightness( $layer['brightness'] );
266 }
267
268 if ( isset( $layer['blackout'] ) ) {
269 $poster->blackout( $layer['blackout'] );
270 }
271
272 return $poster;
273 }
274
275 /**
276 * Draw rectangle layer.
277 *
278 * @param PosterEditor $poster Instance of PosterEditor class.
279 * @param array $layer Rectangle layer options.
280 * @param array $boundary Optional. List of previous layer boundaries.
281 *
282 * @return PosterEditor PosterEditor instance.
283 */
284 private static function draw_rectangle( $poster, $layer, &$boundary = array() ) {
285 // Both x and y should be set.
286 if ( ! isset( $layer['x'], $layer['y'] ) ) {
287 return $poster;
288 }
289
290 $args = self::prepare_args( $layer, array( 'color', 'opacity', 'thickness' ) );
291
292 if ( ! empty( $layer['outline'] ) ) {
293 $args['outline'] = true;
294 }
295
296 if ( ! isset( $layer['width'] ) ) {
297 $layer['width'] = 1;
298 }
299
300 if ( ! isset( $layer['height'] ) ) {
301 $layer['height'] = 1;
302 }
303
304 // Update layer position and dimensions.
305 $layer = self::update_layer_position( $layer, $boundary );
306 $layer = self::update_layer_dimensions( $layer, $poster );
307
308 // Draw rectangle.
309 $poster->rectangle( $layer['x'], $layer['y'], $layer['width'], $layer['height'], $args );
310
311 // Set boundary from layer dimensions.
312 foreach ( array( 'x', 'y', 'width', 'height' ) as $param ) {
313 $boundary[ $param ] = $layer[ $param ];
314 }
315
316 return $poster;
317 }
318
319 /**
320 * Draw image layer
321 *
322 * @param PosterEditor $poster Instance of PosterEditor class.
323 * @param array $layer Image layer options.
324 * @param array $boundary Optional. List of previous layer boundaries.
325 *
326 * @return PosterEditor PosterEditor instance.
327 */
328 private static function draw_image( $poster, $layer, &$boundary = array() ) {
329 if ( ! isset( $layer['attachment'] ) ) {
330 return $poster;
331 }
332
333 $image = new PosterEditor();
334
335 // Update layer position and dimensions.
336 $layer = self::update_layer_position( $layer, $boundary );
337 $layer = self::update_layer_dimensions( $layer, $poster );
338
339 $args = self::prepare_args( $layer, array( 'x', 'y', 'opacity' ) );
340
341 // Try to get file from attachment id.
342 $file = get_attached_file( $layer['attachment'] );
343
344 if ( empty( $file ) ) {
345 return $poster;
346 }
347
348 // Create new editor instance by attachment id.
349 $attachment = $image->make( $file );
350
351 return $poster->insert( self::resize_attachment( $attachment, $layer ), $args, $boundary );
352 }
353
354 /**
355 * Draw text layer.
356 *
357 * @param PosterEditor $poster Instance of PosterEditor class.
358 * @param array $layer Text layer options.
359 * @param array $boundary Optional. List of previous layer boundaries.
360 *
361 * @return PosterEditor PosterEditor instance.
362 */
363 private static function draw_text( $poster, $layer, &$boundary = array() ) {
364 $allowed = array(
365 'x',
366 'y',
367 'width',
368 'height',
369 'fontsize',
370 'color',
371 'lineheight',
372 'opacity',
373 'horizontal',
374 'vertical',
375 'debug',
376 );
377
378 // Update layer position and dimensions.
379 $layer = self::update_layer_position( $layer, $boundary );
380 $layer = self::update_layer_dimensions( $layer, $poster );
381
382 // Prepare common layer args.
383 $args = self::prepare_args( $layer, $allowed );
384
385 // Try to set font file by name or attachment path.
386 $args['fontpath'] = self::get_fontpath( $layer );
387
388 $boundary = self::set_empty_boundary( $args );
389
390 if ( ! empty( $layer['content'] ) ) {
391 $poster->text( $layer['content'], $args, $boundary );
392 }
393
394 return $poster;
395 }
396
397 /**
398 * Set empty boundary list to reset boundary for empty layers.
399 *
400 * @since 3.0
401 *
402 * @param array $args List of common layer args.
403 */
404 private static function set_empty_boundary( $args ) {
405 $boundary = array(
406 'x' => 0,
407 'y' => 0,
408 'width' => 0,
409 'height' => 0,
410 );
411
412 if ( isset( $args['x'] ) ) {
413 $boundary['x'] = $args['x'];
414 }
415
416 if ( isset( $args['y'] ) ) {
417 $boundary['y'] = $args['y'];
418 }
419
420 return $boundary;
421 }
422
423 /**
424 * Smart resizing of image layer attachment according to selected options.
425 *
426 * @param PosterEditor $attachment Instance of PosterEditor class.
427 * @param array $layer Layer settings.
428 *
429 * @return PosterEditor PosterEditor instance.
430 */
431 private static function resize_attachment( $attachment, $layer ) {
432 $width = null;
433
434 if ( isset( $layer['width'] ) ) {
435 $width = $layer['width'];
436 }
437
438 $height = null;
439
440 if ( isset( $layer['height'] ) ) {
441 $height = $layer['height'];
442 }
443
444 // Reduce the image size if width or height dimensions are not specified.
445 if ( is_null( $width ) || is_null( $height ) ) {
446 return $attachment->downsize( $width, $height );
447 }
448
449 if ( 'top' === $layer['resize'] ) {
450 return $attachment->fit( $width, $height, 'top' );
451 }
452
453 if ( 'bottom' === $layer['resize'] ) {
454 return $attachment->fit( $width, $height, 'bottom' );
455 }
456
457 if ( 'ignore' === $layer['resize'] ) {
458 return $attachment->resize( $width, $height );
459 }
460
461 if ( 'crop' === $layer['resize'] ) {
462 return $attachment->crop( $width, $height );
463 }
464
465 // Fit center by default.
466 return $attachment->fit( $width, $height );
467 }
468
469 /**
470 * Get font file path by layer data.
471 *
472 * @param array $layer Layer data.
473 * @param string $path Default file path.
474 *
475 * @return string Filtered path to font file.
476 */
477 private static function get_fontpath( $layer, $path = '' ) {
478 if ( isset( $layer['fontname'] ) ) {
479 $path = sprintf( SHARING_IMAGE_DIR . 'fonts/%s.ttf', $layer['fontname'] );
480 }
481
482 if ( isset( $layer['fontfile'] ) ) {
483 $path = get_attached_file( $layer['fontfile'] );
484 }
485
486 /**
487 * Filters generator font file path.
488 *
489 * @param string $path Font file path.
490 * @param array $layer Layer data.
491 */
492 return apply_filters( 'sharing_image_get_fontpath', $path, $layer );
493 }
494
495 /**
496 * Update layer position using boundary settings.
497 *
498 * @param array $layer List of layer options.
499 * @param array $boundary List of previous layer boundaries.
500 *
501 * @return array List of layer settings.
502 */
503 private static function update_layer_position( $layer, $boundary ) {
504 if ( empty( $layer['boundary'] ) || 'absolute' === $layer['boundary'] ) {
505 return $layer;
506 }
507
508 // Check if boundary sizes exist.
509 foreach ( array( 'x', 'y', 'width', 'height' ) as $size ) {
510 if ( ! isset( $boundary[ $size ] ) ) {
511 return $layer;
512 }
513 }
514
515 $x = $layer['x'] + $boundary['width'] + $boundary['x'];
516 $y = $layer['y'] + $boundary['height'] + $boundary['y'];
517
518 switch ( $layer['boundary'] ) {
519 case 'horizontally':
520 $layer['x'] = $x;
521 break;
522
523 case 'vertically':
524 $layer['y'] = $y;
525 break;
526
527 default:
528 $layer['x'] = $x;
529 $layer['y'] = $y;
530 }
531
532 return $layer;
533 }
534
535 /**
536 * Update layer sizes for negative dimensions.
537 *
538 * @param array $layer List of layer options.
539 * @param PosterEditor $poster Instance of PosterEditor class.
540 *
541 * @return array List of layer settings.
542 */
543 private static function update_layer_dimensions( $layer, $poster ) {
544 if ( ! isset( $layer['width'] ) ) {
545 $layer['width'] = 0;
546 }
547
548 if ( ! isset( $layer['height'] ) ) {
549 $layer['height'] = 0;
550 }
551
552 $width = absint( $poster->width() );
553
554 if ( $layer['width'] <= 0 ) {
555 $layer['width'] = $width + $layer['width'] - $layer['x'];
556 }
557
558 $height = absint( $poster->height() );
559
560 if ( $layer['height'] <= 0 ) {
561 $layer['height'] = $height + $layer['height'] - $layer['y'];
562 }
563
564 return $layer;
565 }
566
567 /**
568 * Prepare args and remove not-allowed keys.
569 *
570 * @param array $args List of source arguments.
571 * @param array $allowed List of allowed keys.
572 *
573 * @return array List of prepared args.
574 */
575 private static function prepare_args( $args, $allowed ) {
576 return wp_array_slice_assoc( $args, $allowed );
577 }
578 }
579