Changeset 3446683
- Timestamp:
- 01/25/2026 09:30:01 PM (2 months ago)
- Location:
- nextgen-gallery-geo/trunk
- Files:
-
- 3 edited
-
functions.php (modified) (21 diffs)
-
plugin.php (modified) (1 diff)
-
readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
nextgen-gallery-geo/trunk/functions.php
r3445193 r3446683 16 16 * @since 2.1.3 Amended functions: geo2_maps_enqueue_scripts(), geo2_maps_handle_proxy_request(), geo2_maps_coordinates(), geo2_maps_exif(), geo2_maps_exif_camera(), geo2_maps_shortcodes(). 17 17 * @since 2.1.4 Amended functions: geo2_maps_coordinates(), geo2_maps_exif(), geo2_maps_exif_camera(), geo2_maps_check_content(), geo2_maps_data(), geo2_maps_data_single(), geo2_maps_data_worldmap(), geo2_maps_enqueue_scripts(). New function: geo2_maps_is_webp(), geo2_maps_get_webp_metadata(), geo2_maps_webp_search_xmp(), geo2_maps_webp_search_xmp_gps(), geo2_maps_webp_search_xmp_exif_info(), geo2_maps_eval_fraction(), geo2_maps_is_valid_tiff_blob(), geo2_maps_parse_exif_blob(). 18 * @since 2.1.5 Amended functions: geo2_maps_get_webp_metadata(), geo2_maps_webp_search_xmp(), geo2_maps_webp_search_xmp_gps(), geo2_maps_webp_search_xmp_exif_info(), geo2_maps_parse_exif_blob(). 18 19 * @copyright Copyright (c) 2023, Pawel Block 19 20 * @link http://www.geo2maps.plus … … 755 756 } 756 757 757 // === Configuration ===758 if ( ! defined( 'GEO2_MAPS_MAX_BYTES' ) ) {759 define( 'GEO2_MAPS_MAX_BYTES', 32 * 1024 * 1024 );760 } // 32MB safety cap761 if ( ! defined( 'GEO2_MAPS_CHUNK_SIZE' ) ) {762 define( 'GEO2_MAPS_CHUNK_SIZE', 1024 * 1024 );763 } // 1MB764 765 758 /** 766 759 * Detect if file is WebP by checking file extension and magic bytes … … 861 854 * 862 855 * @since 2.1.4 856 * @since 2.1.5 Corrected to extract EXIF data if no XMP is found also for GPS. 863 857 * 864 858 * @param string $file_path The path to the WebP file. … … 966 960 } elseif ( $xmp_streamed !== false && $sections === 'IFD0&EXIF' ) { 967 961 $xmp_search_data = geo2_maps_webp_search_xmp_exif_info( $xmp_streamed ); 968 $xmp_data = array_replace( $xmp_data, $xmp_search_data );962 $xmp_data = array_replace( $xmp_data, $xmp_search_data ); 969 963 } 970 964 // Reset for the main scan loop. … … 982 976 } 983 977 } 978 979 // Check if XMP data is incomplete for 'GPS' section. 980 if ( $sections === 'GPS' && ( empty( $xmp_data ) || ! isset( $xmp_data['GPSLatitude'] ) ) ) { 981 $is_xmp_incomplete = true; 982 } 983 984 984 // Scan chunks for EXIF if no XMP or XMP did not contain search data. 985 985 if ( $has_exif && ( ! $xmp_data || $is_xmp_incomplete ) ) { … … 1013 1013 $exif_data = array(); 1014 1014 } 1015 $result = array_replace( $exif_data, $xmp_data ); 1015 1016 $result = $exif_data; 1017 foreach ( $xmp_data as $key => $value ) { 1018 if ( $value !== null && $value !== '' ) { 1019 $result[ $key ] = $value; 1020 } 1021 } 1016 1022 // Cache result (we cache only positive arrays to avoid caching failures). 1017 1023 if ( $caching_enabled && $result !== false ) { … … 1027 1033 } 1028 1034 1029 1030 1035 /** 1031 1036 * Searches for an XMP metadata block within a file stream. 1032 1037 * 1033 * This function reads a file stream in chunks to efficiently find the1034 * start and end tags of an XMP block ('<x:xmpmeta>' and '</x:xmpmeta>').1038 * This function parses the RIFF structure of the WebP file to locate 1039 * the 'XMP ' chunk and extract its content. 1035 1040 * 1036 1041 * @since 2.1.4 … … 1041 1046 */ 1042 1047 function geo2_maps_webp_search_xmp( $fp ) { 1043 if ( ! is_resource( $fp ) ) { 1048 rewind( $fp ); 1049 $header = fread( $fp, 12 ); 1050 if ( strlen( $header ) < 12 ) { 1044 1051 return false; 1045 1052 } 1046 $buffer = ''; 1047 $read = 0; 1048 $max = GEO2_MAPS_MAX_BYTES; 1049 $chunk = GEO2_MAPS_CHUNK_SIZE; 1050 $tail_keep = 4096; 1051 1052 while ( ! feof( $fp ) && $read < $max ) { 1053 $data = fread( $fp, $chunk ); 1054 if ( $data === false || $data === '' ) { 1053 1054 $riff = substr( $header, 0, 4 ); 1055 $webp = substr( $header, 8, 4 ); 1056 1057 if ( $riff !== 'RIFF' || $webp !== 'WEBP' ) { 1058 return false; 1059 } 1060 1061 $xmp_data = ''; 1062 1063 while ( ! feof( $fp ) ) { 1064 $chunk_header = fread( $fp, 8 ); 1065 if ( strlen( $chunk_header ) < 8 ) { 1055 1066 break; 1056 1067 } 1057 $read += strlen( $data ); 1058 $buffer .= $data; 1059 1060 $start = strpos( $buffer, '<x:xmpmeta' ); 1061 if ( $start !== false ) { 1062 $end = strpos( $buffer, '</x:xmpmeta>', $start ); 1063 if ( $end === false ) { 1064 // keep from start to avoid unbounded growth. 1065 $buffer = substr( $buffer, $start ); 1066 continue; 1067 } 1068 $end += strlen( '</x:xmpmeta>' ); 1069 return substr( $buffer, $start, $end - $start ); 1070 } 1071 1072 // keep only tail to avoid memory growth. 1073 if ( strlen( $buffer ) > $tail_keep ) { 1074 $buffer = substr( $buffer, -$tail_keep ); 1075 } 1076 } 1077 1078 return false; 1068 1069 $chunk_tag = substr( $chunk_header, 0, 4 ); 1070 // WebP chunk size is little-endian 32-bit integer. 1071 $chunk_size = unpack( 'V', substr( $chunk_header, 4, 4 ) )[1]; 1072 1073 if ( $chunk_tag === 'XMP ' ) { 1074 // Found XMP chunk, read the full content based on chunk size. 1075 $read = 0; 1076 while ( $read < $chunk_size && ! feof( $fp ) ) { 1077 $buffer = fread( $fp, min( 8192, $chunk_size - $read ) ); 1078 if ( $buffer === false ) { 1079 break; 1080 } 1081 $xmp_data .= $buffer; 1082 $read += strlen( $buffer ); 1083 } 1084 // Padding byte if size is odd (RIFF standard). 1085 if ( $chunk_size % 2 !== 0 ) { 1086 fseek( $fp, 1, SEEK_CUR ); 1087 } 1088 continue; 1089 } 1090 1091 // Skip chunk data. 1092 fseek( $fp, $chunk_size, SEEK_CUR ); 1093 1094 // Padding byte if size is odd (RIFF standard). 1095 if ( $chunk_size % 2 !== 0 ) { 1096 fseek( $fp, 1, SEEK_CUR ); 1097 } 1098 } 1099 1100 return ! empty( $xmp_data ) ? $xmp_data : false; 1079 1101 } 1080 1102 … … 1085 1107 * 1086 1108 * @since 2.1.4 1109 * @since 2.1.5 Added support for WebD files create with older Adobe XMP Core 7.0 ( metadata saved in "" not in <> tags). 1087 1110 * 1088 1111 * @see geo2_maps_get_webp_metadata() … … 1097 1120 $lon_ref = 'E'; 1098 1121 1099 if ( preg_match( '/<exif:GPSLatitude>([^<]+)</', $xmp, $m ) ) { 1100 $lat = trim( $m[1] ); 1122 $result = array(); 1123 1124 if ( preg_match( '/(?:<exif:GPSLatitude>([^<]+)<\/|exif:GPSLatitude="([^"]+)")/i', $xmp, $m ) ) { 1125 $lat = trim( ! empty( $m[1] ) ? $m[1] : $m[2] ); 1101 1126 $lat_ref = ( stripos( $lat, 'S' ) !== false ) ? 'S' : 'N'; 1102 1127 } 1103 if ( preg_match( '/ <exif:GPSLongitude>([^<]+)</', $xmp, $m ) ) {1104 $lon = trim( $m[1] );1128 if ( preg_match( '/(?:<exif:GPSLongitude>([^<]+)<\/|exif:GPSLongitude="([^"]+)")/i', $xmp, $m ) ) { 1129 $lon = trim( ! empty( $m[1] ) ? $m[1] : $m[2] ); 1105 1130 $lon_ref = ( stripos( $lon, 'W' ) !== false ) ? 'W' : 'E'; 1106 1131 } … … 1127 1152 } 1128 1153 1129 return $result? $result : false;1154 return ! empty( $result ) ? $result : false; 1130 1155 } 1131 1156 … … 1137 1162 * 1138 1163 * @since 2.1.4 1164 * @since 2.1.5 Added support for WebD files create with older Adobe XMP Core 7.0 ( metadata saved in "" not in <> tags). 1139 1165 * 1140 1166 * @see geo2_maps_get_webp_metadata() … … 1146 1172 $data = array(); 1147 1173 1148 $data = array();1149 1150 1174 // --- 1. TIMESTAMP (created_timestamp) --- 1151 1175 $data['created_timestamp'] = ''; 1152 1176 // Search for common date tags (DateTimeOriginal, CreateDate, DateCreated, ModifyDate). 1153 if ( preg_match( '/ <(?:exif:DateTimeOriginal|xmp:CreateDate|photoshop:DateCreated|xmp:ModifyDate)>([^<]+)<\//i', $xmp_raw, $m ) ) {1154 $raw_date = trim( $m[1] );1177 if ( preg_match( '/(?:<(?:exif:DateTimeOriginal|xmp:CreateDate|photoshop:DateCreated|xmp:ModifyDate)>([^<]+)<\/|(?:exif:DateTimeOriginal|xmp:CreateDate|photoshop:DateCreated|xmp:ModifyDate)="([^"]+)")/i', $xmp_raw, $m ) ) { 1178 $raw_date = trim( ! empty( $m[1] ) ? $m[1] : $m[2] ); 1155 1179 // Clean date string. 1156 1180 $clean_date = str_replace( 'T', ' ', $raw_date ); … … 1164 1188 1165 1189 // Extract Make (tiff:Make or exif:Make). 1166 if ( preg_match( '/ <(?:tiff|exif):Make>([^<]+)<\//i', $xmp_raw, $m ) ) {1167 $make = trim( $m[1] );1190 if ( preg_match( '/(?:<(?:tiff|exif):Make>([^<]+)<\/|(?:tiff|exif):Make="([^"]+)")/i', $xmp_raw, $m ) ) { 1191 $make = trim( ! empty( $m[1] ) ? $m[1] : $m[2] ); 1168 1192 } 1169 1193 // Extract Model (tiff:Model or exif:Model). 1170 if ( preg_match( '/ <(?:tiff|exif):Model>([^<]+)<\//i', $xmp_raw, $m ) ) {1171 $model = trim( $m[1] );1194 if ( preg_match( '/(?:<(?:tiff|exif):Model>([^<]+)<\/|(?:tiff|exif):Model="([^"]+)")/i', $xmp_raw, $m ) ) { 1195 $model = trim( ! empty( $m[1] ) ? $m[1] : $m[2] ); 1172 1196 } 1173 1197 … … 1178 1202 // Lens Make: aux:LensMake or exifex:LensMake. 1179 1203 $data['lens_make'] = ''; 1180 if ( preg_match( '/ <(?:aux|exifex):LensMake>([^<]+)<\//i', $xmp_raw, $m ) ) {1181 $data['lens_make'] = trim( $m[1] );1204 if ( preg_match( '/(?:<(?:aux|exifex):LensMake>([^<]+)<\/|(?:aux|exifex):LensMake="([^"]+)")/i', $xmp_raw, $m ) ) { 1205 $data['lens_make'] = trim( ! empty( $m[1] ) ? $m[1] : $m[2] ); 1182 1206 } 1183 1207 1184 1208 // Lens Model: aux:LensModel, exifex:LensModel, or aux:lens. 1185 1209 $data['lens_model'] = ''; 1186 if ( preg_match( '/ <(?:aux|exifex):LensModel>([^<]+)<\//i', $xmp_raw, $m ) ) {1187 $data['lens_model'] = trim( $m[1] );1188 } elseif ( preg_match( '/ <aux:lens>([^<]+)<\//i', $xmp_raw, $m ) ) {1210 if ( preg_match( '/(?:<(?:aux|exifex):LensModel>([^<]+)<\/|(?:aux|exifex):LensModel="([^"]+)")/i', $xmp_raw, $m ) ) { 1211 $data['lens_model'] = trim( ! empty( $m[1] ) ? $m[1] : $m[2] ); 1212 } elseif ( preg_match( '/(?:<aux:lens>([^<]+)<\/|aux:Lens="([^"]+)")/i', $xmp_raw, $m ) ) { 1189 1213 // Specific tag used in your sample XML. 1190 $data['lens_model'] = trim( $m[1] );1214 $data['lens_model'] = trim( ! empty( $m[1] ) ? $m[1] : $m[2] ); 1191 1215 } 1192 1216 1193 1217 // --- 4. APERTURE (aperture) --- 1194 1218 $data['aperture'] = ''; 1195 if ( preg_match( '/ <exif:FNumber>([^<]+)<\//i', $xmp_raw, $m ) ) {1196 $val = geo2_maps_eval_fraction( $m[1] );1219 if ( preg_match( '/(?:<exif:FNumber>([^<]+)<\/|exif:FNumber="([^"]+)")/i', $xmp_raw, $m ) ) { 1220 $val = geo2_maps_eval_fraction( ! empty( $m[1] ) ? $m[1] : $m[2] ); 1197 1221 if ( $val > 0 ) { 1198 1222 $data['aperture'] = 'f/' . round( $val, 1 ); … … 1202 1226 // --- 5. FOCAL LENGTH (focal_length) --- 1203 1227 $data['focal_length'] = ''; 1204 if ( preg_match( '/ <exif:FocalLength>([^<]+)<\//i', $xmp_raw, $m ) ) {1205 $val = geo2_maps_eval_fraction( $m[1] );1228 if ( preg_match( '/(?:<exif:FocalLength>([^<]+)<\/|exif:FocalLength="([^"]+)")/i', $xmp_raw, $m ) ) { 1229 $val = geo2_maps_eval_fraction( ! empty( $m[1] ) ? $m[1] : $m[2] ); 1206 1230 if ( $val > 0 ) { 1207 1231 $data['focal_length'] = round( $val, 1 ) . 'mm'; … … 1212 1236 $data['iso'] = ''; 1213 1237 // ISO is often nested in an rdf:seq list, this regex extracts the value. 1214 if ( preg_match( '/ <exif:ISOSpeedRatings>.*?<rdf:li>([^<]+)<\/rdf:li>/is', $xmp_raw, $m ) ) {1215 $data['iso'] = trim( $m[1] );1238 if ( preg_match( '/(?:<exif:ISOSpeedRatings>.*?<rdf:li>([^<]+)<\/rdf:li>|exif:ISOSpeedRatings="([^"]+)")/is', $xmp_raw, $m ) ) { 1239 $data['iso'] = trim( ! empty( $m[1] ) ? $m[1] : $m[2] ); 1216 1240 } 1217 1241 1218 1242 // --- 7. SHUTTER SPEED (shutter_speed) --- 1219 1243 $data['shutter_speed'] = ''; 1220 if ( preg_match( '/ <exif:ExposureTime>([^<]+)<\//i', $xmp_raw, $m ) ) {1221 $data['shutter_speed'] = trim( $m[1] );1244 if ( preg_match( '/(?:<exif:ExposureTime>([^<]+)<\/|exif:ExposureTime="([^"]+)")/i', $xmp_raw, $m ) ) { 1245 $data['shutter_speed'] = trim( ! empty( $m[1] ) ? $m[1] : $m[2] ); 1222 1246 } 1223 1247 return $data; … … 1253 1277 * 1254 1278 * @since 2.1.4 1279 * @since 2.1.5 Critical logic mistake corrected. 1255 1280 * 1256 1281 * @see geo2_maps_get_webp_metadata() … … 1274 1299 } 1275 1300 $magic = substr( $blob, 2, 2 ); 1276 if ( $magic === "\x2A\x00" || $magic === "\x00\x2A" ) {1301 if ( $magic !== "\x2A\x00" && $magic !== "\x00\x2A" ) { 1277 1302 return false; 1278 1303 } … … 1508 1533 if ( $sections === 'IFD0&EXIF' ) { 1509 1534 $info = array( 1510 'created_timestamp' => ' ',1511 'camera' => ' ',1535 'created_timestamp' => 'g', 1536 'camera' => 'r', 1512 1537 'lens_make' => '', 1513 1538 'lens_model' => '', … … 1517 1542 'shutter_speed' => '', 1518 1543 ); 1544 1519 1545 // DateTimeOriginal (0x9003) in EXIF IFD, fallback to DateTime (0x0132) in IFD0. 1520 1546 if ( isset( $exif_tags[0x9003] ) && is_string( $exif_tags[0x9003] ) ) { -
nextgen-gallery-geo/trunk/plugin.php
r3445206 r3446683 25 25 * Plugin URI: https://wordpress.org/plugins/nextgen-gallery-geo/ 26 26 * Description: Geo2 Maps Add-on for NextGEN Gallery is a flexible plugin, displaying beautiful maps with your photos by using EXIF data or geocoding. 27 * Version: 2.1. 427 * Version: 2.1.5 28 28 29 29 * Author URI: http://www.geo2maps.plus -
nextgen-gallery-geo/trunk/readme.txt
r3445210 r3446683 6 6 Tested up to: 6.9 7 7 Requires PHP: 7.2.0 8 Stable tag: 2.1. 48 Stable tag: 2.1.5 9 9 License: GPLv2 or later 10 10 License URI: http://www.gnu.org/licenses/gpl-2.0.html … … 181 181 == Changelog == 182 182 183 = V2.1.4 - 07.12.2025 = 183 = V2.1.5 - 25.01.2026 = 184 185 * Update: Support added for WebD files created with an older library Adobe XMP Core 7.0 186 187 * Bugfix: Mistake corrected which prevented extracting WebD metadata from EXIP chunk. 188 189 = V2.1.4 - 21.01.2026 = 184 190 185 191 * NEW: Added support for reading GPS and EXIF metadata from WebP images.
Note: See TracChangeset
for help on using the changeset viewer.