wordpress:设置从微信公众号复制文章到博客-解决图片失效和转存问题

NO.1
使用场景

把公众号的文章同步发布到博客

解决微信公众号图片外链失效问题

一键复制文章到剪切板并把图片转存到博客服务器(不使用公众号图片外链也不使用cdn)

NO.2
图片失效

从公众号复制文章,粘贴到博客编辑器后

会变成下图所示,提示未经允许不可引用

img

NO.3
插件开发

参考文章

WordPress自定义插件:代码高亮,允许内联样式,显示隐藏按钮

想了以下几种方法

一.使用chrome插件

  1. 使用chrome插件自带的API,获取剪切板的内容,并把图片处理

  2. 使用ffmpeg.wasm把图片压缩和转码为webp格式

  3. 使用当前活动窗口网站的cookie,调用wordpress rest api上传图片到媒体库

  4. 把文章中图片的地址替换为最终的图片地址

遇到的问题

除了cookie以外,wordpress还需要nonce参数 (防止 WordPress 网站受到 CSRF 攻击

这个nonce暂时没有方法从网站获取到,所以chrome插件排查

img

二.wordpress插件

如果是把图片存在cdn,那么不需要wordpress插件就可以通过js,python或者api等解决

但是要把剪切板中图片url或者base64,blob图片转存到wordpress,那么使用wordpress更为方便

直接写方法即可,不用考虑登录,权限,后期备份,迁移等问题

只需要安装插件即可

img

NO.4
开发过程

具体思路

  1. 监听wordpress中tinymce编辑器的复制粘贴事件

  2. 在复制前对剪切板中的数据进行图片查询,符合条件的将进行压缩和转码以及上传处理

  3. 图片处理后把成功的url进行替换

首先,在tinymce初始化中新加参数

add_filter('tiny_mce_before_init', 'custom_tinymce_config');

设置允许复制图片

$init['paste_data_images'] = "true";

设置粘贴后处理

此选项使您能够在将粘贴的内容插入编辑器之前但在将其解析为 DOM 结构之后对其进行修改

官网文档

https://www.tiny.cloud/docs/plugins/opensource/paste/

要特别注意:

官网文档演示的是JS的环境,但是本文是wordpress中php的环境

函数的声明是php中的字符串

 $init['paste_postprocess'] ="function (plugin, args) {}"

php错误演示

以下代码是我最初的写法,都是不对的,只有上面的代码才是正确的

function test(){}$init['paste_postprocess'] ="test"// or$init['paste_postprocess'] =test

为什么不选则粘贴预处理

因为tinymce数返回的参数args.content是字符串html,很难对指定标签进行查找或者过滤

而paste_postprocess中args.node是dom形式的nodelist,可以直接设置dom属性来改变样式和属性

paste_preprocess

查到复制内容dom中所有的img标签

使用querySelectorAll来查找img标签集合

let imgArr = args.node.querySelectorAll('img')

对集合做遍历

对每一个img标签设置id和图片加载错误事件监听

  1. 生成一个随机字符串作为img标签id,方便后面定位

  2. 把图片的源地址(如公众号图片地址)作为自定义属性放在标签上(data-origin)

  3. 把图片地址改为即将存放在博客服务器的地址(域名+文件夹+随机字符串+图片后缀)

  4. 设置图片加载错误监听,只要监听到错误,就把图片id放在window全局变量中(errImgArr)(暂时不对图片处理,后面统一按顺序进行队列处理,避免同时上传图片)


js生成随机字符串

function randomString(length) {        var str = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';        var result = '';        for (var i = length; i > 0; --i) {            result += str[Math.floor(Math.random() * str.length)];        }        return result;    }

设置图片自定义

let imgArr = args.node.querySelectorAll('img')    if (imgArr && imgArr.length > 0) {        window.errImgArr=[]        for (let i = 0; i < imgArr.length; ++i) {            let ranStr = randomString(10)            ranStrArr[i] = ranStr            imgArr[i].ranStr = ranStr            imgArr[i].imgUrl =  imgArr[i].getAttribute('src')            imgArr[i].setAttribute('data-origin', imgArr[i].getAttribute('src'))            imgArr[i].setAttribute('alt', `img`)            imgArr[i].setAttribute('id', ranStr)            imgArr[i].setAttribute('src', host+`/wp-content/uploads/images/`+ranStr+`.png`)            imgArr[i].setAttribute('data-src', host+`/wp-content/uploads/images/`+ranStr+`.png`)            imgArr[i].onerror =(e)=>{                console.log(`图片加载错误`,e,window)                window.errImgArr.push(imgArr[i].ranStr)            }        }    }

设置延时3到5秒后,查看window全局变量errImgArr

如果有值,则使用for循环同步并按顺序上传和设置正确图片地址

通过tinyMCE.activeEditor.contentDocument方法可以获取tinymce编辑器中的document

直接使用document获取的是博客管理后台的dom,非编辑器的dom

async function imgtoggle(){        if(window&&window.errImgArr&&window.errImgArr.length>0){            for (var i = 0; i<window.errImgArr.length ; i++) {                let domAll=tinyMCE.activeEditor.contentDocument                let imgDom=domAll.getElementById(window.errImgArr[i])                let srcUrl=imgDom.getAttribute('data-origin')                let idStr=imgDom.getAttribute('id')                imgDom.setAttribute('src',host+`/wp-content/uploads/images/`+`loading.gif`)                try{                    console.log(`开始`,i,`图片上传`,srcUrl,idStr)                    let imgRes=await saveImg(srcUrl,idStr)                    imgDom.setAttribute('src',imgRes.imgDomainUrl)                    imgDom.setAttribute('data-src',imgRes.imgDomainUrl)                }catch(err){                }            }        }    }    setTimeout(()=>{       imgtoggle()    },5000)

其中saveImg为图片上传到博客的php接口

saveImg函数

设置接口为同级目录的api.php

传递参数为原图片地址,和图片id(作为图片文件名)

注意这里使用post方法和formdata传参,是为了避免get参数过长和特殊字符问题

使用formdata方便php简单取值

function saveImg(imgUrl, ranStr) {        var host = document.location.origin        return new Promise((resolve, reject) => {            let formdata = new FormData();            formdata.append('imgUrl', imgUrl);            formdata.append('ranStr', ranStr);            const options = {                method: 'POST',                body: formdata,            };            fetch(host + '/wp-content/plugins/tinymceSet/' + 'api.php', options)                .then(data => {                    return data.json();                })                .then(res => {                    console.log('res', res);                    if (res && res.imgDomainUrl) {                        resolve(res)                    }                })        })    }

img

新建api.php在插件目录

使用$_POST[‘xxx’]获取参数

使用file_put_contents和file_get_contents开保存图片文件

<?php // 获取要转存的图片url$url=$_POST['imgUrl'];// 设置图片服务器存储地址$rand_str=$_POST['ranStr'];$img = '../../uploads/images/';$img .=$rand_str;$img .='.png';// 存储图片file_put_contents($img, file_get_contents($url));// 获取图片实际的域名地址$domain = $_SERVER['HTTP_HOST'];$imgDomainUrl='//';$imgDomainUrl.=$domain;$imgDomainUrl.='/wp-content/uploads/images/';$imgDomainUrl .=$rand_str;$imgDomainUrl .='.png';$arr = array('code' => 0, 'data' => $_POST["imgUrl"], 'msg' => 'success','domain' => $domain,'imgDomainUrl' => $imgDomainUrl );echo json_encode($arr);

注意,要想是api.php正常保存图片,图片目录的权限要设置正确

755权限无反应,777权限可以正常写入

img

到此为止

从公众号复制文章内容,然后复制到博客tinymce(经典编辑器)

可以完全复制内容,并且图片自动保存到博客并展示(不会出现外链不可使用等问题)

为了插件兼容别的博客,作为域名动态处理,上传的地址会匹配当前博客地址

var host = document.location.origin
NO.5
Tips

写这个插件预期解决以下问题

  1. 避免每次公众号文章同步到博客,因为图片问题,需要画10-30分钟来做复制编辑,设置插件后,只需要1-3分钟,文章越多,节省的时间越多

  2. 为后续自动化压缩图片做准备,一篇文章图片多了,访问速度相对会下降,且占据磁盘空间,后续可以使用js,php或者单独的api对图片自动压缩

  3. 为后续图片转码做准备,jpg,png,gif等转为webp格式,尺寸小,seo友好

END