| Server IP : 38.190.208.107 / Your IP : 216.73.217.46 Web Server : nginx/1.28.1 System : Linux ht2026040333114 6.1.0-10-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.37-1 (2023-07-03) x86_64 User : root ( 0) PHP Version : 8.2.28 Disable Function : passthru,exec,system,putenv,chroot,chgrp,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv,eval,assert,passthru,system,exec,shell_exec,popen,proc_open MySQL : OFF | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /www/wwwroot/test20.chuangfenglink.xyz/wp-content/plugins/wpqiniu/ |
Upload File : |
<?php
/**
Plugin Name: WPQiNiu(七牛云对象存储插件)
Plugin URI: https://www.lezaiyun.com/1097.html
Description: WordPress同步附件内容远程至七牛云对象存储中,实现网站数据与静态资源分离,提高网站加载速度。微信公众号: <font color="red">老蒋朋友圈</font>
Version: 5.0
Author: 老蒋和他的小伙伴
Author URI: https://www.lezaiyun.com
*/
if (!defined('ABSPATH')) die();
// 要求 PHP 7.4 及以上版本
if (version_compare(PHP_VERSION, '7.4.0', '<')) {
add_action('admin_notices', function () {
echo '<div class="notice notice-error"><p>WPQiNiu 插件需要 PHP 7.4 或更高版本,当前版本:' . esc_html(PHP_VERSION) . '。请升级 PHP 后启用。</p></div>';
});
return;
}
if (!class_exists('WPQiNiu')) {
class WPQiNiu {
private $option_name = 'wpqiniu_options'; // 插件参数保存名称
private $menu_title = '七牛云存储设置'; // 设置菜单的菜单名
private $page_title = '七牛云存储设置'; // 设置菜单的页面title
private $capability = 'manage_options'; // 设置页面管理所需权限
private $version = '5.0'; // 插件数据版本, 每次修改应与上方的Version值相同
private $setting_notices = [
'update_success' => 'WP七牛插件设置保存完毕', // post数据保存成功时提示内容
'update_failed' => 'WP七牛插件设置更新失败', // 失败时提示
];
private $base_folder;
private $wp_upload_dir;
private $object_storage;
private $options;
// 允许上传的图片后缀(核心修改)
private $allow_image_exts = ['jpg','jpeg','png','gif','webp','svg','ico','bmp','tiff','mp4'];
function __construct() {
# 插件 activation 函数当一个插件在 WordPress 中”activated(启用)”时被触发。
register_activation_hook(__FILE__, array($this, 'init_options'));
register_deactivation_hook(__FILE__, array($this, 'restore_options')); # 禁用时触发钩子
$this->includes();
$this->constants();
# 避免上传插件/主题被同步到对象存储
$request_uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
if (strpos($request_uri, 'update.php') === false) {
add_filter('wp_handle_upload', array($this, 'upload_attachments'));
if ( version_compare(get_bloginfo('version'), 5.3, '<') ){
add_filter( 'wp_update_attachment_metadata', array($this, 'upload_and_thumbs') );
} else {
add_filter( 'wp_generate_attachment_metadata', array($this, 'upload_and_thumbs') );
add_filter( 'wp_save_image_editor_file', array($this, 'save_image_editor_file') );
}
}
# 检测不重复的文件名
add_filter('wp_unique_filename', array($this, 'unique_filename') );
# 删除文件时触发删除远端文件,该删除会默认删除缩略图
add_action('delete_attachment', array($this, 'delete_remote_attachment'));
# 添加插件设置菜单
add_action('admin_menu', array($this, 'admin_menu_setting'));
add_filter('plugin_action_links', array($this, 'setting_plugin_action_links'), 10, 2);
# 自动重命名
add_filter( 'sanitize_file_name', array($this, 'sanitize_file_name_handler'), 10, 1 );
}
private function includes() {
require_once('api.php');
}
private function constants() {
$this->base_folder = plugin_basename(dirname(__FILE__));
$this->wp_upload_dir = wp_get_upload_dir();
$this->options = get_option($this->option_name);
# PHP7.4 版本后,对于bool值作为array调用时,会产生警告内容。
if (!is_array($this->options)) {
$this->init_options();
}
$this->object_storage = new QiNiuApi($this->options); // option更新后,若变动了七牛参数,则QiNiuApi实例的重新创建,目前只有setting中会触发
}
// 核心判断:仅允许图片文件上传(核心修改)
private function is_allowed_image_file($file_path) {
$ext = strtolower(pathinfo($file_path, PATHINFO_EXTENSION));
$filename = basename($file_path);
// 屏蔽 Woodmart 动态样式文件 xts-*.css
if (strpos($filename, 'xts-') === 0 && $ext === 'css') {
return false;
}
// 屏蔽所有 CSS/JS 文件
if (in_array($ext, ['css', 'js', 'html', 'htm', 'json'])) {
return false;
}
// 仅允许图片
return in_array($ext, $this->allow_image_exts);
}
/**
* 文件上传功能基础函数,被其它需要进行文件上传的模块调用
* @param $key : 远端需要的Key值[包含路径]
* @param $file_local_path : 文件在本地的路径。
*
* @return bool : 暂未想好如何与wp进行响应。
*/
public function _file_upload($key, $file_local_path) {
// 核心拦截:非图片文件直接不上传(核心修改)
if (!$this->is_allowed_image_file($file_local_path)) {
return true;
}
### 上传文件
# 由于增加了独立文件名钩子对cos中同名文件的判断,避免同名文件的存在,因此这里直接覆盖上传。
try {
$this->object_storage->Upload(
$this->key_handler($key, get_option('upload_url_path')),
$file_local_path
);
// 如果上传成功,且不再本地保存,在此删除本地文件
if ($this->options['no_local_file']) {
$this->delete_local_file($file_local_path);
}
return True;
} catch (\Exception $e) {
return False;
}
}
private function remote_key_exist( $filename ) {
return $this->object_storage->hasExist( $this->key_handler($this->wp_upload_dir['subdir'] . "/$filename",
get_option('upload_url_path')));
}
/**
* 删除远程附件(包括图片的原图)
* 这里全部以非/开头,因此上传的函数中也要替换掉key中开头的/
* @param $post_id
*/
public function delete_remote_attachment($post_id) {
// 获取要删除的对象Key的数组
$deleteObjects = array();
$meta = wp_get_attachment_metadata( $post_id );
$upload_url_path = get_option('upload_url_path');
if (isset($meta['file'])) {
$attachment_key = $meta['file'];
array_push($deleteObjects, $this->key_handler($attachment_key, $upload_url_path));
} else {
$file = get_attached_file( $post_id );
$attached_key = str_replace( $this->wp_upload_dir['basedir'] . '/', '', $file ); # 不能以/开头
$deleteObjects[] = $this->key_handler($attached_key, $upload_url_path);
}
if (isset($meta['sizes']) && count($meta['sizes']) > 0) {
foreach ($meta['sizes'] as $val) {
$attachment_thumbs_key = dirname($meta['file']) . '/' . $val['file'];
$deleteObjects[] = $this->key_handler($attachment_thumbs_key, $upload_url_path);
}
}
if ( !empty( $deleteObjects ) ) {
// 执行删除远程对象
$allKeys = array_chunk($deleteObjects, 1000); # 每次最多删除1000个,多于1000循环进行
foreach ($allKeys as $keys){
//删除文件, 每个数组1000个元素
$this->object_storage->Delete($keys);
}
}
}
// 初始化选项
// TODO: 让不同对象存储适用相同参数与setting
public function init_options() {
$options = array(
'version' => $this->version, # 用于以后当有数据结构升级时初始化数据
'bucket' => "",
'accessKey' => "",
'secretKey' => "",
'no_local_file' => False, # 不在本地保留备份
'backup_url_path' => '',
'opt' => array(
'auto_rename' => False,
),
);
if(!$this->options){
if (add_option($this->option_name, $options, '', 'yes')) {
$this->options = get_option($this->option_name);
}
}
if ( isset($this->options['backup_url_path']) && $this->options['backup_url_path'] != '' ) {
update_option('upload_url_path', $this->options['backup_url_path']);
// 理论上来说,更新完upload_url_path后,这里的option的backup_url_path还需要修改为'';
// 但因为时机上目前只有激活与禁用2种,因此就由禁用时直接赋值,这里减少一次更新。
// 后续出现多种场景判断再考虑。
}
}
public function restore_options () {
$this->options['backup_url_path'] = get_option('upload_url_path');
if (update_option($this->option_name, $this->options)) { // 此处修改的参数不影响对象存储实例
$this->options = get_option($this->option_name); // 上面的赋值及更新,这里似乎不用再重新获取。 - -!
}
update_option('upload_url_path', '');
}
/**
* 此函数处理上传的key,用于支持 对象存储子目录
* @param $key
* @param $upload_url_path
* @return string
*/
private function key_handler($key, $upload_url_path){
# 参数2 为了减少option的获取次数
$url_parse = is_string($upload_url_path) ? wp_parse_url($upload_url_path) : false;
# 约定url不要以/结尾,减少判断条件(PHP 8+ 中 wp_parse_url 空值可能返回 false)
if (is_array($url_parse) && array_key_exists('path', $url_parse)) {
if ( substr($key, 0, 1) == '/' ) {
$key = $url_parse['path'] . $key;
} else {
$key = $url_parse['path'] . '/' . $key;
}
}
# $url_parse['path'] 以/开头,在七牛环境下不能以/开头,所以需要处理掉
return ltrim($key, '/');
}
/**
* 删除本地文件
* @param $file_path : 文件路径
* @return bool
*/
public function delete_local_file($file_path) {
try {
if (!@file_exists($file_path)) { # 文件不存在
return TRUE;
}
if (!@unlink($file_path)) { # 删除文件
return FALSE;
}
return TRUE;
} catch (\Exception $ex) {
return FALSE;
}
}
/**
* 上传图片及缩略图
* @param $metadata: 附件元数据
* @return array $metadata: 附件元数据
* 官方的钩子文档上写了可以添加 $attachment_id 参数,但实际测试过程中部分wp接收到不存在的参数时会报错,上传失败,返回报错为“HTTP错误”
*/
public function upload_and_thumbs( $metadata ) {
if (isset( $metadata['file'] )) {
$attachment_local_path = $this->wp_upload_dir['basedir'] . '/' . $metadata['file'];
// 仅上传图片文件
if ($this->is_allowed_image_file($attachment_local_path)) {
# 1.先上传主图
$attachment_key = $metadata['file'];
$this->_file_upload($attachment_key, $attachment_local_path);
}
}
# 如果存在缩略图则上传缩略图
if (isset($metadata['sizes']) && count($metadata['sizes']) > 0) {
foreach ($metadata['sizes'] as $val) {
$attachment_thumbs_local_path = $this->wp_upload_dir['basedir'] . '/' . dirname($metadata['file']) . '/' . $val['file'];
if ($this->is_allowed_image_file($attachment_thumbs_local_path)) {
$attachment_thumbs_key = dirname($metadata['file']) . '/' . $val['file'];
$this->_file_upload($attachment_thumbs_key, $attachment_thumbs_local_path);
}
}
}
return $metadata;
}
/**
* @param array $upload {
* Array of upload data.
*
* @type string $file Filename of the newly-uploaded file.
* @type string $url URL of the uploaded file.
* @type string $type File type.
* @return array $upload
*/
public function upload_attachments ($upload) {
// 非图片文件直接返回,不上传七牛
if (!$this->is_allowed_image_file($upload['file'])) {
return $upload;
}
$mime_types = get_allowed_mime_types();
$image_mime_types = array(
// Image formats.
$mime_types['jpg|jpeg|jpe'],
$mime_types['gif'],
$mime_types['png'],
$mime_types['bmp'],
$mime_types['tiff|tif'],
$mime_types['ico'],
);
if ( ! in_array( $upload['type'], $image_mime_types ) ) {
$key = str_replace( $this->wp_upload_dir['basedir'] . '/', '', $upload['file'] );
$local_path = $upload['file'];
$this->_file_upload( $key, $local_path);
}
return $upload;
}
public function save_image_editor_file($override){
add_filter( 'wp_update_attachment_metadata', array($this,'image_editor_file_save' ));
return $override;
}
public function image_editor_file_save( $metadata ){
$metadata = $this->upload_and_thumbs($metadata);
remove_filter( 'wp_update_attachment_metadata', array($this, 'image_editor_file_save') );
return $metadata;
}
/**
* Filters the result when generating a unique file name.
*
* @since 4.5.0
*
* @param string $filename Unique file name.
* @return string New filename, if given wasn't unique
*
* 参数 $ext 在官方钩子文档中可以使用,部分 WP 版本因为多了这个参数就会报错。 返回“HTTP错误”
*/
public function unique_filename( $filename ) {
$ext = '.' . pathinfo( $filename, PATHINFO_EXTENSION);
// 非图片不校验远程重名
if (!$this->is_allowed_image_file($filename)) {
return $filename;
}
$number = '';
while ( $this->remote_key_exist( $filename ) ) {
$new_number = (int) $number + 1;
if ( '' == "$number$ext" ) {
$filename = "$filename-" . $new_number;
} else {
$filename = str_replace( array( "-$number$ext", "$number$ext" ), '-' . $new_number . $ext, $filename );
}
$number = $new_number;
}
return $filename;
}
public function sanitize_file_name_handler( $filename ){
if (!empty($this->options['opt']['auto_rename'])) {
return date("YmdHis") . "" . mt_rand(100, 999) . "." . pathinfo($filename, PATHINFO_EXTENSION);
} else {
return $filename;
}
}
/** 根据提交数据进行缩略图设置修改与备份。 (暂时取消在这一步对插件参数更新的步骤,留到后面一起进行更新)
* @param $options
* @param $set_thumb
* @return mixed
*/
private function set_thumbsize_handler($options, $set_thumb){
if($set_thumb) {
$options['opt']['thumbsize'] = array(
'thumbnail_size_w' => get_option('thumbnail_size_w'),
'thumbnail_size_h' => get_option('thumbnail_size_h'),
'medium_size_w' => get_option('medium_size_w'),
'medium_size_h' => get_option('medium_size_h'),
'large_size_w' => get_option('large_size_w'),
'large_size_h' => get_option('large_size_h'),
'medium_large_size_w' => get_option('medium_large_size_w'),
'medium_large_size_h' => get_option('medium_large_size_h'),
);
update_option('thumbnail_size_w', 0);
update_option('thumbnail_size_h', 0);
update_option('medium_size_w', 0);
update_option('medium_size_h', 0);
update_option('large_size_w', 0);
update_option('large_size_h', 0);
update_option('medium_large_size_w', 0);
update_option('medium_large_size_h', 0);
} else {
if(isset($options['opt']['thumbsize'])) {
update_option('thumbnail_size_w', $options['opt']['thumbsize']['thumbnail_size_w']);
update_option('thumbnail_size_h', $options['opt']['thumbsize']['thumbnail_size_h']);
update_option('medium_size_w', $options['opt']['thumbsize']['medium_size_w']);
update_option('medium_size_h', $options['opt']['thumbsize']['medium_size_h']);
update_option('large_size_w', $options['opt']['thumbsize']['large_size_w']);
update_option('large_size_h', $options['opt']['thumbsize']['large_size_h']);
update_option('medium_large_size_w', $options['opt']['thumbsize']['medium_large_size_w']);
update_option('medium_large_size_h', $options['opt']['thumbsize']['medium_large_size_h']);
unset($options['opt']['thumbsize']);
}
}
return $options;
}
// 在插件列表页添加设置按钮
public function setting_plugin_action_links($links, $file) {
if ($file == plugin_basename(dirname(__FILE__) . '/index.php')) {
$links[] = '<a href="admin.php?page=' . $this->base_folder . '/index.php">设置</a>';
}
return $links;
}
// 在导航栏“设置”中添加条目
public function admin_menu_setting() {
add_options_page($this->page_title, $this->menu_title, $this->capability, __FILE__, array($this, 'setting_page'));
}
/**
* 插件设置页面
*/
public function setting_page() {
// 如果当前用户权限不足
if (!current_user_can( $this->capability )) wp_die('Insufficient privileges!');
$this->options = get_option($this->option_name);
$nonce_valid = isset($_POST['wpqiniu_nonce']) && wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['wpqiniu_nonce'])), 'wpqiniu_settings');
if ($this->options && $nonce_valid && !empty($_POST) && isset($_POST['type']) && $_POST['type'] == 'info_set') {
$this->options['no_local_file'] = isset($_POST['no_local_file']);
$this->options['bucket'] = isset($_POST['bucket']) ? sanitize_text_field(trim(stripslashes($_POST['bucket']))) : '';
$this->options['accessKey'] = isset($_POST['accessKey']) ? sanitize_text_field(trim(stripslashes($_POST['accessKey']))) : '';
$this->options['secretKey'] = isset($_POST['secretKey']) ? sanitize_text_field(trim(stripslashes($_POST['secretKey']))) : '';
$this->options['opt']['auto_rename'] = isset($_POST['auto_rename']);
$this->options = $this->set_thumbsize_handler($this->options, isset($_POST['disable_thumb']) );
$upload_path = isset($_POST['upload_url_path']) ? trim(stripslashes($_POST['upload_url_path'])) : '';
update_option('upload_url_path', esc_url_raw($upload_path));
update_option($this->option_name, $this->options);
$this->object_storage = new QiNiuApi($this->options);
?>
<div class="notice notice-success settings-error is-dismissible"><p><?php echo esc_html($this->setting_notices['update_success']); ?></p></div>
<?php
}
require_once('setting.php');
}
}
global $WPQiNiu;
$WPQiNiu = new WPQiNiu();
}