PluginProbe
Extendify / 3.2.2
Extendify v3.2.2
3.2.2 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 All 128 releases
extendify / app / Draft / Controllers / ImageController.php

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

173 lines 5.9 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 // media_sideload_image() takes no file name; it names the file from the URL.
49 $markAiFilename = function ($file) {
50 $info = pathinfo($file['name']);
51 $file['name'] = "{$info['filename']}-ai-generated.{$info['extension']}";
52 return $file;
53 };
54 if ($request->get_param('ai_generated')) {
55 add_filter('wp_handle_sideload_prefilter', $markAiFilename);
56 }
57
58 $imageId = \media_sideload_image($request->get_param('source'), 0, null, 'id');
59 remove_filter('wp_handle_sideload_prefilter', $markAiFilename);
60
61 // Without this the caller can't tell a failed upload from a declined one.
62 if (\is_wp_error($imageId)) {
63 return new \WP_REST_Response(['message' => $imageId->get_error_message()], 500);
64 }
65
66 if ($request->get_param('ai_generated')) {
67 update_post_meta($imageId, 'extendify_ai_generated', true);
68 $label = $request->get_file_params()['disclosure_label'] ?? null;
69 if (isset($label['tmp_name'])) {
70 self::stampImage($imageId, $label['tmp_name']);
71 }
72 }
73
74 if ($request->get_param('alt_text')) {
75 update_post_meta(
76 $imageId,
77 '_wp_attachment_image_alt',
78 Sanitizer::sanitizeText($request->get_param('alt_text'))
79 );
80 }
81
82 if ($request->get_param('caption')) {
83 wp_update_post(
84 Sanitizer::sanitizeArray([
85 'ID' => $imageId,
86 'post_excerpt' => $request->get_param('caption'),
87 ])
88 );
89 }
90
91 $imageObject = \get_post($imageId);
92 $altText = (get_post_meta($imageId, '_wp_attachment_image_alt', true))
93 ? get_post_meta($imageId, '_wp_attachment_image_alt', true)
94 : '';
95
96 return new \WP_REST_Response(
97 [
98 'id' => $imageId,
99 'caption' => ['raw' => $imageObject->post_excerpt],
100 'source_url' => wp_get_attachment_url($imageId),
101 'alt_text' => $altText,
102 ]
103 );
104 }
105
106 /**
107 * Composite the client-rendered disclosure label onto the attachment. The
108 * PNG already includes its border, unlike the canvas path's fill box.
109 *
110 * @param int $imageId - The attachment ID.
111 * @param string $labelPath - Path to the uploaded label PNG.
112 * @return void
113 */
114 private static function stampImage($imageId, $labelPath)
115 {
116 $file = \get_attached_file($imageId);
117 $type = $file ? (\wp_check_filetype($file)['type'] ?? null) : null;
118 $loaders = [
119 'image/jpeg' => 'imagecreatefromjpeg',
120 'image/png' => 'imagecreatefrompng',
121 'image/webp' => 'imagecreatefromwebp',
122 ];
123 if (!isset($loaders[$type]) || !function_exists($loaders[$type]) || !is_file($file)) {
124 return;
125 }
126
127 // phpcs:ignore WordPress.PHP.NoSilencedErrors, Generic.PHP.NoSilencedErrors.Discouraged
128 $image = @call_user_func($loaders[$type], $file);
129 // phpcs:ignore WordPress.PHP.NoSilencedErrors, Generic.PHP.NoSilencedErrors.Discouraged
130 $label = @imagecreatefrompng($labelPath);
131 if (!$image || !$label) {
132 return;
133 }
134
135 $width = imagesx($image);
136 $height = imagesy($image);
137 // The client renders the label at a 48px font; the canvas stamp uses 2% of the shorter side.
138 $scale = max(12, round(min($width, $height) * 0.02)) / 48;
139 $labelWidth = (int) round(imagesx($label) * $scale);
140 $labelHeight = (int) round(imagesy($label) * $scale);
141 imagealphablending($image, true);
142 imagecopyresampled(
143 $image,
144 $label,
145 (int) round(($width * self::PILL_RIGHT_EDGE) - $labelWidth),
146 (int) round(($height - $labelHeight) / 2),
147 0,
148 0,
149 $labelWidth,
150 $labelHeight,
151 imagesx($label),
152 imagesy($label)
153 );
154
155 if ($type === 'image/jpeg') {
156 imagejpeg($image, $file, 90);
157 }
158
159 if ($type === 'image/png') {
160 imagesavealpha($image, true);
161 imagepng($image, $file);
162 }
163
164 if ($type === 'image/webp') {
165 imagewebp($image, $file, 90);
166 }
167
168 // Sub-sizes were generated from the unstamped original; rebuild them.
169 $metadata = \wp_generate_attachment_metadata($imageId, $file);
170 \wp_update_attachment_metadata($imageId, $metadata);
171 }
172 }
173