PluginProbe
WPJAM Basic / trunk
WPJAM Basic vtrunk
wpjam-basic / components / wpjam-cdn.php

wpjam-cdn.php in WPJAM Basic trunk, at components/wpjam-cdn.php

307 lines 14.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 ]);