| 1 |
<?php |
| 2 |
/* |
| 3 |
Name: CDN 加速 |
| 4 |
URI: https://mp.weixin.qq.com/s/bie4JkmExgULgvEgx-AjUw |
| 5 |
Description: CDN 加速使用云存储对博客的静态资源进行 CDN 加速。 |
| 6 |
Version: 2.0 |
| 7 |
*/ |
| 8 |
class WPJAM_CDN{ |
| 9 |
public static function __callStatic($method, $args){ |
| 10 |
if($method == 'get_setting'){ |
| 11 |
$args[0] = ['dx'=>'distance.width', 'dy'=>'distance.height', 'wm_width'=>'wm_size.width', 'wm_height'=>'wm_size.height'][$args[0]] ?? $args[0]; |
| 12 |
} |
| 13 |
|
| 14 |
$result = wpjam_call_option(self::class, $method, ...$args); |
| 15 |
|
| 16 |
return $method == 'get_setting' && $args[0] == 'watermark' ? wpjam_at($result, '?', 0) : $result; |
| 17 |
} |
| 18 |
|
| 19 |
public static function get_sections(){ |
| 20 |
$cdn_fields = [ |
| 21 |
'cdn_name' => ['title'=>'云存储', 'type'=>'select', 'options'=>[''=>'请选择']+self::get_items()+['disabled'=>['title'=>'切回本站', 'description'=>'当使用 CDN 之后想切换回使用本站图片才勾选该选项,并将原 CDN 域名填到「本地设置」的「额外域名」中。']]], |
| 22 |
'host' => ['title'=>'CDN 域名', 'type'=>'url', 'description'=>'设置为在CDN云存储绑定的域名。', 'show_if'=>['cdn_name', self::get_by_support()]], |
| 23 |
'image' => ['title'=>'图片处理', 'class'=>'switch', 'value'=>1, 'show_if'=>['cdn_name', self::get_by_support('image')], 'label'=>'开启云存储图片处理功能,使用云存储进行裁图、添加水印等操作。<br /><strong>开启之后,文章和媒体库中的所有图片都会镜像到云存储。</strong>'], |
| 24 |
]; |
| 25 |
|
| 26 |
$local_fields = [ |
| 27 |
'local' => ['title'=>'本地域名', 'type'=>'url', 'description'=>'并将该域名填� |
| 28 |
�<strong>云存储的镜像源</strong>', 'value'=>home_url()], |
| 29 |
'exts' => ['title'=>'扩展名', 'fields'=>[ |
| 30 |
'img_exts' => ['label'=>'所有图片扩展名,如已开启云存储图片处理功能,则该选项自动开启!'], |
| 31 |
'exts' => ['type'=>'mu-text', 'button_text'=>'添加扩展名', 'direction'=>'row', 'sortable'=>false], |
| 32 |
], 'description'=>'镜像到云存储的静态文件扩展名'], |
| 33 |
'dirs' => ['title'=>'目录', 'type'=>'mu-text', 'direction'=>'row', 'sortable'=>false, 'style'=>'width:120px;', 'value'=>['wp-content','wp-includes'], 'description'=>'镜像到云存储的静态文件所在目录'], |
| 34 |
'locals' => ['title'=>'额外域名', 'type'=>'mu-text', 'item_type'=>'url'], |
| 35 |
]; |
| 36 |
|
| 37 |
$sections = [ |
| 38 |
'cdn' => ['title'=>'云存储设置', 'fields'=>$cdn_fields], |
| 39 |
'local' => ['title'=>'本地设置', 'fields'=>$local_fields], |
| 40 |
]; |
| 41 |
|
| 42 |
if(is_network_admin()){ |
| 43 |
return wpjam_except($sections, 'local[fields][local]'); |
| 44 |
} |
| 45 |
|
| 46 |
if(!($external = wpjam_basic_get_setting('upload_external_images')) && !is_multisite() && $GLOBALS['wp_rewrite']->using_mod_rewrite_permalinks() && extension_loaded('gd')){ |
| 47 |
$remote_summary = '*自动将外部图片镜像到云存储需要博客支持固定链接和服务器支持GD库(不支持gif图片)'; |
| 48 |
$remote_fields = ['remote'=>['title'=>'外部图片', 'options'=>[''=>'� |
| 49 |
�闭外部图片镜像到云存储', '1'=>'自动将外部图片镜像到云存储(不推荐)']]]; |
| 50 |
}else{ |
| 51 |
$remote_fields = ['external'=>['title'=>'外部图片', 'type'=>'view', 'description'=>($external ? '已在' : '请� |
| 52 |
�到').'「文章设置」中开启「支持在文章列表页上传外部图片」']]; |
| 53 |
} |
| 54 |
|
| 55 |
$remote_fields += ['exceptions'=>['title'=>'例外', 'type'=>'textarea', 'description'=>'如果外部图片的链接中� |
| 56 |
含以上字符串或域名,就不会被保存并镜像到云存储。']]; |
| 57 |
|
| 58 |
$image_fields = [ |
| 59 |
'thumb' => ['title'=>'缩图设置', 'fields'=>[ |
| 60 |
'no_subsizes' => ['value'=>1, 'label'=>'使用云存储的缩图功能,本地不再生成各种尺寸的缩略图。'], |
| 61 |
'thumbnail' => ['value'=>1, 'label'=>'使用云存储的缩图功能对文章中的图片进行最佳尺寸显示处理。', 'fields'=>[ |
| 62 |
'max_width' => ['value'=>($GLOBALS['content_width'] ?? 0), 'type'=>'number', 'class'=>'small-text', 'before'=>'文章中图片最大宽度:', 'after'=>'px。'] |
| 63 |
]] |
| 64 |
]], |
| 65 |
'format' => ['title'=>'格式质量', 'fields'=>[ |
| 66 |
'webp' => ['label'=>'将图片转换成 WebP 格式。', 'show_if'=>['cdn_name', self::get_by_support('webp')]], |
| 67 |
'interlace' => ['label'=>'JPEG格式图片渐进显示。', 'show_if'=>['cdn_name', self::get_by_support('quality')]], |
| 68 |
'quality' => ['type'=>'number', 'before'=>'图片质量:', 'class'=>'small-text', 'mim'=>0, 'max'=>100, 'show_if'=>['cdn_name', self::get_by_support('quality')]] |
| 69 |
]], |
| 70 |
'wm' => ['title'=>'水印设置', 'show_if'=>['cdn_name', self::get_by_support('wm')], 'fields'=>[ |
| 71 |
'view' => ['type'=>'view', 'title'=>'使用说明:', 'value'=>'请使用云存储域名下的图片,水印设置� |
| 72 |
应用于文章� |
| 73 |
容中的图片'], |
| 74 |
'watermark' => ['type'=>'image', 'title'=>'水印图片:'], |
| 75 |
'dissolve' => ['type'=>'number', 'title'=>'透明度:', 'class'=>'small-text', 'value'=>100, 'min'=>1, 'max'=>100], |
| 76 |
'gravity' => ['type'=>'select', 'title'=>'水印位置:', 'options'=>['SouthEast'=>'右下角', 'SouthWest'=>'左下角', 'NorthEast'=>'右上角', 'NorthWest'=>'左上角', 'Center'=>'正中间', 'West'=>'左中间', 'East'=>'右中间', 'North'=>'上中间', 'South'=>'下中间']], |
| 77 |
'distance' => ['type'=>'size', 'title'=>'水印边距:', 'fields'=>['width'=>['value'=>10], 'height'=>['value'=>10]]], |
| 78 |
'wm_size' => ['type'=>'size', 'title'=>'最小尺寸:', 'description'=>'小于该尺寸的图片都不会加上水印', 'show_if'=>['cdn_name', self::get_by_support('wm_size')]] |
| 79 |
]], |
| 80 |
|
| 81 |
'volc_imagex_template' => ['title'=>'火山引擎图片处理模板', 'show_if'=>['cdn_name', 'volc_imagex']] |
| 82 |
]; |
| 83 |
|
| 84 |
return $sections+[ |
| 85 |
'image' => ['title'=>'图片设置', 'fields'=>$image_fields, 'show_if'=>['image', 1]], |
| 86 |
'remote' => ['title'=>'外部图片', 'fields'=>$remote_fields, 'summary'=>$remote_summary ?? '', 'show_if'=>['cdn_name', self::get_by_support('wm')]], |
| 87 |
]; |
| 88 |
} |
| 89 |
|
| 90 |
public static function get_by_support($feature=null){ |
| 91 |
return array_keys(array_filter(self::get_items(), fn($item)=> $feature ? in_array($feature, $item['supports'] ?? []) : true)); |
| 92 |
} |
| 93 |
|
| 94 |
public static function is($url){ |
| 95 |
$host = '//'.explode('//', CDN_HOST, 2)[1]; |
| 96 |
|
| 97 |
return apply_filters('wpjam_is_cdn_url', array_any(['http:', 'https:', ''], fn($v)=> str_starts_with($url, $v.$host)), $url); |
| 98 |
} |
| 99 |
|
| 100 |
public static function downsize($size, $args){ |
| 101 |
$attr = ['width', 'height']; |
| 102 |
|
| 103 |
[$meta, $url] = is_numeric($args) ? [wp_get_attachment_metadata($args), wp_get_attachment_url($args)] : [$args, $args['url']]; |
| 104 |
|
| 105 |
if(is_array($meta) && array_all($attr, fn($k)=> isset($meta[$k]))){ |
| 106 |
$size = wpjam_parse_size($size, 2); |
| 107 |
$size = $size['crop'] ? array_map(fn($k)=> min($size[$k], $meta[$k]), $attr) : wp_constrain_dimensions(...array_merge(...array_map(fn($v)=> array_values(wpjam_pick($v, $attr)), [$meta, $size]))); |
| 108 |
|
| 109 |
if(array_any($attr, fn($k, $i)=> $size[$i] < $meta[$k])){ |
| 110 |
return [wpjam_get_thumbnail($url, $size), (int)($size[0]/2), (int)($size[1]/2), true]; |
| 111 |
}elseif(is_numeric($args)){ |
| 112 |
return [$url, ...$size, false]; |
| 113 |
} |
| 114 |
} |
| 115 |
} |
| 116 |
|
| 117 |
public static function replace($str, $to_cdn=true, $html=false){ |
| 118 |
$locals = self::get_arg('locals'); |
| 119 |
$args = $to_cdn ? [$locals, CDN_HOST] : [array_slice($locals, 0, -2), LOCAL_HOST]; |
| 120 |
|
| 121 |
if($html){ |
| 122 |
return strtr($str, array_fill_keys(...$args)); |
| 123 |
} |
| 124 |
|
| 125 |
foreach($args[0] as $v){ |
| 126 |
if(try_prefix($str, '-', $v)){ |
| 127 |
return $args[1].$str; |
| 128 |
} |
| 129 |
} |
| 130 |
|
| 131 |
return $str; |
| 132 |
} |
| 133 |
|
| 134 |
public static function filter_metadata($meta, $id){ |
| 135 |
if(is_array($meta) && empty($meta['sizes']) && wp_attachment_is_image($id)){ |
| 136 |
$args = ['url'=>wp_get_attachment_url($id)]+$meta; |
| 137 |
$orient = $meta['width'] > $meta['height'] ? 'portrait' : 'landscape'; |
| 138 |
|
| 139 |
foreach(wp_get_registered_image_subsizes() as $name => $size){ |
| 140 |
if($size = self::downsize($size, $args)){ |
| 141 |
$arr = explode('?', $size[0]); |
| 142 |
$file = implode('?', [wp_basename(array_shift($arr)), ...$arr]); |
| 143 |
|
| 144 |
$meta['size'][$name] = ['file'=>$file, 'url'=>$size[0], 'width'=>$size[1], 'height'=>$size[2], 'orientation'=>$orient]; |
| 145 |
} |
| 146 |
} |
| 147 |
} |
| 148 |
|
| 149 |
return $meta; |
| 150 |
} |
| 151 |
|
| 152 |
public static function filter_image_block($content, $parsed){ |
| 153 |
return str_replace('<img', '<img'.array_reduce(['sizeSlug', 'width', 'height'], fn($c, $k)=> $c.(($v = $parsed['attrs'][$k] ?? '') ? ' data-'.$k.'="'.$v.'"' : ''), ''), $content); |
| 154 |
} |
| 155 |
|
| 156 |
public static function filter_img_tag($tag, $context, $id){ |
| 157 |
$proc = $context == 'the_content' ? new WP_HTML_Tag_Processor($tag) : ''; |
| 158 |
$src = $proc && $proc->next_tag('img') ? $proc->get_attribute('src') : ''; |
| 159 |
$src = self::replace($src, true, false); |
| 160 |
|
| 161 |
if(!$src || wpjam_is_external_url($src)){ |
| 162 |
return $tag; |
| 163 |
} |
| 164 |
|
| 165 |
$attr = ['width', 'height']; |
| 166 |
$size = wpjam_fill($attr, fn($k)=> $proc->get_attribute($k) ?: (float)$proc->get_attribute('data-'.$k)); |
| 167 |
$max = (int)self::get_setting('max_width', ($GLOBALS['content_width'] ?? 0)); |
| 168 |
$meta = $id ? wp_get_attachment_metadata($id) : []; |
| 169 |
$meta = is_array($meta) ? $meta : []; |
| 170 |
$size = array_filter($size) ? $size : ($meta ? wpjam_pick((wpjam_get($meta, 'sizes.'.($proc->get_attribute('data-sizeSlug') ?: 'full')) ?: $meta), $attr) : ['width'=>$max])+$size; |
| 171 |
|
| 172 |
if(array_all($size, fn($v)=> is_numeric($v))){ |
| 173 |
if($max && $size['width'] > $max){ |
| 174 |
$size = ['width'=>$max, 'height'=>(int)($max/$size['width']*$size['height'])]; |
| 175 |
|
| 176 |
array_walk($size, fn($v, $k)=> $v ? $proc->set_attribute($k, $v) : null); |
| 177 |
} |
| 178 |
|
| 179 |
$size = $meta ? wpjam_except($size, array_filter($attr, fn($k)=> $size[$k]*2 >= $meta[$k])) : $size; |
| 180 |
} |
| 181 |
|
| 182 |
$proc->set_attribute('src', wpjam_get_thumbnail($src, wpjam_parse_size($size+['content'=>true], 2))); |
| 183 |
|
| 184 |
return $proc->get_updated_html(); |
| 185 |
} |
| 186 |
|
| 187 |
public static function on_plugins_loaded(){ |
| 188 |
define('CDN_NAME', self::get_setting('cdn_name')); |
| 189 |
define('CDN_HOST', untrailingslashit(self::get_setting('host') ?: site_url())); |
| 190 |
define('LOCAL_HOST', untrailingslashit(set_url_scheme(self::get_setting('local') ?: site_url()))); |
| 191 |
|
| 192 |
$hosts = wpjam_map([LOCAL_HOST, CDN_HOST], fn($v)=> substr_replace($v, ...($v[4] === 's' ? ['', 4, 1] : ['s', 4, 0]))); |
| 193 |
$locals = [$hosts[0], ...array_map('untrailingslashit', self::get_setting('locals') ?: [])]; |
| 194 |
$object = self::update_arg('locals', [...array_unique(apply_filters('wpjam_cdn_local_hosts', $locals)), $hosts[1], LOCAL_HOST]); |
| 195 |
|
| 196 |
if(CDN_NAME === 'disabled' || !CDN_NAME){ |
| 197 |
return CDN_NAME ? wpjam_hooks('the_content, wpjam_thumbnail'.((is_admin() || wpjam_is_json_request()) ? '' : ', wpjam_html'), fn($html)=> self::replace($html, false, true), 5) : null; |
| 198 |
} |
| 199 |
|
| 200 |
do_action('wpjam_cdn_loaded'); |
| 201 |
|
| 202 |
$exts = array_diff(array_map('trim', self::get_setting('exts') ?: []), is_login() ? ['js', 'css', ''] : ['']); |
| 203 |
$exts = array_unique(array_merge($exts, self::get_setting('img_exts') ? wp_get_ext_types()['image'] : [])); |
| 204 |
|
| 205 |
wpjam_hooks([ |
| 206 |
['wp_resource_hints', '+', fn($type)=> $type == 'dns-prefetch' ? [CDN_HOST] : [], 10, 2], |
| 207 |
['wpjam_is_external_url', fn($status, $url, $scene)=> $status && !wpjam_is_cdn_url($url) && ($scene != 'fetch' || !array_any($object->exceptions ??= wpjam_lines(self::get_setting('exceptions')), fn($v)=> str_contains($url, $v))), 10, 3], |
| 208 |
]); |
| 209 |
|
| 210 |
if(self::get_setting('image')){ |
| 211 |
$file = self::get_item(CDN_NAME.'[file]') ?? dirname(__DIR__).'/cdn'.'/'.CDN_NAME.'.php'; |
| 212 |
|
| 213 |
$file && file_exists($file) && ($cb = include $file) && is_callable($cb) && add_filter('wpjam_thumbnail', $cb, 10, 2); |
| 214 |
|
| 215 |
self::get_setting('no_subsizes', 1) && wpjam_hooks([ |
| 216 |
['wp_calculate_image_srcset_meta', []], |
| 217 |
['embed_thumbnail_image_size', '160x120'], |
| 218 |
['intermediate_image_sizes_advanced', fn($sizes)=> wpjam_pick($sizes, ['full'])], |
| 219 |
['wp_get_attachment_metadata', [self::class, 'filter_metadata'], 10, 2], |
| 220 |
['wp_img_tag_add_srcset_and_sizes_attr, wp_img_tag_add_width_and_height_attr', false] |
| 221 |
]); |
| 222 |
|
| 223 |
self::get_setting('thumbnail', 1) && wpjam_hooks([ |
| 224 |
['render_block_core/image', [self::class, 'filter_image_block'], 5, 2], |
| 225 |
['wp_content_img_tag', [self::class, 'filter_img_tag'], 1, 3] |
| 226 |
]); |
| 227 |
|
| 228 |
wpjam_hooks([ |
| 229 |
['wpjam_thumbnail, wp_mime_type_icon', [self::class, 'replace'], 1], |
| 230 |
|
| 231 |
['wp_get_attachment_url', fn($url, $id)=> in_array(wpjam_file($id, 'ext'), $exts) ? self::replace($url) : $url, 10, 2], |
| 232 |
['image_downsize', fn($downsize, $id, $size)=> wp_attachment_is_image($id) ? self::downsize($size, $id) : $downsize, 10, 3] |
| 233 |
]); |
| 234 |
} |
| 235 |
|
| 236 |
if($exts && !is_admin() && !wpjam_is_json_request()){ |
| 237 |
$local = '(?:https?\://|//)'.preg_quote(explode('//', LOCAL_HOST, 2)[1]); |
| 238 |
$dirs = wpjam_join('|', wpjam_map(self::get_setting('dirs') ?: [], fn($v)=> preg_quote(trim($v)))); |
| 239 |
$regex = '#(?:'.$local.')/('.($dirs ? '(?:'.$dirs.')/' : '').'[^;"\'\s\?\>\<]+\.(?:'.wpjam_join('|', $exts).')["\'\)\s\]\?])#'; |
| 240 |
|
| 241 |
add_filter('wpjam_html', fn($html)=> wpjam_preg_replace($regex, CDN_HOST.'/$1', self::replace($html, false, true)), 5); |
| 242 |
} |
| 243 |
|
| 244 |
self::get_setting('remote') && !wpjam_basic_get_setting('upload_external_images') && !is_multisite() && (include dirname(__DIR__).'/cdn/remote.php'); |
| 245 |
} |
| 246 |
|
| 247 |
public static function add_hooks(){ |
| 248 |
self::add_items([ |
| 249 |
'aliyun_oss' => [ |
| 250 |
'title' => '阿里云OSS', |
| 251 |
'supports' => ['image', 'webp', 'wm', 'wm_size', 'quality'], |
| 252 |
'description' => '请点击这里注册和申请<strong><a href="http://wpjam.com/go/aliyun/" target="_blank">阿里云</a></strong>可获得代金券,点击这里查看<strong><a href="https://blog.wpjam.com/m/aliyun-oss-cdn/" target="_blank">阿里云OSS详细使用指南</a></strong>。' |
| 253 |
], |
| 254 |
'qcloud_cos' => [ |
| 255 |
'title' => '� |
| 256 |
�讯云COS', |
| 257 |
'supports' => ['image', 'webp', 'wm', 'wm_size', 'quality'], |
| 258 |
'description' => '请点击这里注册和申请<strong><a href="http://wpjam.com/go/qcloud/" target="_blank">� |
| 259 |
�讯云</a></strong>可获得优惠券,点击这里查看<strong><a href="https://blog.wpjam.com/m/qcloud-cos-cdn/" target="_blank">� |
| 260 |
�讯云COS详细使用指南</a></strong>。' |
| 261 |
], |
| 262 |
'volc_imagex' => [ |
| 263 |
'title' => '火山引擎veImageX', |
| 264 |
'supports' => ['image', 'webp'], |
| 265 |
'description' => '点击这里查看<strong><a href="http://blog.wpjam.com/m/volc-veimagex/" target="_blank">火山引擎 veImageX 详细使用指南</a></strong>。' |
| 266 |
], |
| 267 |
'qiniu' => [ |
| 268 |
'title' => '七牛云存储', |
| 269 |
'supports' => ['image', 'wm', 'quality'], |
| 270 |
], |
| 271 |
'ucloud' => ['title'=>'UCloud'] |
| 272 |
]); |
| 273 |
|
| 274 |
add_action('plugins_loaded', [self::class, 'on_plugins_loaded'], 99); |
| 275 |
} |
| 276 |
} |
| 277 |
|
| 278 |
function wpjam_register_cdn($name, $args){ |
| 279 |
return WPJAM_CDN::add_item($name, $args); |
| 280 |
} |
| 281 |
|
| 282 |
function wpjam_unregister_cdn($name){ |
| 283 |
return WPJAM_CDN::delete_item($name); |
| 284 |
} |
| 285 |
|
| 286 |
function wpjam_cdn_get_setting($name, ...$args){ |
| 287 |
return WPJAM_CDN::get_setting($name, ...$args); |
| 288 |
} |
| 289 |
|
| 290 |
function wpjam_is_cdn_url($url){ |
| 291 |
return WPJAM_CDN::is($url); |
| 292 |
} |
| 293 |
|
| 294 |
function wpjam_cdn_host_replace($html, $to_cdn=true){ |
| 295 |
return WPJAM_CDN::replace($html, $to_cdn, true); |
| 296 |
} |
| 297 |
|
| 298 |
function wpjam_local_host_replace($html){ |
| 299 |
return str_replace(CDN_HOST, LOCAL_HOST, $html); |
| 300 |
} |
| 301 |
|
| 302 |
wpjam_register_option('wpjam-cdn', [ |
| 303 |
'title' => 'CDN加速', |
| 304 |
'model' => 'WPJAM_CDN', |
| 305 |
'site_default' => true, |
| 306 |
'menu_page' => ['parent'=>'wpjam-basic', 'position'=>2, 'summary'=>__FILE__], |
| 307 |
]); |