Changeset 3467539
- Timestamp:
- 02/23/2026 10:23:08 AM (6 weeks ago)
- Location:
- cdntr
- Files:
-
- 8 edited
- 1 copied
-
tags/1.2.5 (copied) (copied from cdntr/trunk)
-
tags/1.2.5/cdntr.php (modified) (2 diffs)
-
tags/1.2.5/inc/cdntr.class.php (modified) (7 diffs)
-
tags/1.2.5/inc/cdntr_engine.class.php (modified) (8 diffs)
-
tags/1.2.5/readme.txt (modified) (4 diffs)
-
trunk/cdntr.php (modified) (2 diffs)
-
trunk/inc/cdntr.class.php (modified) (7 diffs)
-
trunk/inc/cdntr_engine.class.php (modified) (8 diffs)
-
trunk/readme.txt (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
cdntr/tags/1.2.5/cdntr.php
r3131497 r3467539 6 6 Author URI: https://cdn.com.tr 7 7 License: GPLv2 or later 8 Version: 1.2. 28 Version: 1.2.5 9 9 */ 10 10 … … 16 16 17 17 // constants 18 define( 'CDNTR_VERSION', '1. 0' );18 define( 'CDNTR_VERSION', '1.2.5' ); 19 19 define( 'CDNTR_MIN_PHP', '5.6' ); 20 20 define( 'CDNTR_MIN_WP', '5.1' ); -
cdntr/tags/1.2.5/inc/cdntr.class.php
r3130906 r3467539 41 41 add_action( 'init', array( __CLASS__, 'register_textdomain' ) ); 42 42 add_action( 'init', array( __CLASS__, 'check_meta_valid' ) ); 43 add_action( 'save_post', array( __CLASS__, 'maybe_purge_post_paths' ), 20, 3 ); 44 add_action( 'deleted_post', array( __CLASS__, 'purge_deleted_post_paths' ), 20, 1 ); 45 add_action( 'created_term', array( __CLASS__, 'maybe_purge_term_paths' ), 20, 3 ); 46 add_action( 'edited_term', array( __CLASS__, 'maybe_purge_term_paths' ), 20, 3 ); 47 add_action( 'delete_term', array( __CLASS__, 'maybe_purge_deleted_term_paths' ), 20, 5 ); 48 add_action( 'customize_save_after', array( __CLASS__, 'maybe_purge_sitewide' ), 20, 1 ); 49 add_action( 'switch_theme', array( __CLASS__, 'maybe_purge_sitewide' ), 20, 3 ); 50 add_action( 'cdntr_run_coalesced_purge', array( __CLASS__, 'run_coalesced_purge' ) ); 43 51 44 52 // multisite hook … … 356 364 'cdntr_is_purge_all_button' => '', 357 365 'cdntr_api_password' => '', 366 'cdntr_purge_mode' => 'async', 367 'cdntr_purge_coalesce_window'=> 3, 368 'cdntr_purge_telemetry' => '1', 358 369 ); 359 370 // cdntr_account_id cdntr_api_user cdntr_api_password … … 731 742 ); 732 743 744 $telemetry_status = 'error'; 745 733 746 // check if API call failed 734 747 if ( is_wp_error( $response ) ) { … … 743 756 744 757 if ( $response_status_code === 200 ) { 758 $telemetry_status = 'ok'; 745 759 $response = array( 746 760 'wrapper' => '<div class="notice notice-success is-dismissible"><p><strong>%s</strong></p></div>', … … 779 793 } 780 794 795 self::record_purge_telemetry( 'all_sync', $telemetry_status ); 796 781 797 return $response; 798 } 799 800 /** 801 * purge CDN cache asynchronously (fire-and-forget) 802 * 803 * @since 2.1.0 804 * @change 2.1.0 805 * 806 * @return array 807 */ 808 public static function purge_cdn_cache_async() { 809 810 $auth = base64_encode( CDNTR_Engine::$settings['cdntr_api_user'] . ':' . CDNTR_Engine::$settings['cdntr_api_password'] ); 811 812 wp_remote_post( 813 'https://cdn.com.tr/api/purgeAll', 814 array( 815 'timeout' => 1, 816 'httpversion' => '1.1', 817 'blocking' => false, 818 'headers' => array( 819 'Authorization' => 'Basic ' . $auth, 820 'Content-Type' => 'application/json', 821 ), 822 ) 823 ); 824 825 self::record_purge_telemetry( 'all_async', 'queued' ); 826 827 return array( 828 'ok' => true, 829 'message' => 'Purge queued.', 830 ); 831 } 832 833 /** 834 * Purge specific URL paths from CDN. 835 * 836 * @since 2.0.9 837 * @change 2.0.9 838 * 839 * @param array $paths URL paths (e.g. /product/foo/) 840 * @return array 841 */ 842 public static function purge_cdn_paths( $paths ) { 843 844 $paths = self::normalize_purge_paths( $paths ); 845 846 if ( empty( $paths ) ) { 847 self::record_purge_telemetry( 'paths_sync', 'skipped', array( 'paths' => 0 ) ); 848 return array( 849 'ok' => false, 850 'message' => 'No valid purge paths.', 851 ); 852 } 853 854 $auth = base64_encode( CDNTR_Engine::$settings['cdntr_api_user'] . ':' . CDNTR_Engine::$settings['cdntr_api_password'] ); 855 $response = wp_remote_post( 856 'https://cdn.com.tr/api/purge', 857 array( 858 'timeout' => 15, 859 'httpversion' => '1.1', 860 'headers' => array( 861 'Authorization' => 'Basic ' . $auth, 862 'Content-Type' => 'application/json', 863 ), 864 'body' => wp_json_encode( array( 'paths' => $paths ) ), 865 ) 866 ); 867 868 if ( is_wp_error( $response ) ) { 869 self::record_purge_telemetry( 'paths_sync', 'error', array( 'paths' => count( $paths ) ) ); 870 return array( 871 'ok' => false, 872 'message' => $response->get_error_message(), 873 ); 874 } 875 876 $status = (int) wp_remote_retrieve_response_code( $response ); 877 self::record_purge_telemetry( 878 'paths_sync', 879 ( $status >= 200 && $status < 300 ) ? 'ok' : 'error', 880 array( 881 'status' => $status, 882 'paths' => count( $paths ), 883 ) 884 ); 885 return array( 886 'ok' => ( $status >= 200 && $status < 300 ), 887 'status' => $status, 888 'message' => (string) wp_remote_retrieve_body( $response ), 889 ); 890 } 891 892 /** 893 * Purge specific URL paths asynchronously (fire-and-forget). 894 * 895 * @since 2.1.0 896 * @change 2.1.0 897 * 898 * @param array $paths 899 * @return array 900 */ 901 public static function purge_cdn_paths_async( $paths ) { 902 903 $paths = self::normalize_purge_paths( $paths ); 904 if ( empty( $paths ) ) { 905 self::record_purge_telemetry( 'paths_async', 'skipped', array( 'paths' => 0 ) ); 906 return array( 907 'ok' => false, 908 'message' => 'No valid purge paths.', 909 ); 910 } 911 912 $auth = base64_encode( CDNTR_Engine::$settings['cdntr_api_user'] . ':' . CDNTR_Engine::$settings['cdntr_api_password'] ); 913 wp_remote_post( 914 'https://cdn.com.tr/api/purge', 915 array( 916 'timeout' => 1, 917 'httpversion' => '1.1', 918 'blocking' => false, 919 'headers' => array( 920 'Authorization' => 'Basic ' . $auth, 921 'Content-Type' => 'application/json', 922 ), 923 'body' => wp_json_encode( array( 'paths' => $paths ) ), 924 ) 925 ); 926 927 self::record_purge_telemetry( 'paths_async', 'queued', array( 'paths' => count( $paths ) ) ); 928 929 return array( 930 'ok' => true, 931 'message' => 'Purge queued.', 932 ); 933 } 934 935 /** 936 * Purge only affected URLs when content changes. 937 * 938 * @since 2.0.9 939 * @change 2.0.9 940 */ 941 public static function maybe_purge_post_paths( $post_id, $post, $update ) { 942 943 if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) { 944 return; 945 } 946 947 if ( empty( CDNTR_Engine::$settings['cdntr_api_user'] ) || empty( CDNTR_Engine::$settings['cdntr_api_password'] ) ) { 948 return; 949 } 950 951 if ( ! $post instanceof \WP_Post ) { 952 $post = get_post( $post_id ); 953 if ( ! $post ) { 954 return; 955 } 956 } 957 958 if ( $post->post_status !== 'publish' && $post->post_status !== 'private' ) { 959 return; 960 } 961 962 $paths = array( 963 '/', 964 wp_parse_url( get_permalink( $post_id ), PHP_URL_PATH ), 965 ); 966 967 if ( $post->post_type === 'product' ) { 968 $shop_page_id = function_exists( 'wc_get_page_id' ) ? wc_get_page_id( 'shop' ) : 0; 969 if ( $shop_page_id > 0 ) { 970 $paths[] = wp_parse_url( get_permalink( $shop_page_id ), PHP_URL_PATH ); 971 } 972 973 $terms = get_the_terms( $post_id, 'product_cat' ); 974 if ( is_array( $terms ) ) { 975 foreach ( $terms as $term ) { 976 $term_link = get_term_link( $term ); 977 if ( ! is_wp_error( $term_link ) ) { 978 $paths[] = wp_parse_url( $term_link, PHP_URL_PATH ); 979 } 980 } 981 } 982 } 983 984 self::enqueue_purge_paths( $paths ); 985 } 986 987 /** 988 * Purge homepage after post deletion to avoid stale listings. 989 * 990 * @since 2.0.9 991 * @change 2.0.9 992 */ 993 public static function purge_deleted_post_paths( $post_id ) { 994 995 if ( empty( CDNTR_Engine::$settings['cdntr_api_user'] ) || empty( CDNTR_Engine::$settings['cdntr_api_password'] ) ) { 996 return; 997 } 998 999 self::enqueue_purge_paths( array( '/' ) ); 1000 } 1001 1002 /** 1003 * Purge related paths when selected taxonomies are changed. 1004 * 1005 * @since 2.1.0 1006 * @change 2.1.0 1007 */ 1008 public static function maybe_purge_term_paths( $term_id, $tt_id, $taxonomy ) { 1009 1010 if ( empty( CDNTR_Engine::$settings['cdntr_api_user'] ) || empty( CDNTR_Engine::$settings['cdntr_api_password'] ) ) { 1011 return; 1012 } 1013 1014 $allowed_taxonomies = (array) apply_filters( 'cdntr_purge_taxonomies', array( 'product_cat' ) ); 1015 if ( empty( $taxonomy ) || ! in_array( $taxonomy, $allowed_taxonomies, true ) ) { 1016 return; 1017 } 1018 1019 if ( self::is_purge_throttled( 'term_' . $taxonomy, 10 ) ) { 1020 return; 1021 } 1022 1023 $paths = array( '/' ); 1024 $term = get_term( (int) $term_id, $taxonomy ); 1025 if ( $term && ! is_wp_error( $term ) ) { 1026 $term_link = get_term_link( $term ); 1027 if ( ! is_wp_error( $term_link ) ) { 1028 $paths[] = wp_parse_url( $term_link, PHP_URL_PATH ); 1029 } 1030 } 1031 1032 $shop_page_id = function_exists( 'wc_get_page_id' ) ? wc_get_page_id( 'shop' ) : 0; 1033 if ( $shop_page_id > 0 ) { 1034 $paths[] = wp_parse_url( get_permalink( $shop_page_id ), PHP_URL_PATH ); 1035 } 1036 1037 self::enqueue_purge_paths( $paths ); 1038 } 1039 1040 /** 1041 * Purge related paths when selected taxonomies are deleted. 1042 * 1043 * @since 2.1.0 1044 * @change 2.1.0 1045 */ 1046 public static function maybe_purge_deleted_term_paths( $term, $tt_id, $taxonomy, $deleted_term, $object_ids ) { 1047 1048 if ( empty( CDNTR_Engine::$settings['cdntr_api_user'] ) || empty( CDNTR_Engine::$settings['cdntr_api_password'] ) ) { 1049 return; 1050 } 1051 1052 $allowed_taxonomies = (array) apply_filters( 'cdntr_purge_taxonomies', array( 'product_cat' ) ); 1053 if ( empty( $taxonomy ) || ! in_array( $taxonomy, $allowed_taxonomies, true ) ) { 1054 return; 1055 } 1056 1057 if ( self::is_purge_throttled( 'term_delete_' . $taxonomy, 10 ) ) { 1058 return; 1059 } 1060 1061 $paths = array( '/' ); 1062 $shop_page_id = function_exists( 'wc_get_page_id' ) ? wc_get_page_id( 'shop' ) : 0; 1063 if ( $shop_page_id > 0 ) { 1064 $paths[] = wp_parse_url( get_permalink( $shop_page_id ), PHP_URL_PATH ); 1065 } 1066 1067 self::enqueue_purge_paths( $paths ); 1068 } 1069 1070 /** 1071 * Purge whole site for theme/customizer style changes. 1072 * 1073 * @since 2.1.0 1074 * @change 2.1.0 1075 */ 1076 public static function maybe_purge_sitewide() { 1077 1078 if ( empty( CDNTR_Engine::$settings['cdntr_api_user'] ) || empty( CDNTR_Engine::$settings['cdntr_api_password'] ) ) { 1079 return; 1080 } 1081 1082 if ( self::is_purge_throttled( 'sitewide', 20 ) ) { 1083 return; 1084 } 1085 1086 self::enqueue_purge_all(); 1087 } 1088 1089 /** 1090 * Normalize purge paths to a clean unique path list. 1091 * 1092 * @since 2.0.9 1093 * @change 2.0.9 1094 * 1095 * @param array $paths 1096 * @return array 1097 */ 1098 private static function normalize_purge_paths( $paths ) { 1099 1100 if ( ! is_array( $paths ) ) { 1101 $paths = array(); 1102 } 1103 1104 $normalized = array(); 1105 foreach ( $paths as $path ) { 1106 if ( ! is_string( $path ) || $path === '' ) { 1107 continue; 1108 } 1109 1110 // Strip control chars and trim to avoid malformed payloads. 1111 $path = trim( preg_replace( '/[\x00-\x1F\x7F]/', '', $path ) ); 1112 if ( $path === '' || strlen( $path ) > 2048 ) { 1113 continue; 1114 } 1115 1116 $clean = wp_parse_url( $path, PHP_URL_PATH ); 1117 if ( ! is_string( $clean ) || $clean === '' ) { 1118 continue; 1119 } 1120 1121 // Block traversal-like paths from reaching purge API. 1122 if ( strpos( $clean, '..' ) !== false ) { 1123 continue; 1124 } 1125 1126 if ( strpos( $clean, '/' ) !== 0 ) { 1127 $clean = '/' . $clean; 1128 } 1129 1130 $normalized[] = $clean; 1131 } 1132 1133 return array_values( array_unique( $normalized ) ); 1134 } 1135 1136 /** 1137 * Basic transient lock to avoid duplicate purge bursts. 1138 * 1139 * @since 2.1.0 1140 * @change 2.1.0 1141 */ 1142 private static function is_purge_throttled( $scope, $ttl = 10 ) { 1143 1144 $scope = sanitize_key( (string) $scope ); 1145 $ttl = absint( $ttl ); 1146 if ( $scope === '' || $ttl < 1 ) { 1147 return false; 1148 } 1149 1150 $lock_key = 'cdntr_purge_lock_' . $scope; 1151 if ( get_transient( $lock_key ) ) { 1152 return true; 1153 } 1154 1155 set_transient( $lock_key, 1, $ttl ); 1156 return false; 1157 } 1158 1159 /** 1160 * Queue targeted purge paths in a short coalescing window. 1161 * 1162 * @since 2.1.0 1163 * @change 2.1.0 1164 * 1165 * @param array $paths 1166 */ 1167 private static function enqueue_purge_paths( $paths ) { 1168 1169 if ( empty( CDNTR_Engine::$settings['cdntr_api_user'] ) || empty( CDNTR_Engine::$settings['cdntr_api_password'] ) ) { 1170 return; 1171 } 1172 1173 $paths = self::normalize_purge_paths( $paths ); 1174 if ( empty( $paths ) ) { 1175 return; 1176 } 1177 1178 $queue_key = self::get_purge_queue_transient_name( 'paths' ); 1179 $queued_paths = get_transient( $queue_key ); 1180 if ( ! is_array( $queued_paths ) ) { 1181 $queued_paths = array(); 1182 } 1183 1184 $queued_paths = array_values( array_unique( array_merge( $queued_paths, $paths ) ) ); 1185 set_transient( $queue_key, $queued_paths, 300 ); 1186 1187 self::record_purge_telemetry( 'paths_queue', 'queued', array( 'paths' => count( $queued_paths ) ) ); 1188 1189 if ( ! wp_next_scheduled( 'cdntr_run_coalesced_purge' ) ) { 1190 wp_schedule_single_event( time() + self::get_purge_coalesce_window(), 'cdntr_run_coalesced_purge' ); 1191 } 1192 } 1193 1194 /** 1195 * Queue purge-all requests in a short coalescing window. 1196 * 1197 * @since 2.1.0 1198 * @change 2.1.0 1199 */ 1200 private static function enqueue_purge_all() { 1201 1202 if ( empty( CDNTR_Engine::$settings['cdntr_api_user'] ) || empty( CDNTR_Engine::$settings['cdntr_api_password'] ) ) { 1203 return; 1204 } 1205 1206 set_transient( self::get_purge_queue_transient_name( 'all' ), 1, 300 ); 1207 self::record_purge_telemetry( 'all_queue', 'queued' ); 1208 1209 if ( ! wp_next_scheduled( 'cdntr_run_coalesced_purge' ) ) { 1210 wp_schedule_single_event( time() + self::get_purge_coalesce_window(), 'cdntr_run_coalesced_purge' ); 1211 } 1212 } 1213 1214 /** 1215 * Execute queued purge operations after coalescing window. 1216 * 1217 * @since 2.1.0 1218 * @change 2.1.0 1219 */ 1220 public static function run_coalesced_purge() { 1221 1222 if ( empty( CDNTR_Engine::$settings['cdntr_api_user'] ) || empty( CDNTR_Engine::$settings['cdntr_api_password'] ) ) { 1223 return; 1224 } 1225 1226 $purge_all_key = self::get_purge_queue_transient_name( 'all' ); 1227 $paths_key = self::get_purge_queue_transient_name( 'paths' ); 1228 1229 $purge_all = (bool) get_transient( $purge_all_key ); 1230 $paths = get_transient( $paths_key ); 1231 $paths = is_array( $paths ) ? $paths : array(); 1232 1233 delete_transient( $purge_all_key ); 1234 delete_transient( $paths_key ); 1235 1236 if ( $purge_all ) { 1237 if ( self::should_use_async_purge() ) { 1238 self::purge_cdn_cache_async(); 1239 } else { 1240 self::purge_cdn_cache(); 1241 } 1242 return; 1243 } 1244 1245 if ( empty( $paths ) ) { 1246 return; 1247 } 1248 1249 if ( self::should_use_async_purge() ) { 1250 self::purge_cdn_paths_async( $paths ); 1251 } else { 1252 self::purge_cdn_paths( $paths ); 1253 } 1254 } 1255 1256 /** 1257 * Resolve purge mode (sync|async) from settings/filters. 1258 * 1259 * @since 2.1.0 1260 * @change 2.1.0 1261 * 1262 * @return string 1263 */ 1264 private static function get_purge_mode() { 1265 1266 $mode = 'async'; 1267 if ( ! empty( CDNTR_Engine::$settings['cdntr_purge_mode'] ) ) { 1268 $mode = strtolower( (string) CDNTR_Engine::$settings['cdntr_purge_mode'] ); 1269 } 1270 1271 $mode = (string) apply_filters( 'cdntr_purge_mode', $mode ); 1272 if ( $mode !== 'sync' && $mode !== 'async' ) { 1273 $mode = 'async'; 1274 } 1275 1276 return $mode; 1277 } 1278 1279 /** 1280 * Determine if async purge mode is active. 1281 * 1282 * @since 2.1.0 1283 * @change 2.1.0 1284 * 1285 * @return bool 1286 */ 1287 private static function should_use_async_purge() { 1288 1289 return self::get_purge_mode() === 'async'; 1290 } 1291 1292 /** 1293 * Coalescing window in seconds. 1294 * 1295 * @since 2.1.0 1296 * @change 2.1.0 1297 * 1298 * @return int 1299 */ 1300 private static function get_purge_coalesce_window() { 1301 1302 $window = 3; 1303 if ( isset( CDNTR_Engine::$settings['cdntr_purge_coalesce_window'] ) ) { 1304 $window = absint( CDNTR_Engine::$settings['cdntr_purge_coalesce_window'] ); 1305 } 1306 1307 $window = (int) apply_filters( 'cdntr_purge_coalesce_window', $window ); 1308 return max( 1, $window ); 1309 } 1310 1311 /** 1312 * Purge queue transient name scoped by blog. 1313 * 1314 * @since 2.1.0 1315 * @change 2.1.0 1316 * 1317 * @param string $scope 1318 * @return string 1319 */ 1320 private static function get_purge_queue_transient_name( $scope ) { 1321 1322 return 'cdntr_purge_queue_' . sanitize_key( $scope ) . '_' . get_current_blog_id(); 1323 } 1324 1325 /** 1326 * Persist lightweight purge telemetry for diagnostics. 1327 * 1328 * @since 2.1.0 1329 * @change 2.1.0 1330 * 1331 * @param string $type 1332 * @param string $status 1333 * @param array $meta 1334 */ 1335 private static function record_purge_telemetry( $type, $status, $meta = array() ) { 1336 1337 if ( empty( CDNTR_Engine::$settings['cdntr_purge_telemetry'] ) ) { 1338 return; 1339 } 1340 1341 $key = sanitize_key( (string) $type ) . ':' . sanitize_key( (string) $status ); 1342 if ( $key === ':' ) { 1343 return; 1344 } 1345 1346 $telemetry = get_option( 'cdntr_purge_telemetry', array() ); 1347 if ( ! is_array( $telemetry ) ) { 1348 $telemetry = array(); 1349 } 1350 1351 if ( ! isset( $telemetry['counters'] ) || ! is_array( $telemetry['counters'] ) ) { 1352 $telemetry['counters'] = array(); 1353 } 1354 1355 if ( ! isset( $telemetry['counters'][ $key ] ) ) { 1356 $telemetry['counters'][ $key ] = 0; 1357 } 1358 1359 $telemetry['counters'][ $key ]++; 1360 $telemetry['last_event'] = array( 1361 'type' => (string) $type, 1362 'status' => (string) $status, 1363 'meta' => is_array( $meta ) ? $meta : array(), 1364 'timestamp' => time(), 1365 ); 1366 1367 update_option( 'cdntr_purge_telemetry', $telemetry, false ); 782 1368 } 783 1369 … … 863 1449 public static function add_custom_meta_tags() { 864 1450 ?> 865 <meta name="cdn-site-verification" content="<?php echo esc_attr( CDNTR_Engine::$settings['cdntr_api_user'] ); ?>"> ';1451 <meta name="cdn-site-verification" content="<?php echo esc_attr( CDNTR_Engine::$settings['cdntr_api_user'] ); ?>"> 866 1452 <?php 867 1453 } … … 1057 1643 'cdntr_is_purge_all_button'=> (string) sanitize_text_field( $settings['cdntr_is_purge_all_button'] ), 1058 1644 'cdn_hostname_arr' => (string) sanitize_text_field( $settings['cdn_hostname_arr'] ) , 1645 'cdntr_purge_mode' => ( ( isset( $settings['cdntr_purge_mode'] ) && strtolower( (string) $settings['cdntr_purge_mode'] ) === 'sync' ) ? 'sync' : 'async' ), 1646 'cdntr_purge_coalesce_window' => max( 1, absint( isset( $settings['cdntr_purge_coalesce_window'] ) ? $settings['cdntr_purge_coalesce_window'] : 3 ) ), 1647 'cdntr_purge_telemetry' => ( ! empty( $settings['cdntr_purge_telemetry'] ) ? '1' : '0' ), 1059 1648 ); 1060 1649 -
cdntr/tags/1.2.5/inc/cdntr_engine.class.php
r3130906 r3467539 35 35 36 36 public static $settings; 37 private static $excluded_strings = array(); 38 private static $included_file_extensions_regex = ''; 39 private static $rewrite_cache = array(); 40 private static $rewrite_cache_limit = 1024; 37 41 38 42 … … 49 53 self::$settings = CDNTR::get_settings(); 50 54 51 if ( ! empty( self::$settings ) ) { 55 if ( ! empty( self::$settings ) && ! self::bypass_rewrite() ) { 56 self::prepare_runtime_settings(); 52 57 self::start_buffering(); 58 } 59 } 60 61 /** 62 * Prepare normalized runtime settings once per request. 63 * 64 * @since 2.0.9 65 * @change 2.0.9 66 */ 67 private static function prepare_runtime_settings() { 68 69 self::$excluded_strings = array(); 70 self::$included_file_extensions_regex = ''; 71 self::$rewrite_cache = array(); 72 73 if ( ! empty( self::$settings['excluded_strings'] ) ) { 74 $excluded_strings = explode( PHP_EOL, self::$settings['excluded_strings'] ); 75 $excluded_strings = array_map( 'trim', $excluded_strings ); 76 self::$excluded_strings = array_filter( $excluded_strings, function( $value ) { 77 return $value !== ''; 78 } ); 79 } 80 81 if ( ! empty( self::$settings['included_file_extensions'] ) ) { 82 self::$included_file_extensions_regex = quotemeta( 83 implode( '|', explode( PHP_EOL, self::$settings['included_file_extensions'] ) ) 84 ); 53 85 } 54 86 } … … 146 178 147 179 // if string excluded (case sensitive) 148 if ( ! empty( self::$settings['excluded_strings'] ) ) { 149 $excluded_strings = explode( PHP_EOL, self::$settings['excluded_strings'] ); 150 151 foreach ( $excluded_strings as $excluded_string ) { 180 if ( ! empty( self::$excluded_strings ) ) { 181 foreach ( self::$excluded_strings as $excluded_string ) { 152 182 if ( strpos( $file_url, $excluded_string ) !== false ) { 153 183 return true; … … 225 255 $site_hostnames = (array) apply_filters( 'cdntr_site_hostnames', array( $site_hostname ) ); 226 256 $cdn_hostname = self::$settings['cdn_hostname']; 257 $cache_key = $site_hostname . '|' . $file_url; 258 259 if ( isset( self::$rewrite_cache[ $cache_key ] ) ) { 260 return self::$rewrite_cache[ $cache_key ]; 261 } 227 262 228 263 // if excluded or already using CDN hostname 229 264 if ( self::is_excluded( $file_url ) || stripos( $file_url, $cdn_hostname ) !== false ) { 230 return $file_url; 265 self::remember_rewrite_cache( $cache_key, $file_url ); 266 return self::$rewrite_cache[ $cache_key ]; 231 267 } 232 268 … … 234 270 foreach ( $site_hostnames as $site_hostname ) { 235 271 if ( stripos( $file_url, '//' . $site_hostname ) !== false || stripos( $file_url, '\/\/' . $site_hostname ) !== false ) { 236 return substr_replace( $file_url, $cdn_hostname, stripos( $file_url, $site_hostname ), strlen( $site_hostname ) ); 272 self::remember_rewrite_cache( 273 $cache_key, 274 substr_replace( $file_url, $cdn_hostname, stripos( $file_url, $site_hostname ), strlen( $site_hostname ) ) 275 ); 276 return self::$rewrite_cache[ $cache_key ]; 237 277 } 238 278 } … … 242 282 // rewrite relative URL (e.g. /wp-content/uploads/example.jpg) 243 283 if ( strpos( $file_url, '//' ) !== 0 && strpos( $file_url, '/' ) === 0 ) { 244 return '//' . $cdn_hostname . $file_url; 284 self::remember_rewrite_cache( $cache_key, '//' . $cdn_hostname . $file_url ); 285 return self::$rewrite_cache[ $cache_key ]; 245 286 } 246 287 247 288 // rewrite escaped relative URL (e.g. \/wp-content\/uploads\/example.jpg) 248 289 if ( strpos( $file_url, '\/\/' ) !== 0 && strpos( $file_url, '\/' ) === 0 ) { 249 return '\/\/' . $cdn_hostname . $file_url; 250 } 251 } 252 253 return $file_url; 290 self::remember_rewrite_cache( $cache_key, '\/\/' . $cdn_hostname . $file_url ); 291 return self::$rewrite_cache[ $cache_key ]; 292 } 293 } 294 295 self::remember_rewrite_cache( $cache_key, $file_url ); 296 297 return self::$rewrite_cache[ $cache_key ]; 254 298 } 255 299 … … 274 318 $contents = apply_filters( 'cdntr_contents_before_rewrite', $contents ); 275 319 276 $included_file_extensions_regex = quotemeta( implode( '|', explode( PHP_EOL, self::$settings['included_file_extensions'] ) ) ); 320 $included_file_extensions_regex = self::$included_file_extensions_regex; 321 if ( empty( $included_file_extensions_regex ) ) { 322 return $contents; 323 } 277 324 278 325 $urls_regex = '#(?:(?:[\"\'\s=>,;]|url\()\K|^)[^\"\'\s(=>,;]+(' . $included_file_extensions_regex . ')(\?[^\/?\\\"\'\s)>,]+)?(?:(?=\/?[?\\\"\'\s)>,&])|$)#i'; … … 282 329 return $rewritten_contents; 283 330 } 331 332 /** 333 * Keep rewrite cache bounded to prevent excessive memory usage. 334 * 335 * @since 2.0.9 336 * @change 2.0.9 337 * 338 * @param string $key cache key 339 * @param string $value cache value 340 */ 341 private static function remember_rewrite_cache( $key, $value ) { 342 343 if ( count( self::$rewrite_cache ) >= self::$rewrite_cache_limit ) { 344 array_shift( self::$rewrite_cache ); 345 } 346 347 self::$rewrite_cache[ $key ] = $value; 348 } 284 349 } -
cdntr/tags/1.2.5/readme.txt
r3131495 r3467539 3 3 Tags: cdn, content delivery network, content distribution network 4 4 Tested up to: 6.6.1 5 Stable tag: 1.2. 25 Stable tag: 1.2.5 6 6 Requires at least: 5.7 7 7 Requires PHP: 7.2 … … 34 34 *Endpoints: 35 35 *Purge All Cache: https://cdn.com.tr/api/purgeAll 36 *Purge Selected Paths: https://cdn.com.tr/api/purge 36 37 *Check Account Status: https://cdn.com.tr/api/checkAccount 37 38 *Privacy and Terms … … 43 44 * [CDNTR](https://cdn.com.tr) 44 45 45 == Screenshots==46 == Changelog == 46 47 47 1. screenshot-1.png 48 = 1.2.5 = 49 * Added async purge mode support for runtime-triggered invalidations 50 * Added coalesced purge queue with short window to reduce duplicate API calls 51 * Added lightweight purge telemetry counters for operational diagnostics 52 * Added purge mode/coalescing filters (`cdntr_purge_mode`, `cdntr_purge_coalesce_window`) 48 53 49 == Changelog == 54 = 1.2.4 = 55 * Added built-in term change purge hooks (create/edit/delete) with focused taxonomy support 56 * Added built-in sitewide purge hooks for customizer and theme switch events 57 * Added short purge burst throttling for noisy event streams to reduce duplicate API calls 58 * Reduced need for site-specific CDNTR auto-purge mu-plugins by moving common logic into core plugin 59 60 = 1.2.3 = 61 * Security hardening for path-based purge normalization (control-char stripping, traversal-like path guard, length bound) 62 * Safer taxonomy purge handling by skipping invalid term links 63 * Improved delete-flow purge coverage by collecting related post/category/shop paths before deletion 50 64 51 65 = 1.2.2 = … … 54 68 = 1.2.1 = 55 69 * Minor security updates 70 71 -
cdntr/trunk/cdntr.php
r3131497 r3467539 6 6 Author URI: https://cdn.com.tr 7 7 License: GPLv2 or later 8 Version: 1.2. 28 Version: 1.2.5 9 9 */ 10 10 … … 16 16 17 17 // constants 18 define( 'CDNTR_VERSION', '1. 0' );18 define( 'CDNTR_VERSION', '1.2.5' ); 19 19 define( 'CDNTR_MIN_PHP', '5.6' ); 20 20 define( 'CDNTR_MIN_WP', '5.1' ); -
cdntr/trunk/inc/cdntr.class.php
r3130906 r3467539 41 41 add_action( 'init', array( __CLASS__, 'register_textdomain' ) ); 42 42 add_action( 'init', array( __CLASS__, 'check_meta_valid' ) ); 43 add_action( 'save_post', array( __CLASS__, 'maybe_purge_post_paths' ), 20, 3 ); 44 add_action( 'deleted_post', array( __CLASS__, 'purge_deleted_post_paths' ), 20, 1 ); 45 add_action( 'created_term', array( __CLASS__, 'maybe_purge_term_paths' ), 20, 3 ); 46 add_action( 'edited_term', array( __CLASS__, 'maybe_purge_term_paths' ), 20, 3 ); 47 add_action( 'delete_term', array( __CLASS__, 'maybe_purge_deleted_term_paths' ), 20, 5 ); 48 add_action( 'customize_save_after', array( __CLASS__, 'maybe_purge_sitewide' ), 20, 1 ); 49 add_action( 'switch_theme', array( __CLASS__, 'maybe_purge_sitewide' ), 20, 3 ); 50 add_action( 'cdntr_run_coalesced_purge', array( __CLASS__, 'run_coalesced_purge' ) ); 43 51 44 52 // multisite hook … … 356 364 'cdntr_is_purge_all_button' => '', 357 365 'cdntr_api_password' => '', 366 'cdntr_purge_mode' => 'async', 367 'cdntr_purge_coalesce_window'=> 3, 368 'cdntr_purge_telemetry' => '1', 358 369 ); 359 370 // cdntr_account_id cdntr_api_user cdntr_api_password … … 731 742 ); 732 743 744 $telemetry_status = 'error'; 745 733 746 // check if API call failed 734 747 if ( is_wp_error( $response ) ) { … … 743 756 744 757 if ( $response_status_code === 200 ) { 758 $telemetry_status = 'ok'; 745 759 $response = array( 746 760 'wrapper' => '<div class="notice notice-success is-dismissible"><p><strong>%s</strong></p></div>', … … 779 793 } 780 794 795 self::record_purge_telemetry( 'all_sync', $telemetry_status ); 796 781 797 return $response; 798 } 799 800 /** 801 * purge CDN cache asynchronously (fire-and-forget) 802 * 803 * @since 2.1.0 804 * @change 2.1.0 805 * 806 * @return array 807 */ 808 public static function purge_cdn_cache_async() { 809 810 $auth = base64_encode( CDNTR_Engine::$settings['cdntr_api_user'] . ':' . CDNTR_Engine::$settings['cdntr_api_password'] ); 811 812 wp_remote_post( 813 'https://cdn.com.tr/api/purgeAll', 814 array( 815 'timeout' => 1, 816 'httpversion' => '1.1', 817 'blocking' => false, 818 'headers' => array( 819 'Authorization' => 'Basic ' . $auth, 820 'Content-Type' => 'application/json', 821 ), 822 ) 823 ); 824 825 self::record_purge_telemetry( 'all_async', 'queued' ); 826 827 return array( 828 'ok' => true, 829 'message' => 'Purge queued.', 830 ); 831 } 832 833 /** 834 * Purge specific URL paths from CDN. 835 * 836 * @since 2.0.9 837 * @change 2.0.9 838 * 839 * @param array $paths URL paths (e.g. /product/foo/) 840 * @return array 841 */ 842 public static function purge_cdn_paths( $paths ) { 843 844 $paths = self::normalize_purge_paths( $paths ); 845 846 if ( empty( $paths ) ) { 847 self::record_purge_telemetry( 'paths_sync', 'skipped', array( 'paths' => 0 ) ); 848 return array( 849 'ok' => false, 850 'message' => 'No valid purge paths.', 851 ); 852 } 853 854 $auth = base64_encode( CDNTR_Engine::$settings['cdntr_api_user'] . ':' . CDNTR_Engine::$settings['cdntr_api_password'] ); 855 $response = wp_remote_post( 856 'https://cdn.com.tr/api/purge', 857 array( 858 'timeout' => 15, 859 'httpversion' => '1.1', 860 'headers' => array( 861 'Authorization' => 'Basic ' . $auth, 862 'Content-Type' => 'application/json', 863 ), 864 'body' => wp_json_encode( array( 'paths' => $paths ) ), 865 ) 866 ); 867 868 if ( is_wp_error( $response ) ) { 869 self::record_purge_telemetry( 'paths_sync', 'error', array( 'paths' => count( $paths ) ) ); 870 return array( 871 'ok' => false, 872 'message' => $response->get_error_message(), 873 ); 874 } 875 876 $status = (int) wp_remote_retrieve_response_code( $response ); 877 self::record_purge_telemetry( 878 'paths_sync', 879 ( $status >= 200 && $status < 300 ) ? 'ok' : 'error', 880 array( 881 'status' => $status, 882 'paths' => count( $paths ), 883 ) 884 ); 885 return array( 886 'ok' => ( $status >= 200 && $status < 300 ), 887 'status' => $status, 888 'message' => (string) wp_remote_retrieve_body( $response ), 889 ); 890 } 891 892 /** 893 * Purge specific URL paths asynchronously (fire-and-forget). 894 * 895 * @since 2.1.0 896 * @change 2.1.0 897 * 898 * @param array $paths 899 * @return array 900 */ 901 public static function purge_cdn_paths_async( $paths ) { 902 903 $paths = self::normalize_purge_paths( $paths ); 904 if ( empty( $paths ) ) { 905 self::record_purge_telemetry( 'paths_async', 'skipped', array( 'paths' => 0 ) ); 906 return array( 907 'ok' => false, 908 'message' => 'No valid purge paths.', 909 ); 910 } 911 912 $auth = base64_encode( CDNTR_Engine::$settings['cdntr_api_user'] . ':' . CDNTR_Engine::$settings['cdntr_api_password'] ); 913 wp_remote_post( 914 'https://cdn.com.tr/api/purge', 915 array( 916 'timeout' => 1, 917 'httpversion' => '1.1', 918 'blocking' => false, 919 'headers' => array( 920 'Authorization' => 'Basic ' . $auth, 921 'Content-Type' => 'application/json', 922 ), 923 'body' => wp_json_encode( array( 'paths' => $paths ) ), 924 ) 925 ); 926 927 self::record_purge_telemetry( 'paths_async', 'queued', array( 'paths' => count( $paths ) ) ); 928 929 return array( 930 'ok' => true, 931 'message' => 'Purge queued.', 932 ); 933 } 934 935 /** 936 * Purge only affected URLs when content changes. 937 * 938 * @since 2.0.9 939 * @change 2.0.9 940 */ 941 public static function maybe_purge_post_paths( $post_id, $post, $update ) { 942 943 if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) { 944 return; 945 } 946 947 if ( empty( CDNTR_Engine::$settings['cdntr_api_user'] ) || empty( CDNTR_Engine::$settings['cdntr_api_password'] ) ) { 948 return; 949 } 950 951 if ( ! $post instanceof \WP_Post ) { 952 $post = get_post( $post_id ); 953 if ( ! $post ) { 954 return; 955 } 956 } 957 958 if ( $post->post_status !== 'publish' && $post->post_status !== 'private' ) { 959 return; 960 } 961 962 $paths = array( 963 '/', 964 wp_parse_url( get_permalink( $post_id ), PHP_URL_PATH ), 965 ); 966 967 if ( $post->post_type === 'product' ) { 968 $shop_page_id = function_exists( 'wc_get_page_id' ) ? wc_get_page_id( 'shop' ) : 0; 969 if ( $shop_page_id > 0 ) { 970 $paths[] = wp_parse_url( get_permalink( $shop_page_id ), PHP_URL_PATH ); 971 } 972 973 $terms = get_the_terms( $post_id, 'product_cat' ); 974 if ( is_array( $terms ) ) { 975 foreach ( $terms as $term ) { 976 $term_link = get_term_link( $term ); 977 if ( ! is_wp_error( $term_link ) ) { 978 $paths[] = wp_parse_url( $term_link, PHP_URL_PATH ); 979 } 980 } 981 } 982 } 983 984 self::enqueue_purge_paths( $paths ); 985 } 986 987 /** 988 * Purge homepage after post deletion to avoid stale listings. 989 * 990 * @since 2.0.9 991 * @change 2.0.9 992 */ 993 public static function purge_deleted_post_paths( $post_id ) { 994 995 if ( empty( CDNTR_Engine::$settings['cdntr_api_user'] ) || empty( CDNTR_Engine::$settings['cdntr_api_password'] ) ) { 996 return; 997 } 998 999 self::enqueue_purge_paths( array( '/' ) ); 1000 } 1001 1002 /** 1003 * Purge related paths when selected taxonomies are changed. 1004 * 1005 * @since 2.1.0 1006 * @change 2.1.0 1007 */ 1008 public static function maybe_purge_term_paths( $term_id, $tt_id, $taxonomy ) { 1009 1010 if ( empty( CDNTR_Engine::$settings['cdntr_api_user'] ) || empty( CDNTR_Engine::$settings['cdntr_api_password'] ) ) { 1011 return; 1012 } 1013 1014 $allowed_taxonomies = (array) apply_filters( 'cdntr_purge_taxonomies', array( 'product_cat' ) ); 1015 if ( empty( $taxonomy ) || ! in_array( $taxonomy, $allowed_taxonomies, true ) ) { 1016 return; 1017 } 1018 1019 if ( self::is_purge_throttled( 'term_' . $taxonomy, 10 ) ) { 1020 return; 1021 } 1022 1023 $paths = array( '/' ); 1024 $term = get_term( (int) $term_id, $taxonomy ); 1025 if ( $term && ! is_wp_error( $term ) ) { 1026 $term_link = get_term_link( $term ); 1027 if ( ! is_wp_error( $term_link ) ) { 1028 $paths[] = wp_parse_url( $term_link, PHP_URL_PATH ); 1029 } 1030 } 1031 1032 $shop_page_id = function_exists( 'wc_get_page_id' ) ? wc_get_page_id( 'shop' ) : 0; 1033 if ( $shop_page_id > 0 ) { 1034 $paths[] = wp_parse_url( get_permalink( $shop_page_id ), PHP_URL_PATH ); 1035 } 1036 1037 self::enqueue_purge_paths( $paths ); 1038 } 1039 1040 /** 1041 * Purge related paths when selected taxonomies are deleted. 1042 * 1043 * @since 2.1.0 1044 * @change 2.1.0 1045 */ 1046 public static function maybe_purge_deleted_term_paths( $term, $tt_id, $taxonomy, $deleted_term, $object_ids ) { 1047 1048 if ( empty( CDNTR_Engine::$settings['cdntr_api_user'] ) || empty( CDNTR_Engine::$settings['cdntr_api_password'] ) ) { 1049 return; 1050 } 1051 1052 $allowed_taxonomies = (array) apply_filters( 'cdntr_purge_taxonomies', array( 'product_cat' ) ); 1053 if ( empty( $taxonomy ) || ! in_array( $taxonomy, $allowed_taxonomies, true ) ) { 1054 return; 1055 } 1056 1057 if ( self::is_purge_throttled( 'term_delete_' . $taxonomy, 10 ) ) { 1058 return; 1059 } 1060 1061 $paths = array( '/' ); 1062 $shop_page_id = function_exists( 'wc_get_page_id' ) ? wc_get_page_id( 'shop' ) : 0; 1063 if ( $shop_page_id > 0 ) { 1064 $paths[] = wp_parse_url( get_permalink( $shop_page_id ), PHP_URL_PATH ); 1065 } 1066 1067 self::enqueue_purge_paths( $paths ); 1068 } 1069 1070 /** 1071 * Purge whole site for theme/customizer style changes. 1072 * 1073 * @since 2.1.0 1074 * @change 2.1.0 1075 */ 1076 public static function maybe_purge_sitewide() { 1077 1078 if ( empty( CDNTR_Engine::$settings['cdntr_api_user'] ) || empty( CDNTR_Engine::$settings['cdntr_api_password'] ) ) { 1079 return; 1080 } 1081 1082 if ( self::is_purge_throttled( 'sitewide', 20 ) ) { 1083 return; 1084 } 1085 1086 self::enqueue_purge_all(); 1087 } 1088 1089 /** 1090 * Normalize purge paths to a clean unique path list. 1091 * 1092 * @since 2.0.9 1093 * @change 2.0.9 1094 * 1095 * @param array $paths 1096 * @return array 1097 */ 1098 private static function normalize_purge_paths( $paths ) { 1099 1100 if ( ! is_array( $paths ) ) { 1101 $paths = array(); 1102 } 1103 1104 $normalized = array(); 1105 foreach ( $paths as $path ) { 1106 if ( ! is_string( $path ) || $path === '' ) { 1107 continue; 1108 } 1109 1110 // Strip control chars and trim to avoid malformed payloads. 1111 $path = trim( preg_replace( '/[\x00-\x1F\x7F]/', '', $path ) ); 1112 if ( $path === '' || strlen( $path ) > 2048 ) { 1113 continue; 1114 } 1115 1116 $clean = wp_parse_url( $path, PHP_URL_PATH ); 1117 if ( ! is_string( $clean ) || $clean === '' ) { 1118 continue; 1119 } 1120 1121 // Block traversal-like paths from reaching purge API. 1122 if ( strpos( $clean, '..' ) !== false ) { 1123 continue; 1124 } 1125 1126 if ( strpos( $clean, '/' ) !== 0 ) { 1127 $clean = '/' . $clean; 1128 } 1129 1130 $normalized[] = $clean; 1131 } 1132 1133 return array_values( array_unique( $normalized ) ); 1134 } 1135 1136 /** 1137 * Basic transient lock to avoid duplicate purge bursts. 1138 * 1139 * @since 2.1.0 1140 * @change 2.1.0 1141 */ 1142 private static function is_purge_throttled( $scope, $ttl = 10 ) { 1143 1144 $scope = sanitize_key( (string) $scope ); 1145 $ttl = absint( $ttl ); 1146 if ( $scope === '' || $ttl < 1 ) { 1147 return false; 1148 } 1149 1150 $lock_key = 'cdntr_purge_lock_' . $scope; 1151 if ( get_transient( $lock_key ) ) { 1152 return true; 1153 } 1154 1155 set_transient( $lock_key, 1, $ttl ); 1156 return false; 1157 } 1158 1159 /** 1160 * Queue targeted purge paths in a short coalescing window. 1161 * 1162 * @since 2.1.0 1163 * @change 2.1.0 1164 * 1165 * @param array $paths 1166 */ 1167 private static function enqueue_purge_paths( $paths ) { 1168 1169 if ( empty( CDNTR_Engine::$settings['cdntr_api_user'] ) || empty( CDNTR_Engine::$settings['cdntr_api_password'] ) ) { 1170 return; 1171 } 1172 1173 $paths = self::normalize_purge_paths( $paths ); 1174 if ( empty( $paths ) ) { 1175 return; 1176 } 1177 1178 $queue_key = self::get_purge_queue_transient_name( 'paths' ); 1179 $queued_paths = get_transient( $queue_key ); 1180 if ( ! is_array( $queued_paths ) ) { 1181 $queued_paths = array(); 1182 } 1183 1184 $queued_paths = array_values( array_unique( array_merge( $queued_paths, $paths ) ) ); 1185 set_transient( $queue_key, $queued_paths, 300 ); 1186 1187 self::record_purge_telemetry( 'paths_queue', 'queued', array( 'paths' => count( $queued_paths ) ) ); 1188 1189 if ( ! wp_next_scheduled( 'cdntr_run_coalesced_purge' ) ) { 1190 wp_schedule_single_event( time() + self::get_purge_coalesce_window(), 'cdntr_run_coalesced_purge' ); 1191 } 1192 } 1193 1194 /** 1195 * Queue purge-all requests in a short coalescing window. 1196 * 1197 * @since 2.1.0 1198 * @change 2.1.0 1199 */ 1200 private static function enqueue_purge_all() { 1201 1202 if ( empty( CDNTR_Engine::$settings['cdntr_api_user'] ) || empty( CDNTR_Engine::$settings['cdntr_api_password'] ) ) { 1203 return; 1204 } 1205 1206 set_transient( self::get_purge_queue_transient_name( 'all' ), 1, 300 ); 1207 self::record_purge_telemetry( 'all_queue', 'queued' ); 1208 1209 if ( ! wp_next_scheduled( 'cdntr_run_coalesced_purge' ) ) { 1210 wp_schedule_single_event( time() + self::get_purge_coalesce_window(), 'cdntr_run_coalesced_purge' ); 1211 } 1212 } 1213 1214 /** 1215 * Execute queued purge operations after coalescing window. 1216 * 1217 * @since 2.1.0 1218 * @change 2.1.0 1219 */ 1220 public static function run_coalesced_purge() { 1221 1222 if ( empty( CDNTR_Engine::$settings['cdntr_api_user'] ) || empty( CDNTR_Engine::$settings['cdntr_api_password'] ) ) { 1223 return; 1224 } 1225 1226 $purge_all_key = self::get_purge_queue_transient_name( 'all' ); 1227 $paths_key = self::get_purge_queue_transient_name( 'paths' ); 1228 1229 $purge_all = (bool) get_transient( $purge_all_key ); 1230 $paths = get_transient( $paths_key ); 1231 $paths = is_array( $paths ) ? $paths : array(); 1232 1233 delete_transient( $purge_all_key ); 1234 delete_transient( $paths_key ); 1235 1236 if ( $purge_all ) { 1237 if ( self::should_use_async_purge() ) { 1238 self::purge_cdn_cache_async(); 1239 } else { 1240 self::purge_cdn_cache(); 1241 } 1242 return; 1243 } 1244 1245 if ( empty( $paths ) ) { 1246 return; 1247 } 1248 1249 if ( self::should_use_async_purge() ) { 1250 self::purge_cdn_paths_async( $paths ); 1251 } else { 1252 self::purge_cdn_paths( $paths ); 1253 } 1254 } 1255 1256 /** 1257 * Resolve purge mode (sync|async) from settings/filters. 1258 * 1259 * @since 2.1.0 1260 * @change 2.1.0 1261 * 1262 * @return string 1263 */ 1264 private static function get_purge_mode() { 1265 1266 $mode = 'async'; 1267 if ( ! empty( CDNTR_Engine::$settings['cdntr_purge_mode'] ) ) { 1268 $mode = strtolower( (string) CDNTR_Engine::$settings['cdntr_purge_mode'] ); 1269 } 1270 1271 $mode = (string) apply_filters( 'cdntr_purge_mode', $mode ); 1272 if ( $mode !== 'sync' && $mode !== 'async' ) { 1273 $mode = 'async'; 1274 } 1275 1276 return $mode; 1277 } 1278 1279 /** 1280 * Determine if async purge mode is active. 1281 * 1282 * @since 2.1.0 1283 * @change 2.1.0 1284 * 1285 * @return bool 1286 */ 1287 private static function should_use_async_purge() { 1288 1289 return self::get_purge_mode() === 'async'; 1290 } 1291 1292 /** 1293 * Coalescing window in seconds. 1294 * 1295 * @since 2.1.0 1296 * @change 2.1.0 1297 * 1298 * @return int 1299 */ 1300 private static function get_purge_coalesce_window() { 1301 1302 $window = 3; 1303 if ( isset( CDNTR_Engine::$settings['cdntr_purge_coalesce_window'] ) ) { 1304 $window = absint( CDNTR_Engine::$settings['cdntr_purge_coalesce_window'] ); 1305 } 1306 1307 $window = (int) apply_filters( 'cdntr_purge_coalesce_window', $window ); 1308 return max( 1, $window ); 1309 } 1310 1311 /** 1312 * Purge queue transient name scoped by blog. 1313 * 1314 * @since 2.1.0 1315 * @change 2.1.0 1316 * 1317 * @param string $scope 1318 * @return string 1319 */ 1320 private static function get_purge_queue_transient_name( $scope ) { 1321 1322 return 'cdntr_purge_queue_' . sanitize_key( $scope ) . '_' . get_current_blog_id(); 1323 } 1324 1325 /** 1326 * Persist lightweight purge telemetry for diagnostics. 1327 * 1328 * @since 2.1.0 1329 * @change 2.1.0 1330 * 1331 * @param string $type 1332 * @param string $status 1333 * @param array $meta 1334 */ 1335 private static function record_purge_telemetry( $type, $status, $meta = array() ) { 1336 1337 if ( empty( CDNTR_Engine::$settings['cdntr_purge_telemetry'] ) ) { 1338 return; 1339 } 1340 1341 $key = sanitize_key( (string) $type ) . ':' . sanitize_key( (string) $status ); 1342 if ( $key === ':' ) { 1343 return; 1344 } 1345 1346 $telemetry = get_option( 'cdntr_purge_telemetry', array() ); 1347 if ( ! is_array( $telemetry ) ) { 1348 $telemetry = array(); 1349 } 1350 1351 if ( ! isset( $telemetry['counters'] ) || ! is_array( $telemetry['counters'] ) ) { 1352 $telemetry['counters'] = array(); 1353 } 1354 1355 if ( ! isset( $telemetry['counters'][ $key ] ) ) { 1356 $telemetry['counters'][ $key ] = 0; 1357 } 1358 1359 $telemetry['counters'][ $key ]++; 1360 $telemetry['last_event'] = array( 1361 'type' => (string) $type, 1362 'status' => (string) $status, 1363 'meta' => is_array( $meta ) ? $meta : array(), 1364 'timestamp' => time(), 1365 ); 1366 1367 update_option( 'cdntr_purge_telemetry', $telemetry, false ); 782 1368 } 783 1369 … … 863 1449 public static function add_custom_meta_tags() { 864 1450 ?> 865 <meta name="cdn-site-verification" content="<?php echo esc_attr( CDNTR_Engine::$settings['cdntr_api_user'] ); ?>"> ';1451 <meta name="cdn-site-verification" content="<?php echo esc_attr( CDNTR_Engine::$settings['cdntr_api_user'] ); ?>"> 866 1452 <?php 867 1453 } … … 1057 1643 'cdntr_is_purge_all_button'=> (string) sanitize_text_field( $settings['cdntr_is_purge_all_button'] ), 1058 1644 'cdn_hostname_arr' => (string) sanitize_text_field( $settings['cdn_hostname_arr'] ) , 1645 'cdntr_purge_mode' => ( ( isset( $settings['cdntr_purge_mode'] ) && strtolower( (string) $settings['cdntr_purge_mode'] ) === 'sync' ) ? 'sync' : 'async' ), 1646 'cdntr_purge_coalesce_window' => max( 1, absint( isset( $settings['cdntr_purge_coalesce_window'] ) ? $settings['cdntr_purge_coalesce_window'] : 3 ) ), 1647 'cdntr_purge_telemetry' => ( ! empty( $settings['cdntr_purge_telemetry'] ) ? '1' : '0' ), 1059 1648 ); 1060 1649 -
cdntr/trunk/inc/cdntr_engine.class.php
r3130906 r3467539 35 35 36 36 public static $settings; 37 private static $excluded_strings = array(); 38 private static $included_file_extensions_regex = ''; 39 private static $rewrite_cache = array(); 40 private static $rewrite_cache_limit = 1024; 37 41 38 42 … … 49 53 self::$settings = CDNTR::get_settings(); 50 54 51 if ( ! empty( self::$settings ) ) { 55 if ( ! empty( self::$settings ) && ! self::bypass_rewrite() ) { 56 self::prepare_runtime_settings(); 52 57 self::start_buffering(); 58 } 59 } 60 61 /** 62 * Prepare normalized runtime settings once per request. 63 * 64 * @since 2.0.9 65 * @change 2.0.9 66 */ 67 private static function prepare_runtime_settings() { 68 69 self::$excluded_strings = array(); 70 self::$included_file_extensions_regex = ''; 71 self::$rewrite_cache = array(); 72 73 if ( ! empty( self::$settings['excluded_strings'] ) ) { 74 $excluded_strings = explode( PHP_EOL, self::$settings['excluded_strings'] ); 75 $excluded_strings = array_map( 'trim', $excluded_strings ); 76 self::$excluded_strings = array_filter( $excluded_strings, function( $value ) { 77 return $value !== ''; 78 } ); 79 } 80 81 if ( ! empty( self::$settings['included_file_extensions'] ) ) { 82 self::$included_file_extensions_regex = quotemeta( 83 implode( '|', explode( PHP_EOL, self::$settings['included_file_extensions'] ) ) 84 ); 53 85 } 54 86 } … … 146 178 147 179 // if string excluded (case sensitive) 148 if ( ! empty( self::$settings['excluded_strings'] ) ) { 149 $excluded_strings = explode( PHP_EOL, self::$settings['excluded_strings'] ); 150 151 foreach ( $excluded_strings as $excluded_string ) { 180 if ( ! empty( self::$excluded_strings ) ) { 181 foreach ( self::$excluded_strings as $excluded_string ) { 152 182 if ( strpos( $file_url, $excluded_string ) !== false ) { 153 183 return true; … … 225 255 $site_hostnames = (array) apply_filters( 'cdntr_site_hostnames', array( $site_hostname ) ); 226 256 $cdn_hostname = self::$settings['cdn_hostname']; 257 $cache_key = $site_hostname . '|' . $file_url; 258 259 if ( isset( self::$rewrite_cache[ $cache_key ] ) ) { 260 return self::$rewrite_cache[ $cache_key ]; 261 } 227 262 228 263 // if excluded or already using CDN hostname 229 264 if ( self::is_excluded( $file_url ) || stripos( $file_url, $cdn_hostname ) !== false ) { 230 return $file_url; 265 self::remember_rewrite_cache( $cache_key, $file_url ); 266 return self::$rewrite_cache[ $cache_key ]; 231 267 } 232 268 … … 234 270 foreach ( $site_hostnames as $site_hostname ) { 235 271 if ( stripos( $file_url, '//' . $site_hostname ) !== false || stripos( $file_url, '\/\/' . $site_hostname ) !== false ) { 236 return substr_replace( $file_url, $cdn_hostname, stripos( $file_url, $site_hostname ), strlen( $site_hostname ) ); 272 self::remember_rewrite_cache( 273 $cache_key, 274 substr_replace( $file_url, $cdn_hostname, stripos( $file_url, $site_hostname ), strlen( $site_hostname ) ) 275 ); 276 return self::$rewrite_cache[ $cache_key ]; 237 277 } 238 278 } … … 242 282 // rewrite relative URL (e.g. /wp-content/uploads/example.jpg) 243 283 if ( strpos( $file_url, '//' ) !== 0 && strpos( $file_url, '/' ) === 0 ) { 244 return '//' . $cdn_hostname . $file_url; 284 self::remember_rewrite_cache( $cache_key, '//' . $cdn_hostname . $file_url ); 285 return self::$rewrite_cache[ $cache_key ]; 245 286 } 246 287 247 288 // rewrite escaped relative URL (e.g. \/wp-content\/uploads\/example.jpg) 248 289 if ( strpos( $file_url, '\/\/' ) !== 0 && strpos( $file_url, '\/' ) === 0 ) { 249 return '\/\/' . $cdn_hostname . $file_url; 250 } 251 } 252 253 return $file_url; 290 self::remember_rewrite_cache( $cache_key, '\/\/' . $cdn_hostname . $file_url ); 291 return self::$rewrite_cache[ $cache_key ]; 292 } 293 } 294 295 self::remember_rewrite_cache( $cache_key, $file_url ); 296 297 return self::$rewrite_cache[ $cache_key ]; 254 298 } 255 299 … … 274 318 $contents = apply_filters( 'cdntr_contents_before_rewrite', $contents ); 275 319 276 $included_file_extensions_regex = quotemeta( implode( '|', explode( PHP_EOL, self::$settings['included_file_extensions'] ) ) ); 320 $included_file_extensions_regex = self::$included_file_extensions_regex; 321 if ( empty( $included_file_extensions_regex ) ) { 322 return $contents; 323 } 277 324 278 325 $urls_regex = '#(?:(?:[\"\'\s=>,;]|url\()\K|^)[^\"\'\s(=>,;]+(' . $included_file_extensions_regex . ')(\?[^\/?\\\"\'\s)>,]+)?(?:(?=\/?[?\\\"\'\s)>,&])|$)#i'; … … 282 329 return $rewritten_contents; 283 330 } 331 332 /** 333 * Keep rewrite cache bounded to prevent excessive memory usage. 334 * 335 * @since 2.0.9 336 * @change 2.0.9 337 * 338 * @param string $key cache key 339 * @param string $value cache value 340 */ 341 private static function remember_rewrite_cache( $key, $value ) { 342 343 if ( count( self::$rewrite_cache ) >= self::$rewrite_cache_limit ) { 344 array_shift( self::$rewrite_cache ); 345 } 346 347 self::$rewrite_cache[ $key ] = $value; 348 } 284 349 } -
cdntr/trunk/readme.txt
r3131495 r3467539 3 3 Tags: cdn, content delivery network, content distribution network 4 4 Tested up to: 6.6.1 5 Stable tag: 1.2. 25 Stable tag: 1.2.5 6 6 Requires at least: 5.7 7 7 Requires PHP: 7.2 … … 34 34 *Endpoints: 35 35 *Purge All Cache: https://cdn.com.tr/api/purgeAll 36 *Purge Selected Paths: https://cdn.com.tr/api/purge 36 37 *Check Account Status: https://cdn.com.tr/api/checkAccount 37 38 *Privacy and Terms … … 43 44 * [CDNTR](https://cdn.com.tr) 44 45 45 == Screenshots==46 == Changelog == 46 47 47 1. screenshot-1.png 48 = 1.2.5 = 49 * Added async purge mode support for runtime-triggered invalidations 50 * Added coalesced purge queue with short window to reduce duplicate API calls 51 * Added lightweight purge telemetry counters for operational diagnostics 52 * Added purge mode/coalescing filters (`cdntr_purge_mode`, `cdntr_purge_coalesce_window`) 48 53 49 == Changelog == 54 = 1.2.4 = 55 * Added built-in term change purge hooks (create/edit/delete) with focused taxonomy support 56 * Added built-in sitewide purge hooks for customizer and theme switch events 57 * Added short purge burst throttling for noisy event streams to reduce duplicate API calls 58 * Reduced need for site-specific CDNTR auto-purge mu-plugins by moving common logic into core plugin 59 60 = 1.2.3 = 61 * Security hardening for path-based purge normalization (control-char stripping, traversal-like path guard, length bound) 62 * Safer taxonomy purge handling by skipping invalid term links 63 * Improved delete-flow purge coverage by collecting related post/category/shop paths before deletion 50 64 51 65 = 1.2.2 = … … 54 68 = 1.2.1 = 55 69 * Minor security updates 70 71
Note: See TracChangeset
for help on using the changeset viewer.