Changeset 3787850
- Timestamp:
- 11/06/2026 12:01:11 PM (4 hours ago)
- Location:
- originmark-ai
- Files:
-
- 4 edited
- 1 copied
-
tags/1.1.0 (copied) (copied from originmark-ai/trunk )
-
tags/1.1.0/originmark-ai.php (modified) (5 diffs)
-
tags/1.1.0/readme.txt (modified) (3 diffs)
-
trunk/originmark-ai.php (modified) (5 diffs)
-
trunk/readme.txt (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
originmark-ai/tags/1.1.0/originmark-ai.php
r3661021 r3787850 3 3 * Plugin Name: OriginMark AI 4 4 * Description: Lets you mark AI-generated content on your site with a visible "AI Generated" label. Currently supports images (size, position and offset settings); more content types are planned. 5 * Version: 1.0 5 * Version: 1.1.0 6 * Requires at least: 5.8 7 * Requires PHP: 8.1 6 8 * Author: mnecas 7 9 * License: GPLv2 or later 10 * License URI: https://www.gnu.org/licenses/gpl-2.0.html 8 11 * Donate link: https://buymeacoffee.com/mnecas 9 12 * Text Domain: originmark-ai … … 11 14 12 15 if ( ! defined( 'ABSPATH' ) ) exit; 16 17 const ORIGINMARK_DB_VERSION = 2; 18 const ORIGINMARK_META_KEY = '_originmark_ai_generated'; 19 const ORIGINMARK_LEGACY_META_KEY = '_is_ai_generated'; 20 21 function originmark_register_meta() { 22 register_post_meta( 'attachment', ORIGINMARK_META_KEY, array( 23 'type' => 'boolean', 24 'single' => true, 25 'default' => false, 26 'show_in_rest' => true, 27 'auth_callback' => static fn( $allowed, $meta_key, $post_id ) => current_user_can( 'edit_post', $post_id ), 28 ) ); 29 } 30 add_action( 'init', 'originmark_register_meta' ); 31 32 function originmark_maybe_upgrade() { 33 if ( (int) get_option( 'originmark_db_version', 1 ) >= ORIGINMARK_DB_VERSION ) { 34 return; 35 } 36 37 global $wpdb; 38 39 $result = $wpdb->query( 40 $wpdb->prepare( 41 "INSERT INTO {$wpdb->postmeta} ( post_id, meta_key, meta_value ) 42 SELECT old_meta.post_id, %s, '1' 43 FROM {$wpdb->postmeta} AS old_meta 44 INNER JOIN {$wpdb->posts} AS p ON p.ID = old_meta.post_id 45 LEFT JOIN {$wpdb->postmeta} AS new_meta ON new_meta.post_id = old_meta.post_id AND new_meta.meta_key = %s 46 WHERE old_meta.meta_key = %s 47 AND old_meta.meta_value = '1' 48 AND p.post_type = 'attachment' 49 AND p.post_mime_type LIKE %s 50 AND new_meta.meta_id IS NULL", 51 ORIGINMARK_META_KEY, 52 ORIGINMARK_META_KEY, 53 ORIGINMARK_LEGACY_META_KEY, 54 $wpdb->esc_like( 'image/' ) . '%' 55 ) 56 ); 57 58 if ( false === $result ) { 59 return; 60 } 61 62 if ( function_exists( 'wp_cache_supports' ) && wp_cache_supports( 'flush_group' ) ) { 63 wp_cache_flush_group( 'post_meta' ); 64 } 65 66 update_option( 'originmark_db_version', ORIGINMARK_DB_VERSION, true ); 67 } 68 add_action( 'init', 'originmark_maybe_upgrade', 20 ); 69 70 function originmark_is_ai_generated( int $attachment_id ): bool { 71 return '1' === (string) get_post_meta( $attachment_id, ORIGINMARK_META_KEY, true ) 72 || '1' === get_post_meta( $attachment_id, ORIGINMARK_LEGACY_META_KEY, true ); 73 } 13 74 14 75 … … 182 243 // knihovna medii 183 244 function originmark_add_media_field( $form_fields, $post ) { 184 $is_ai = get_post_meta( $post->ID, '_is_ai_generated', true ); 245 if ( ! wp_attachment_is_image( $post ) ) { 246 return $form_fields; 247 } 248 249 $is_ai = originmark_is_ai_generated( $post->ID ); 185 250 186 251 $form_fields['is_ai_generated'] = array( … … 195 260 </div>', 196 261 $post->ID, 197 checked( $is_ai, '1', false ),262 checked( $is_ai, true, false ), 198 263 esc_html__( 'Mark image as created by AI', 'originmark-ai' ) 199 264 ), … … 203 268 add_filter( 'attachment_fields_to_edit', 'originmark_add_media_field', 10, 2 ); 204 269 205 /** 206 * Uložení z media 207 */ 208 function originmark_save_media_field( $post_id ) { 209 if ( ! isset( $_POST['attachments'][ $post_id ] ) ) { 210 return; 211 } 212 213 $attachment_data = $_POST['attachments'][ $post_id ]; 214 215 if ( isset( $attachment_data['is_ai_generated'] ) ) { 216 update_post_meta( $post_id, '_is_ai_generated', '1' ); 270 function originmark_save_media_field( $post, $attachment_data ) { 271 $attachment_id = (int) ( $post['ID'] ?? $post['post_ID'] ?? 0 ); 272 273 if ( ! $attachment_id || ! wp_attachment_is_image( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) ) { 274 return $post; 275 } 276 277 if ( ! empty( $attachment_data['is_ai_generated'] ) ) { 278 update_post_meta( $attachment_id, ORIGINMARK_META_KEY, '1' ); 217 279 } else { 218 delete_post_meta( $post_id, '_is_ai_generated' ); 219 } 220 } 221 add_action( 'edit_attachment', 'originmark_save_media_field' ); 222 223 /** 224 * Uložení z modálního okna Media Library 225 */ 226 function originmark_save_media_field_ajax( $post, $attachment_data ) { 227 if ( isset( $attachment_data['is_ai_generated'] ) ) { 228 update_post_meta( $post['ID'], '_is_ai_generated', '1' ); 229 } else { 230 delete_post_meta( $post['ID'], '_is_ai_generated' ); 231 } 280 delete_post_meta( $attachment_id, ORIGINMARK_META_KEY ); 281 delete_post_meta( $attachment_id, ORIGINMARK_LEGACY_META_KEY ); 282 } 283 232 284 return $post; 233 285 } 234 add_filter( 'attachment_fields_to_save', 'originmark_save_media_field_ajax', 10, 2 ); 235 236 237 // 4. generovani struktury 238 function originmark_inject_badge( $html ) { 239 $chosen_icon = get_option( 'originmark_selected_icon', 'label_ai_generated_white_transparent.svg' ); 240 $icon_size = get_option( 'originmark_icon_size', '100' ); 241 $icon_pos = get_option( 'originmark_icon_position', 'bottom_right' ); 242 $icon_offset = get_option( 'originmark_icon_offset', '15' ); 243 244 // Obrana do hloubky - i kdyby v DB byla stará/nevalidní hodnota, znovu ji zde ošetříme. 245 $chosen_icon = originmark_sanitize_icon( $chosen_icon ); 246 $icon_size = originmark_sanitize_size( $icon_size ); 247 $icon_pos = originmark_sanitize_position( $icon_pos ); 248 $icon_offset = originmark_sanitize_offset( $icon_offset ); 249 250 $icon_url = plugins_url( $chosen_icon, __FILE__ ); 251 252 $icon_offset_attr = esc_attr( $icon_offset ); 253 $icon_size_attr = esc_attr( $icon_size ); 254 255 switch ( $icon_pos ) { 256 case 'top_left': 257 $position_styles = 'top: ' . $icon_offset_attr . 'px; left: ' . $icon_offset_attr . 'px;'; 258 break; 259 case 'top_right': 260 $position_styles = 'top: ' . $icon_offset_attr . 'px; right: ' . $icon_offset_attr . 'px;'; 261 break; 262 case 'bottom_left': 263 $position_styles = 'bottom: ' . $icon_offset_attr . 'px; left: ' . $icon_offset_attr . 'px;'; 264 break; 265 case 'bottom_right': 266 default: 267 $position_styles = 'bottom: ' . $icon_offset_attr . 'px; right: ' . $icon_offset_attr . 'px;'; 268 break; 269 } 270 271 return '<div class="ai-image-wrapper" style="position: relative; display: block; max-width: 100%; width: fit-content; line-height: 0;">' 272 . $html 273 . '<img class="ai-badge-svg" src="' . esc_url( $icon_url ) . '" alt="' . esc_attr__( 'AI Generated', 'originmark-ai' ) . '" style="position: absolute; ' . $position_styles . ' width: ' . $icon_size_attr . 'px; height: auto; pointer-events: none; z-index: 10; filter: drop-shadow(0px 2px 4px rgba(0,0,0,0.3));" />' 274 . '</div>'; 275 } 276 277 278 // Zobrazení na webu na základě ID obrázku 286 add_filter( 'attachment_fields_to_save', 'originmark_save_media_field', 10, 2 ); 287 288 289 function originmark_get_badge_markup(): string { 290 static $markup = null; 291 292 if ( null !== $markup ) { 293 return $markup; 294 } 295 296 $chosen_icon = originmark_sanitize_icon( get_option( 'originmark_selected_icon', 'label_ai_generated_white_transparent.svg' ) ); 297 $icon_size = originmark_sanitize_size( get_option( 'originmark_icon_size', 100 ) ); 298 $icon_pos = originmark_sanitize_position( get_option( 'originmark_icon_position', 'bottom_right' ) ); 299 $icon_offset = originmark_sanitize_offset( get_option( 'originmark_icon_offset', 15 ) ); 300 301 $position_styles = match ( $icon_pos ) { 302 'top_left' => "top: {$icon_offset}px; left: {$icon_offset}px;", 303 'top_right' => "top: {$icon_offset}px; right: {$icon_offset}px;", 304 'bottom_left' => "bottom: {$icon_offset}px; left: {$icon_offset}px;", 305 default => "bottom: {$icon_offset}px; right: {$icon_offset}px;", 306 }; 307 308 $style = "position: absolute; {$position_styles} width: {$icon_size}px; height: auto; pointer-events: none; z-index: 10; filter: drop-shadow(0px 2px 4px rgba(0,0,0,0.3));"; 309 310 $markup = sprintf( 311 '<img class="ai-badge-svg" src="%s" alt="%s" style="%s" />', 312 esc_url( plugins_url( $chosen_icon, __FILE__ ) ), 313 esc_attr__( 'AI Generated', 'originmark-ai' ), 314 esc_attr( $style ) 315 ); 316 317 return $markup; 318 } 319 320 function originmark_prepare_image_html( string $html ): array { 321 if ( ! preg_match( '/<img\b[^>]*>/i', $html, $img_match, PREG_OFFSET_CAPTURE ) ) { 322 return array( $html, '' ); 323 } 324 325 [ $img_tag, $offset ] = $img_match[0]; 326 $alignment = ''; 327 328 $new_tag = preg_replace_callback( 329 '/\sclass=(["\'])(.*?)\1/i', 330 function ( array $m ) use ( &$alignment ): string { 331 $classes = preg_split( '/\s+/', trim( $m[2] ), -1, PREG_SPLIT_NO_EMPTY ); 332 $found = array_intersect( $classes, array( 'alignleft', 'alignright', 'aligncenter' ) ); 333 334 $alignment = (string) reset( $found ); 335 336 return ' class=' . $m[1] . implode( ' ', array_diff( $classes, $found ) ) . $m[1]; 337 }, 338 $img_tag, 339 1 340 ); 341 342 $new_tag = preg_replace( '/^<img\b/i', '<img data-originmark="1"', $new_tag ); 343 344 return array( substr_replace( $html, $new_tag, $offset, strlen( $img_tag ) ), $alignment ); 345 } 346 347 function originmark_inject_badge( string $html ): string { 348 [ $html, $alignment ] = originmark_prepare_image_html( $html ); 349 350 $wrapper_style = match ( $alignment ) { 351 'aligncenter' => 'display: block; width: fit-content; margin-inline: auto;', 352 'alignleft' => 'display: inline-block; float: left;', 353 'alignright' => 'display: inline-block; float: right;', 354 default => 'display: inline-block; vertical-align: bottom;', 355 }; 356 357 return sprintf( 358 '<span class="%s" style="%s">%s%s</span>', 359 esc_attr( trim( 'ai-image-wrapper ' . $alignment ) ), 360 esc_attr( 'position: relative; max-width: 100%; line-height: 0; ' . $wrapper_style ), 361 $html, 362 originmark_get_badge_markup() 363 ); 364 } 365 366 function originmark_maybe_wrap_image( array $matches ): string { 367 $html = $matches[0]; 368 369 if ( str_contains( $html, 'data-originmark' ) ) { 370 return $html; 371 } 372 373 if ( ! preg_match( '/<img\b[^>]*\bwp-image-(\d+)\b/i', $html, $id_match ) ) { 374 return $html; 375 } 376 377 if ( ! originmark_is_ai_generated( (int) $id_match[1] ) ) { 378 return $html; 379 } 380 381 return originmark_inject_badge( $html ); 382 } 383 279 384 function originmark_filter_content_images( $content ) { 280 if ( is_admin() || is_feed() ) {385 if ( is_admin() || is_feed() || ! is_string( $content ) || ! str_contains( $content, '<img' ) ) { 281 386 return $content; 282 387 } 283 388 284 if ( preg_match_all( '/<img[^>]+>/i', $content, $matches ) ) { 285 foreach ( $matches[0] as $img_html ) { 286 $has_badge = false; 287 288 // Kontrola přes ID obrázku zapsané ve třídě wp-image-XYZ 289 if ( preg_match( '/wp-image-([0-9]+)/', $img_html, $id_match ) ) { 290 $attachment_id = intval( $id_match[1] ); 291 if ( get_post_meta( $attachment_id, '_is_ai_generated', true ) === '1' ) { 292 $has_badge = true; 293 } 294 } 295 296 if ( $has_badge ) { 297 $modified_img = originmark_inject_badge( $img_html ); 298 $content = str_replace( $img_html, $modified_img, $content ); 299 } 300 } 301 } 302 return $content; 389 return preg_replace_callback( 390 '/<picture\b[^>]*>.*?<\/picture>|<img\b[^>]*>/is', 391 'originmark_maybe_wrap_image', 392 $content 393 ) ?? $content; 303 394 } 304 395 add_filter( 'the_content', 'originmark_filter_content_images', 20 ); -
originmark-ai/tags/1.1.0/readme.txt
r3661053 r3787850 5 5 Requires at least: 5.8 6 6 Tested up to: 7.0 7 Requires PHP: 7.48 Stable tag: 1. 07 Requires PHP: 8.1 8 Stable tag: 1.1.0 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 64 64 == Changelog == 65 65 66 = 1.1.0 = 67 * Fix: an image used more than once in a post no longer gets a duplicate badge. 68 * Fix: badge wrapper no longer breaks <picture> elements, paragraphs or left/right/center alignment. 69 * Fix: the "AI Generated" checkbox is shown only for images. 70 * Security: saving the checkbox now relies only on the core attachment save flow with a capability check. 71 * Change: the AI flag is stored under a prefixed meta key; existing data is migrated automatically. 72 * Requires PHP 8.1 or newer. 73 66 74 = 1.0 = 67 75 * Initial release. … … 69 77 == Upgrade Notice == 70 78 79 = 1.1.0 = 80 Bug fixes for badge rendering. Requires PHP 8.1+. Existing AI flags are migrated automatically. 81 71 82 = 1.0 = 72 83 Initial release. -
originmark-ai/trunk/originmark-ai.php
r3661021 r3787850 3 3 * Plugin Name: OriginMark AI 4 4 * Description: Lets you mark AI-generated content on your site with a visible "AI Generated" label. Currently supports images (size, position and offset settings); more content types are planned. 5 * Version: 1.0 5 * Version: 1.1.0 6 * Requires at least: 5.8 7 * Requires PHP: 8.1 6 8 * Author: mnecas 7 9 * License: GPLv2 or later 10 * License URI: https://www.gnu.org/licenses/gpl-2.0.html 8 11 * Donate link: https://buymeacoffee.com/mnecas 9 12 * Text Domain: originmark-ai … … 11 14 12 15 if ( ! defined( 'ABSPATH' ) ) exit; 16 17 const ORIGINMARK_DB_VERSION = 2; 18 const ORIGINMARK_META_KEY = '_originmark_ai_generated'; 19 const ORIGINMARK_LEGACY_META_KEY = '_is_ai_generated'; 20 21 function originmark_register_meta() { 22 register_post_meta( 'attachment', ORIGINMARK_META_KEY, array( 23 'type' => 'boolean', 24 'single' => true, 25 'default' => false, 26 'show_in_rest' => true, 27 'auth_callback' => static fn( $allowed, $meta_key, $post_id ) => current_user_can( 'edit_post', $post_id ), 28 ) ); 29 } 30 add_action( 'init', 'originmark_register_meta' ); 31 32 function originmark_maybe_upgrade() { 33 if ( (int) get_option( 'originmark_db_version', 1 ) >= ORIGINMARK_DB_VERSION ) { 34 return; 35 } 36 37 global $wpdb; 38 39 $result = $wpdb->query( 40 $wpdb->prepare( 41 "INSERT INTO {$wpdb->postmeta} ( post_id, meta_key, meta_value ) 42 SELECT old_meta.post_id, %s, '1' 43 FROM {$wpdb->postmeta} AS old_meta 44 INNER JOIN {$wpdb->posts} AS p ON p.ID = old_meta.post_id 45 LEFT JOIN {$wpdb->postmeta} AS new_meta ON new_meta.post_id = old_meta.post_id AND new_meta.meta_key = %s 46 WHERE old_meta.meta_key = %s 47 AND old_meta.meta_value = '1' 48 AND p.post_type = 'attachment' 49 AND p.post_mime_type LIKE %s 50 AND new_meta.meta_id IS NULL", 51 ORIGINMARK_META_KEY, 52 ORIGINMARK_META_KEY, 53 ORIGINMARK_LEGACY_META_KEY, 54 $wpdb->esc_like( 'image/' ) . '%' 55 ) 56 ); 57 58 if ( false === $result ) { 59 return; 60 } 61 62 if ( function_exists( 'wp_cache_supports' ) && wp_cache_supports( 'flush_group' ) ) { 63 wp_cache_flush_group( 'post_meta' ); 64 } 65 66 update_option( 'originmark_db_version', ORIGINMARK_DB_VERSION, true ); 67 } 68 add_action( 'init', 'originmark_maybe_upgrade', 20 ); 69 70 function originmark_is_ai_generated( int $attachment_id ): bool { 71 return '1' === (string) get_post_meta( $attachment_id, ORIGINMARK_META_KEY, true ) 72 || '1' === get_post_meta( $attachment_id, ORIGINMARK_LEGACY_META_KEY, true ); 73 } 13 74 14 75 … … 182 243 // knihovna medii 183 244 function originmark_add_media_field( $form_fields, $post ) { 184 $is_ai = get_post_meta( $post->ID, '_is_ai_generated', true ); 245 if ( ! wp_attachment_is_image( $post ) ) { 246 return $form_fields; 247 } 248 249 $is_ai = originmark_is_ai_generated( $post->ID ); 185 250 186 251 $form_fields['is_ai_generated'] = array( … … 195 260 </div>', 196 261 $post->ID, 197 checked( $is_ai, '1', false ),262 checked( $is_ai, true, false ), 198 263 esc_html__( 'Mark image as created by AI', 'originmark-ai' ) 199 264 ), … … 203 268 add_filter( 'attachment_fields_to_edit', 'originmark_add_media_field', 10, 2 ); 204 269 205 /** 206 * Uložení z media 207 */ 208 function originmark_save_media_field( $post_id ) { 209 if ( ! isset( $_POST['attachments'][ $post_id ] ) ) { 210 return; 211 } 212 213 $attachment_data = $_POST['attachments'][ $post_id ]; 214 215 if ( isset( $attachment_data['is_ai_generated'] ) ) { 216 update_post_meta( $post_id, '_is_ai_generated', '1' ); 270 function originmark_save_media_field( $post, $attachment_data ) { 271 $attachment_id = (int) ( $post['ID'] ?? $post['post_ID'] ?? 0 ); 272 273 if ( ! $attachment_id || ! wp_attachment_is_image( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) ) { 274 return $post; 275 } 276 277 if ( ! empty( $attachment_data['is_ai_generated'] ) ) { 278 update_post_meta( $attachment_id, ORIGINMARK_META_KEY, '1' ); 217 279 } else { 218 delete_post_meta( $post_id, '_is_ai_generated' ); 219 } 220 } 221 add_action( 'edit_attachment', 'originmark_save_media_field' ); 222 223 /** 224 * Uložení z modálního okna Media Library 225 */ 226 function originmark_save_media_field_ajax( $post, $attachment_data ) { 227 if ( isset( $attachment_data['is_ai_generated'] ) ) { 228 update_post_meta( $post['ID'], '_is_ai_generated', '1' ); 229 } else { 230 delete_post_meta( $post['ID'], '_is_ai_generated' ); 231 } 280 delete_post_meta( $attachment_id, ORIGINMARK_META_KEY ); 281 delete_post_meta( $attachment_id, ORIGINMARK_LEGACY_META_KEY ); 282 } 283 232 284 return $post; 233 285 } 234 add_filter( 'attachment_fields_to_save', 'originmark_save_media_field_ajax', 10, 2 ); 235 236 237 // 4. generovani struktury 238 function originmark_inject_badge( $html ) { 239 $chosen_icon = get_option( 'originmark_selected_icon', 'label_ai_generated_white_transparent.svg' ); 240 $icon_size = get_option( 'originmark_icon_size', '100' ); 241 $icon_pos = get_option( 'originmark_icon_position', 'bottom_right' ); 242 $icon_offset = get_option( 'originmark_icon_offset', '15' ); 243 244 // Obrana do hloubky - i kdyby v DB byla stará/nevalidní hodnota, znovu ji zde ošetříme. 245 $chosen_icon = originmark_sanitize_icon( $chosen_icon ); 246 $icon_size = originmark_sanitize_size( $icon_size ); 247 $icon_pos = originmark_sanitize_position( $icon_pos ); 248 $icon_offset = originmark_sanitize_offset( $icon_offset ); 249 250 $icon_url = plugins_url( $chosen_icon, __FILE__ ); 251 252 $icon_offset_attr = esc_attr( $icon_offset ); 253 $icon_size_attr = esc_attr( $icon_size ); 254 255 switch ( $icon_pos ) { 256 case 'top_left': 257 $position_styles = 'top: ' . $icon_offset_attr . 'px; left: ' . $icon_offset_attr . 'px;'; 258 break; 259 case 'top_right': 260 $position_styles = 'top: ' . $icon_offset_attr . 'px; right: ' . $icon_offset_attr . 'px;'; 261 break; 262 case 'bottom_left': 263 $position_styles = 'bottom: ' . $icon_offset_attr . 'px; left: ' . $icon_offset_attr . 'px;'; 264 break; 265 case 'bottom_right': 266 default: 267 $position_styles = 'bottom: ' . $icon_offset_attr . 'px; right: ' . $icon_offset_attr . 'px;'; 268 break; 269 } 270 271 return '<div class="ai-image-wrapper" style="position: relative; display: block; max-width: 100%; width: fit-content; line-height: 0;">' 272 . $html 273 . '<img class="ai-badge-svg" src="' . esc_url( $icon_url ) . '" alt="' . esc_attr__( 'AI Generated', 'originmark-ai' ) . '" style="position: absolute; ' . $position_styles . ' width: ' . $icon_size_attr . 'px; height: auto; pointer-events: none; z-index: 10; filter: drop-shadow(0px 2px 4px rgba(0,0,0,0.3));" />' 274 . '</div>'; 275 } 276 277 278 // Zobrazení na webu na základě ID obrázku 286 add_filter( 'attachment_fields_to_save', 'originmark_save_media_field', 10, 2 ); 287 288 289 function originmark_get_badge_markup(): string { 290 static $markup = null; 291 292 if ( null !== $markup ) { 293 return $markup; 294 } 295 296 $chosen_icon = originmark_sanitize_icon( get_option( 'originmark_selected_icon', 'label_ai_generated_white_transparent.svg' ) ); 297 $icon_size = originmark_sanitize_size( get_option( 'originmark_icon_size', 100 ) ); 298 $icon_pos = originmark_sanitize_position( get_option( 'originmark_icon_position', 'bottom_right' ) ); 299 $icon_offset = originmark_sanitize_offset( get_option( 'originmark_icon_offset', 15 ) ); 300 301 $position_styles = match ( $icon_pos ) { 302 'top_left' => "top: {$icon_offset}px; left: {$icon_offset}px;", 303 'top_right' => "top: {$icon_offset}px; right: {$icon_offset}px;", 304 'bottom_left' => "bottom: {$icon_offset}px; left: {$icon_offset}px;", 305 default => "bottom: {$icon_offset}px; right: {$icon_offset}px;", 306 }; 307 308 $style = "position: absolute; {$position_styles} width: {$icon_size}px; height: auto; pointer-events: none; z-index: 10; filter: drop-shadow(0px 2px 4px rgba(0,0,0,0.3));"; 309 310 $markup = sprintf( 311 '<img class="ai-badge-svg" src="%s" alt="%s" style="%s" />', 312 esc_url( plugins_url( $chosen_icon, __FILE__ ) ), 313 esc_attr__( 'AI Generated', 'originmark-ai' ), 314 esc_attr( $style ) 315 ); 316 317 return $markup; 318 } 319 320 function originmark_prepare_image_html( string $html ): array { 321 if ( ! preg_match( '/<img\b[^>]*>/i', $html, $img_match, PREG_OFFSET_CAPTURE ) ) { 322 return array( $html, '' ); 323 } 324 325 [ $img_tag, $offset ] = $img_match[0]; 326 $alignment = ''; 327 328 $new_tag = preg_replace_callback( 329 '/\sclass=(["\'])(.*?)\1/i', 330 function ( array $m ) use ( &$alignment ): string { 331 $classes = preg_split( '/\s+/', trim( $m[2] ), -1, PREG_SPLIT_NO_EMPTY ); 332 $found = array_intersect( $classes, array( 'alignleft', 'alignright', 'aligncenter' ) ); 333 334 $alignment = (string) reset( $found ); 335 336 return ' class=' . $m[1] . implode( ' ', array_diff( $classes, $found ) ) . $m[1]; 337 }, 338 $img_tag, 339 1 340 ); 341 342 $new_tag = preg_replace( '/^<img\b/i', '<img data-originmark="1"', $new_tag ); 343 344 return array( substr_replace( $html, $new_tag, $offset, strlen( $img_tag ) ), $alignment ); 345 } 346 347 function originmark_inject_badge( string $html ): string { 348 [ $html, $alignment ] = originmark_prepare_image_html( $html ); 349 350 $wrapper_style = match ( $alignment ) { 351 'aligncenter' => 'display: block; width: fit-content; margin-inline: auto;', 352 'alignleft' => 'display: inline-block; float: left;', 353 'alignright' => 'display: inline-block; float: right;', 354 default => 'display: inline-block; vertical-align: bottom;', 355 }; 356 357 return sprintf( 358 '<span class="%s" style="%s">%s%s</span>', 359 esc_attr( trim( 'ai-image-wrapper ' . $alignment ) ), 360 esc_attr( 'position: relative; max-width: 100%; line-height: 0; ' . $wrapper_style ), 361 $html, 362 originmark_get_badge_markup() 363 ); 364 } 365 366 function originmark_maybe_wrap_image( array $matches ): string { 367 $html = $matches[0]; 368 369 if ( str_contains( $html, 'data-originmark' ) ) { 370 return $html; 371 } 372 373 if ( ! preg_match( '/<img\b[^>]*\bwp-image-(\d+)\b/i', $html, $id_match ) ) { 374 return $html; 375 } 376 377 if ( ! originmark_is_ai_generated( (int) $id_match[1] ) ) { 378 return $html; 379 } 380 381 return originmark_inject_badge( $html ); 382 } 383 279 384 function originmark_filter_content_images( $content ) { 280 if ( is_admin() || is_feed() ) {385 if ( is_admin() || is_feed() || ! is_string( $content ) || ! str_contains( $content, '<img' ) ) { 281 386 return $content; 282 387 } 283 388 284 if ( preg_match_all( '/<img[^>]+>/i', $content, $matches ) ) { 285 foreach ( $matches[0] as $img_html ) { 286 $has_badge = false; 287 288 // Kontrola přes ID obrázku zapsané ve třídě wp-image-XYZ 289 if ( preg_match( '/wp-image-([0-9]+)/', $img_html, $id_match ) ) { 290 $attachment_id = intval( $id_match[1] ); 291 if ( get_post_meta( $attachment_id, '_is_ai_generated', true ) === '1' ) { 292 $has_badge = true; 293 } 294 } 295 296 if ( $has_badge ) { 297 $modified_img = originmark_inject_badge( $img_html ); 298 $content = str_replace( $img_html, $modified_img, $content ); 299 } 300 } 301 } 302 return $content; 389 return preg_replace_callback( 390 '/<picture\b[^>]*>.*?<\/picture>|<img\b[^>]*>/is', 391 'originmark_maybe_wrap_image', 392 $content 393 ) ?? $content; 303 394 } 304 395 add_filter( 'the_content', 'originmark_filter_content_images', 20 ); -
originmark-ai/trunk/readme.txt
r3661053 r3787850 5 5 Requires at least: 5.8 6 6 Tested up to: 7.0 7 Requires PHP: 7.48 Stable tag: 1. 07 Requires PHP: 8.1 8 Stable tag: 1.1.0 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 64 64 == Changelog == 65 65 66 = 1.1.0 = 67 * Fix: an image used more than once in a post no longer gets a duplicate badge. 68 * Fix: badge wrapper no longer breaks <picture> elements, paragraphs or left/right/center alignment. 69 * Fix: the "AI Generated" checkbox is shown only for images. 70 * Security: saving the checkbox now relies only on the core attachment save flow with a capability check. 71 * Change: the AI flag is stored under a prefixed meta key; existing data is migrated automatically. 72 * Requires PHP 8.1 or newer. 73 66 74 = 1.0 = 67 75 * Initial release. … … 69 77 == Upgrade Notice == 70 78 79 = 1.1.0 = 80 Bug fixes for badge rendering. Requires PHP 8.1+. Existing AI flags are migrated automatically. 81 71 82 = 1.0 = 72 83 Initial release.
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)