PluginProbe
Sharing Image / 3.7
Sharing Image v3.7
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.7, at classes/class-generator.php

595 lines 14.7 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 if ( isset( $layer['textconvert'] ) && 'default' !== $layer['textconvert'] ) {
241 if ( 'uppercase' === $layer['textconvert'] ) {
242 if ( function_exists( 'mb_strtoupper' ) ) {
243 $layer['content'] = mb_strtoupper( $layer['content'] );
244 } else {
245 $layer['content'] = strtoupper( $layer['content'] );
246 }
247 } elseif ( 'lowercase' === $layer['textconvert'] ) {
248 if ( function_exists( 'mb_strtolower' ) ) {
249 $layer['content'] = mb_strtolower( $layer['content'] );
250 } else {
251 $layer['content'] = strtolower( $layer['content'] );
252 }
253 }
254 }
255
256 return $layer;
257 }
258
259 /**
260 * Draw filter layer
261 *
262 * @param PosterEditor $poster Instance of PosterEditor class.
263 * @param array $layer Filter layer options.
264 *
265 * @return PosterEditor PosterEditor instance.
266 */
267 private static function draw_filter( $poster, $layer ) {
268 if ( ! empty( $layer['grayscale'] ) ) {
269 $poster->grayscale();
270 }
271
272 if ( ! empty( $layer['blur'] ) ) {
273 $poster->blur();
274 }
275
276 if ( isset( $layer['contrast'] ) ) {
277 $poster->contrast( $layer['contrast'] );
278 }
279
280 if ( isset( $layer['brightness'] ) ) {
281 $poster->brightness( $layer['brightness'] );
282 }
283
284 if ( isset( $layer['blackout'] ) ) {
285 $poster->blackout( $layer['blackout'] );
286 }
287
288 return $poster;
289 }
290
291 /**
292 * Draw rectangle layer.
293 *
294 * @param PosterEditor $poster Instance of PosterEditor class.
295 * @param array $layer Rectangle layer options.
296 * @param array $boundary Optional. List of previous layer boundaries.
297 *
298 * @return PosterEditor PosterEditor instance.
299 */
300 private static function draw_rectangle( $poster, $layer, &$boundary = array() ) {
301 // Both x and y should be set.
302 if ( ! isset( $layer['x'], $layer['y'] ) ) {
303 return $poster;
304 }
305
306 $args = self::prepare_args( $layer, array( 'color', 'opacity', 'thickness' ) );
307
308 if ( ! empty( $layer['outline'] ) ) {
309 $args['outline'] = true;
310 }
311
312 if ( ! isset( $layer['width'] ) ) {
313 $layer['width'] = 1;
314 }
315
316 if ( ! isset( $layer['height'] ) ) {
317 $layer['height'] = 1;
318 }
319
320 // Update layer position and dimensions.
321 $layer = self::update_layer_position( $layer, $boundary );
322 $layer = self::update_layer_dimensions( $layer, $poster );
323
324 // Draw rectangle.
325 $poster->rectangle( $layer['x'], $layer['y'], $layer['width'], $layer['height'], $args );
326
327 // Set boundary from layer dimensions.
328 foreach ( array( 'x', 'y', 'width', 'height' ) as $param ) {
329 $boundary[ $param ] = $layer[ $param ];
330 }
331
332 return $poster;
333 }
334
335 /**
336 * Draw image layer
337 *
338 * @param PosterEditor $poster Instance of PosterEditor class.
339 * @param array $layer Image layer options.
340 * @param array $boundary Optional. List of previous layer boundaries.
341 *
342 * @return PosterEditor PosterEditor instance.
343 */
344 private static function draw_image( $poster, $layer, &$boundary = array() ) {
345 if ( ! isset( $layer['attachment'] ) ) {
346 return $poster;
347 }
348
349 $image = new PosterEditor();
350
351 // Update layer position and dimensions.
352 $layer = self::update_layer_position( $layer, $boundary );
353 $layer = self::update_layer_dimensions( $layer, $poster );
354
355 $args = self::prepare_args( $layer, array( 'x', 'y', 'opacity' ) );
356
357 // Try to get file from attachment id.
358 $file = get_attached_file( $layer['attachment'] );
359
360 if ( empty( $file ) ) {
361 return $poster;
362 }
363
364 // Create new editor instance by attachment id.
365 $attachment = $image->make( $file );
366
367 return $poster->insert( self::resize_attachment( $attachment, $layer ), $args, $boundary );
368 }
369
370 /**
371 * Draw text layer.
372 *
373 * @param PosterEditor $poster Instance of PosterEditor class.
374 * @param array $layer Text layer options.
375 * @param array $boundary Optional. List of previous layer boundaries.
376 *
377 * @return PosterEditor PosterEditor instance.
378 */
379 private static function draw_text( $poster, $layer, &$boundary = array() ) {
380 $allowed = array(
381 'x',
382 'y',
383 'width',
384 'height',
385 'fontsize',
386 'color',
387 'lineheight',
388 'opacity',
389 'horizontal',
390 'vertical',
391 'debug',
392 );
393
394 // Update layer position and dimensions.
395 $layer = self::update_layer_position( $layer, $boundary );
396 $layer = self::update_layer_dimensions( $layer, $poster );
397
398 // Prepare common layer args.
399 $args = self::prepare_args( $layer, $allowed );
400
401 // Try to set font file by name or attachment path.
402 $args['fontpath'] = self::get_fontpath( $layer );
403
404 $boundary = self::set_empty_boundary( $args );
405
406 if ( ! empty( $layer['content'] ) ) {
407 $poster->text( $layer['content'], $args, $boundary );
408 }
409
410 return $poster;
411 }
412
413 /**
414 * Set empty boundary list to reset boundary for empty layers.
415 *
416 * @since 3.0
417 *
418 * @param array $args List of common layer args.
419 */
420 private static function set_empty_boundary( $args ) {
421 $boundary = array(
422 'x' => 0,
423 'y' => 0,
424 'width' => 0,
425 'height' => 0,
426 );
427
428 if ( isset( $args['x'] ) ) {
429 $boundary['x'] = $args['x'];
430 }
431
432 if ( isset( $args['y'] ) ) {
433 $boundary['y'] = $args['y'];
434 }
435
436 return $boundary;
437 }
438
439 /**
440 * Smart resizing of image layer attachment according to selected options.
441 *
442 * @param PosterEditor $attachment Instance of PosterEditor class.
443 * @param array $layer Layer settings.
444 *
445 * @return PosterEditor PosterEditor instance.
446 */
447 private static function resize_attachment( $attachment, $layer ) {
448 $width = null;
449
450 if ( isset( $layer['width'] ) ) {
451 $width = $layer['width'];
452 }
453
454 $height = null;
455
456 if ( isset( $layer['height'] ) ) {
457 $height = $layer['height'];
458 }
459
460 // Reduce the image size if width or height dimensions are not specified.
461 if ( is_null( $width ) || is_null( $height ) ) {
462 return $attachment->downsize( $width, $height );
463 }
464
465 if ( 'top' === $layer['resize'] ) {
466 return $attachment->fit( $width, $height, 'top' );
467 }
468
469 if ( 'bottom' === $layer['resize'] ) {
470 return $attachment->fit( $width, $height, 'bottom' );
471 }
472
473 if ( 'ignore' === $layer['resize'] ) {
474 return $attachment->resize( $width, $height );
475 }
476
477 if ( 'crop' === $layer['resize'] ) {
478 return $attachment->crop( $width, $height );
479 }
480
481 // Fit center by default.
482 return $attachment->fit( $width, $height );
483 }
484
485 /**
486 * Get font file path by layer data.
487 *
488 * @param array $layer Layer data.
489 * @param string $path Default file path.
490 *
491 * @return string Filtered path to font file.
492 */
493 private static function get_fontpath( $layer, $path = '' ) {
494 if ( isset( $layer['fontname'] ) ) {
495 $path = sprintf( SHARING_IMAGE_DIR . 'fonts/%s.ttf', $layer['fontname'] );
496 }
497
498 if ( isset( $layer['fontfile'] ) ) {
499 $path = get_attached_file( $layer['fontfile'] );
500 }
501
502 /**
503 * Filters generator font file path.
504 *
505 * @param string $path Font file path.
506 * @param array $layer Layer data.
507 */
508 return apply_filters( 'sharing_image_get_fontpath', $path, $layer );
509 }
510
511 /**
512 * Update layer position using boundary settings.
513 *
514 * @param array $layer List of layer options.
515 * @param array $boundary List of previous layer boundaries.
516 *
517 * @return array List of layer settings.
518 */
519 private static function update_layer_position( $layer, $boundary ) {
520 if ( empty( $layer['boundary'] ) || 'absolute' === $layer['boundary'] ) {
521 return $layer;
522 }
523
524 // Check if boundary sizes exist.
525 foreach ( array( 'x', 'y', 'width', 'height' ) as $size ) {
526 if ( ! isset( $boundary[ $size ] ) ) {
527 return $layer;
528 }
529 }
530
531 $x = $layer['x'] + $boundary['width'] + $boundary['x'];
532 $y = $layer['y'] + $boundary['height'] + $boundary['y'];
533
534 switch ( $layer['boundary'] ) {
535 case 'horizontally':
536 $layer['x'] = $x;
537 break;
538
539 case 'vertically':
540 $layer['y'] = $y;
541 break;
542
543 default:
544 $layer['x'] = $x;
545 $layer['y'] = $y;
546 }
547
548 return $layer;
549 }
550
551 /**
552 * Update layer sizes for negative dimensions.
553 *
554 * @param array $layer List of layer options.
555 * @param PosterEditor $poster Instance of PosterEditor class.
556 *
557 * @return array List of layer settings.
558 */
559 private static function update_layer_dimensions( $layer, $poster ) {
560 if ( ! isset( $layer['width'] ) ) {
561 $layer['width'] = 0;
562 }
563
564 if ( ! isset( $layer['height'] ) ) {
565 $layer['height'] = 0;
566 }
567
568 $width = absint( $poster->width() );
569
570 if ( $layer['width'] <= 0 ) {
571 $layer['width'] = $width + $layer['width'] - $layer['x'];
572 }
573
574 $height = absint( $poster->height() );
575
576 if ( $layer['height'] <= 0 ) {
577 $layer['height'] = $height + $layer['height'] - $layer['y'];
578 }
579
580 return $layer;
581 }
582
583 /**
584 * Prepare args and remove not-allowed keys.
585 *
586 * @param array $args List of source arguments.
587 * @param array $allowed List of allowed keys.
588 *
589 * @return array List of prepared args.
590 */
591 private static function prepare_args( $args, $allowed ) {
592 return wp_array_slice_assoc( $args, $allowed );
593 }
594 }
595