3rdparty
6 days ago
abilities
6 days ago
admin
6 days ago
cli
5 years ago
frontend
2 weeks ago
helpers
2 weeks ago
metaboxes
2 years ago
module
2 weeks ago
modules
6 days ago
opengraph
2 weeks ago
replace-variables
2 weeks ago
rest
6 days ago
settings
2 weeks ago
traits
2 weeks ago
updates
2 weeks ago
class-auto-updater.php
5 years ago
class-cmb2.php
2 weeks ago
class-common.php
5 months ago
class-compatibility.php
1 year ago
class-data-encryption.php
5 months ago
class-defaults.php
6 years ago
class-frontend-seo-score.php
2 weeks ago
class-helper.php
10 months ago
class-installer.php
2 weeks ago
class-json-manager.php
1 year ago
class-kb.php
7 months ago
class-metadata.php
2 months ago
class-post.php
1 year ago
class-rewrite.php
5 months ago
class-settings.php
1 year ago
class-term.php
1 year ago
class-thumbnail-overlay.php
1 year ago
class-tracking.php
6 days ago
class-update-email.php
2 weeks ago
class-updates.php
3 months ago
class-user.php
8 months ago
index.php
7 years ago
interface-runner.php
7 years ago
template-tags.php
1 year ago
class-thumbnail-overlay.php
281 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Thumbnails with overlays. |
| 4 | * |
| 5 | * @since 1.0.82 |
| 6 | * @package RankMath |
| 7 | * @subpackage RankMath\Core |
| 8 | * @author Rank Math <support@rankmath.com> |
| 9 | */ |
| 10 | |
| 11 | namespace RankMath; |
| 12 | |
| 13 | use RankMath\Traits\Hooker; |
| 14 | use RankMath\Helpers\Param; |
| 15 | use RankMath\Helpers\Attachment; |
| 16 | |
| 17 | defined( 'ABSPATH' ) || exit; |
| 18 | |
| 19 | /** |
| 20 | * Thumbnail_Overlay class. |
| 21 | */ |
| 22 | class Thumbnail_Overlay { |
| 23 | |
| 24 | use Hooker; |
| 25 | |
| 26 | /** |
| 27 | * Image module to be used (gd or imagick). |
| 28 | * |
| 29 | * @var string |
| 30 | */ |
| 31 | private $image_module = ''; |
| 32 | |
| 33 | /** |
| 34 | * The Constructor. |
| 35 | */ |
| 36 | public function __construct() { |
| 37 | $this->image_module = extension_loaded( 'imagick' ) ? 'imagick' : 'gd'; |
| 38 | |
| 39 | $this->action( 'wp_ajax_rank_math_overlay_thumb', 'generate_overlay_thumbnail' ); |
| 40 | $this->action( 'wp_ajax_nopriv_rank_math_overlay_thumb', 'generate_overlay_thumbnail' ); |
| 41 | } |
| 42 | |
| 43 | /** |
| 44 | * AJAX function to generate overlay image. Used in social thumbnails. |
| 45 | */ |
| 46 | public function generate_overlay_thumbnail() { |
| 47 | $thumbnail_id = Param::request( 'id', 0, FILTER_VALIDATE_INT ); |
| 48 | $type = Param::request( 'type', 'play' ); |
| 49 | $secret = Param::request( 'hash', '' ); |
| 50 | if ( ! $secret ) { |
| 51 | $secret = Param::request( 'secret', '' ); |
| 52 | } |
| 53 | |
| 54 | $choices = Helper::choices_overlay_images(); |
| 55 | if ( ! isset( $choices[ $type ] ) ) { |
| 56 | die(); |
| 57 | } |
| 58 | $overlay_image = $choices[ $type ]['path']; |
| 59 | $image = Attachment::get_scaled_image_path( $thumbnail_id, 'large' ); |
| 60 | |
| 61 | if ( ! $this->is_secret_valid( $thumbnail_id, $type, $secret ) ) { |
| 62 | die(); |
| 63 | } |
| 64 | |
| 65 | // If 'large' thumbnail is not found, fall back to full size. |
| 66 | if ( empty( $image ) ) { |
| 67 | $image = Attachment::get_scaled_image_path( $thumbnail_id, 'full' ); |
| 68 | } |
| 69 | |
| 70 | $position = $choices[ $type ]['position']; |
| 71 | $this->create_overlay_image( $image, $overlay_image, $position ); |
| 72 | |
| 73 | die(); |
| 74 | } |
| 75 | |
| 76 | /** |
| 77 | * Calculate margins for a GD resource based on position string. |
| 78 | * |
| 79 | * @param string $position Position string. |
| 80 | * @param resource $image GD image resource identifier. |
| 81 | * @param resource $stamp GD image resource identifier. |
| 82 | * |
| 83 | * @return array |
| 84 | */ |
| 85 | private function get_position_margins_gd( $position, $image, $stamp ) { |
| 86 | $margins = [ |
| 87 | 'middle_center' => [], |
| 88 | ]; |
| 89 | |
| 90 | $margins['middle_center']['top'] = round( abs( imagesy( $image ) - imagesy( $stamp ) ) / 2 ); |
| 91 | $margins['middle_center']['left'] = round( abs( imagesx( $image ) - imagesx( $stamp ) ) / 2 ); |
| 92 | |
| 93 | $default_margins = $margins['middle_center']; |
| 94 | $margins = $this->do_filter( 'social/overlay_image_positions', $margins, $image, $stamp, 'gd' ); |
| 95 | |
| 96 | if ( ! isset( $margins[ $position ] ) ) { |
| 97 | return $default_margins; |
| 98 | } |
| 99 | |
| 100 | return $margins[ $position ]; |
| 101 | } |
| 102 | |
| 103 | /** |
| 104 | * Calculate margins for an Imagick object based on position string. |
| 105 | * |
| 106 | * @param string $position Position string. |
| 107 | * @param object $image Imagick object. |
| 108 | * @param object $stamp Imagick object. |
| 109 | * |
| 110 | * @return array |
| 111 | */ |
| 112 | private function get_position_margins_imagick( $position, $image, $stamp ) { |
| 113 | $margins = [ |
| 114 | 'middle_center' => [], |
| 115 | ]; |
| 116 | |
| 117 | $margins['middle_center']['top'] = round( abs( $image->getImageHeight() - $stamp->getImageHeight() ) / 2 ); |
| 118 | $margins['middle_center']['left'] = round( abs( $image->getImageWidth() - $stamp->getImageWidth() ) / 2 ); |
| 119 | |
| 120 | $default_margins = $margins['middle_center']; |
| 121 | $margins = $this->do_filter( 'social/overlay_image_positions', $margins, $image, $stamp, 'imagick' ); |
| 122 | |
| 123 | if ( ! isset( $margins[ $position ] ) ) { |
| 124 | return $default_margins; |
| 125 | } |
| 126 | |
| 127 | return $margins[ $position ]; |
| 128 | } |
| 129 | |
| 130 | /** |
| 131 | * Get correct imagecreatef based on image file. |
| 132 | * |
| 133 | * @param string $image_file Image file. |
| 134 | * |
| 135 | * @return string New generated image |
| 136 | */ |
| 137 | private function get_imagecreatefrom_method( $image_file ) { |
| 138 | $image_format = pathinfo( $image_file, PATHINFO_EXTENSION ); |
| 139 | if ( ! in_array( $image_format, [ 'jpg', 'jpeg', 'gif', 'png' ], true ) ) { |
| 140 | return ''; |
| 141 | } |
| 142 | if ( 'jpg' === $image_format ) { |
| 143 | $image_format = 'jpeg'; |
| 144 | } |
| 145 | |
| 146 | return 'imagecreatefrom' . $image_format; |
| 147 | } |
| 148 | |
| 149 | /** |
| 150 | * Create Overlay Image. |
| 151 | * |
| 152 | * @param string $image_file The permalink generated for this post by WordPress. |
| 153 | * @param string $overlay_image The ID of the post. |
| 154 | * @param string $position Image position. |
| 155 | */ |
| 156 | private function create_overlay_image( $image_file, $overlay_image, $position ) { |
| 157 | wp_raise_memory_limit( 'image' ); |
| 158 | |
| 159 | /** |
| 160 | * Filter: 'rank_math/social/create_overlay_image' - Change the create_overlay_image arguments. |
| 161 | */ |
| 162 | $args = $this->do_filter( 'social/create_overlay_image', compact( 'image_file', 'overlay_image', 'position' ) ); |
| 163 | extract( $args ); // phpcs:ignore |
| 164 | |
| 165 | if ( empty( $image_file ) || empty( $overlay_image ) ) { |
| 166 | return; |
| 167 | } |
| 168 | |
| 169 | $method = 'generate_image_' . $this->image_module; |
| 170 | $this->$method( $image_file, $overlay_image, $position ); |
| 171 | die(); |
| 172 | } |
| 173 | |
| 174 | /** |
| 175 | * Generate image using the GD module. |
| 176 | * |
| 177 | * @param string $image_file The permalink generated for this post by WordPress. |
| 178 | * @param string $overlay_image The ID of the post. |
| 179 | * @param string $position Image position. |
| 180 | */ |
| 181 | private function generate_image_gd( $image_file, $overlay_image, $position ) { |
| 182 | $imagecreatefrom = $this->get_imagecreatefrom_method( $image_file ); |
| 183 | $overlay_imagecreatefrom = $this->get_imagecreatefrom_method( $overlay_image ); |
| 184 | if ( ! $imagecreatefrom || ! $overlay_imagecreatefrom ) { |
| 185 | return; |
| 186 | } |
| 187 | |
| 188 | $stamp = $overlay_imagecreatefrom( $overlay_image ); |
| 189 | $image = $imagecreatefrom( $image_file ); |
| 190 | |
| 191 | if ( ! $image || ! $stamp ) { |
| 192 | return; |
| 193 | } |
| 194 | |
| 195 | $stamp_width = imagesx( $stamp ); |
| 196 | $stamp_height = imagesy( $stamp ); |
| 197 | |
| 198 | $img_width = imagesx( $image ); |
| 199 | |
| 200 | if ( $stamp_width > $img_width ) { |
| 201 | $stamp = imagescale( $stamp, $img_width ); |
| 202 | } |
| 203 | |
| 204 | $margins = $this->get_position_margins_gd( $position, $image, $stamp ); |
| 205 | |
| 206 | // Copy the stamp image onto our photo using the margin offsets and the photo width to calculate positioning of the stamp. |
| 207 | imagecopy( $image, $stamp, $margins['left'], $margins['top'], 0, 0, $stamp_width, $stamp_height ); |
| 208 | |
| 209 | // Output and free memory. |
| 210 | header( 'Content-type: image/png' ); |
| 211 | imagepng( $image ); |
| 212 | imagedestroy( $image ); |
| 213 | } |
| 214 | |
| 215 | /** |
| 216 | * Generate image using the Imagick module. |
| 217 | * |
| 218 | * @param string $image_file The permalink generated for this post by WordPress. |
| 219 | * @param string $overlay_image The ID of the post. |
| 220 | * @param string $position Image position. |
| 221 | * |
| 222 | * @return void |
| 223 | */ |
| 224 | private function generate_image_imagick( $image_file, $overlay_image, $position ) { |
| 225 | try { |
| 226 | $stamp = new \Imagick( $overlay_image ); |
| 227 | $image = new \Imagick( $image_file ); |
| 228 | |
| 229 | if ( ! $image->valid() || ! $stamp->valid() || ! $image->getImageFormat() || ! $stamp->getImageFormat() ) { |
| 230 | return; |
| 231 | } |
| 232 | |
| 233 | // Select the first frame to handle animated images properly. |
| 234 | if ( is_callable( [ $stamp, 'setIteratorIndex' ] ) ) { |
| 235 | $stamp->setIteratorIndex( 0 ); |
| 236 | } |
| 237 | if ( is_callable( [ $image, 'setIteratorIndex' ] ) ) { |
| 238 | $image->setIteratorIndex( 0 ); |
| 239 | } |
| 240 | } catch ( \Exception $e ) { |
| 241 | return; |
| 242 | } |
| 243 | |
| 244 | $stamp_width = $stamp->getImageWidth(); |
| 245 | $img_width = $image->getImageWidth(); |
| 246 | |
| 247 | if ( $stamp_width > $img_width ) { |
| 248 | $stamp->resizeImage( $img_width, 0, \Imagick::FILTER_LANCZOS, 1 ); |
| 249 | } |
| 250 | |
| 251 | $margins = $this->get_position_margins_imagick( $position, $image, $stamp ); |
| 252 | |
| 253 | // Copy the stamp image onto our photo using the margin offsets and the photo width to calculate positioning of the stamp. |
| 254 | $image->compositeImage( $stamp, \Imagick::COMPOSITE_OVER, $margins['left'], $margins['top'] ); |
| 255 | |
| 256 | // Output. |
| 257 | header( 'Content-type: image/png' ); |
| 258 | echo $image->getImageBlob(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped |
| 259 | |
| 260 | // Free memory. |
| 261 | $image->clear(); |
| 262 | $image->destroy(); |
| 263 | |
| 264 | $stamp->clear(); |
| 265 | $stamp->destroy(); |
| 266 | } |
| 267 | |
| 268 | /** |
| 269 | * Check if secret key is valid. |
| 270 | * |
| 271 | * @param int $id The ID of the attachment. |
| 272 | * @param string $type Overlay type. |
| 273 | * @param string $secret Secret key. |
| 274 | * |
| 275 | * @return boolean |
| 276 | */ |
| 277 | private function is_secret_valid( $id, $type, $secret ) { |
| 278 | return md5( $id . $type . wp_salt( 'nonce' ) ) === $secret; |
| 279 | } |
| 280 | } |
| 281 |