PluginProbe
Extendify / 3.2.1
Extendify v3.2.1
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / app / Draft / Controllers / ImageController.php

ImageController.php in Extendify 3.2.1, at app/Draft/Controllers/ImageController.php

162 lines 5.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Image Controller
5 */
6
7 namespace Extendify\Draft\Controllers;
8
9 defined('ABSPATH') || die('No direct access.');
10
11 // Try to execute set the limit to something that will work for 60s duration.
12 // phpcs:ignore WordPress.PHP.NoSilencedErrors, Generic.PHP.NoSilencedErrors.Discouraged
13 if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {
14 // phpcs:ignore WordPress.PHP.NoSilencedErrors, Generic.PHP.NoSilencedErrors.Discouraged
15 @set_time_limit(60);
16 }
17
18 use Extendify\Shared\Services\Sanitizer;
19
20 /**
21 * The controller for uploading images to the Media Library.
22 */
23
24 class ImageController
25 {
26 /**
27 * Must match `PILL_RIGHT_EDGE` in `src/Shared/lib/ai-label.js`.
28 *
29 * @var float
30 */
31 // phpcs:ignore PSR12.Properties.ConstantVisibility.NotFound -- 7.0 floor: no const visibility
32 const PILL_RIGHT_EDGE = 0.85;
33
34 /**
35 * Upload the provided image
36 *
37 * @param \WP_REST_Request $request - The request.
38 * @return \WP_REST_Response
39 */
40 public static function uploadMedia(\WP_REST_Request $request)
41 {
42 if (! function_exists('\media_sideload_image')) {
43 require_once ABSPATH . 'wp-admin/includes/media.php';
44 require_once ABSPATH . 'wp-admin/includes/file.php';
45 require_once ABSPATH . 'wp-admin/includes/image.php';
46 }
47
48 $imageId = \media_sideload_image($request->get_param('source'), 0, null, 'id');
49
50 // Without this the caller can't tell a failed upload from a declined one.
51 if (\is_wp_error($imageId)) {
52 return new \WP_REST_Response(['message' => $imageId->get_error_message()], 500);
53 }
54
55 if ($request->get_param('ai_generated')) {
56 update_post_meta($imageId, 'extendify_ai_generated', true);
57 $label = $request->get_file_params()['disclosure_label'] ?? null;
58 if (isset($label['tmp_name'])) {
59 self::stampImage($imageId, $label['tmp_name']);
60 }
61 }
62
63 if ($request->get_param('alt_text')) {
64 update_post_meta(
65 $imageId,
66 '_wp_attachment_image_alt',
67 Sanitizer::sanitizeText($request->get_param('alt_text'))
68 );
69 }
70
71 if ($request->get_param('caption')) {
72 wp_update_post(
73 Sanitizer::sanitizeArray([
74 'ID' => $imageId,
75 'post_excerpt' => $request->get_param('caption'),
76 ])
77 );
78 }
79
80 $imageObject = \get_post($imageId);
81 $altText = (get_post_meta($imageId, '_wp_attachment_image_alt', true))
82 ? get_post_meta($imageId, '_wp_attachment_image_alt', true)
83 : '';
84
85 return new \WP_REST_Response(
86 [
87 'id' => $imageId,
88 'caption' => ['raw' => $imageObject->post_excerpt],
89 'source_url' => wp_get_attachment_url($imageId),
90 'alt_text' => $altText,
91 ]
92 );
93 }
94
95 /**
96 * Composite the client-rendered disclosure label onto the attachment. The
97 * PNG already includes its border, unlike the canvas path's fill box.
98 *
99 * @param int $imageId - The attachment ID.
100 * @param string $labelPath - Path to the uploaded label PNG.
101 * @return void
102 */
103 private static function stampImage($imageId, $labelPath)
104 {
105 $file = \get_attached_file($imageId);
106 $type = $file ? (\wp_check_filetype($file)['type'] ?? null) : null;
107 $loaders = [
108 'image/jpeg' => 'imagecreatefromjpeg',
109 'image/png' => 'imagecreatefrompng',
110 'image/webp' => 'imagecreatefromwebp',
111 ];
112 if (!isset($loaders[$type]) || !function_exists($loaders[$type]) || !is_file($file)) {
113 return;
114 }
115
116 // phpcs:ignore WordPress.PHP.NoSilencedErrors, Generic.PHP.NoSilencedErrors.Discouraged
117 $image = @call_user_func($loaders[$type], $file);
118 // phpcs:ignore WordPress.PHP.NoSilencedErrors, Generic.PHP.NoSilencedErrors.Discouraged
119 $label = @imagecreatefrompng($labelPath);
120 if (!$image || !$label) {
121 return;
122 }
123
124 $width = imagesx($image);
125 $height = imagesy($image);
126 // The client renders the label at a 48px font; the canvas stamp uses 2% of the shorter side.
127 $scale = max(12, round(min($width, $height) * 0.02)) / 48;
128 $labelWidth = (int) round(imagesx($label) * $scale);
129 $labelHeight = (int) round(imagesy($label) * $scale);
130 imagealphablending($image, true);
131 imagecopyresampled(
132 $image,
133 $label,
134 (int) round(($width * self::PILL_RIGHT_EDGE) - $labelWidth),
135 (int) round(($height - $labelHeight) / 2),
136 0,
137 0,
138 $labelWidth,
139 $labelHeight,
140 imagesx($label),
141 imagesy($label)
142 );
143
144 if ($type === 'image/jpeg') {
145 imagejpeg($image, $file, 90);
146 }
147
148 if ($type === 'image/png') {
149 imagesavealpha($image, true);
150 imagepng($image, $file);
151 }
152
153 if ($type === 'image/webp') {
154 imagewebp($image, $file, 90);
155 }
156
157 // Sub-sizes were generated from the unstamped original; rebuild them.
158 $metadata = \wp_generate_attachment_metadata($imageId, $file);
159 \wp_update_attachment_metadata($imageId, $metadata);
160 }
161 }
162