まえおき・・・どうでもいいこと
この記事を書いているのは、2022年10月16日、日曜日のもう暗くなっている夕方。
WordPress の現行バージョンは 6.0 で次期バージョンの 6.1 はすでに RC1 がリリースされている。6.1 RC1 を試していて、BlockEditor において少し好ましくないことに遭遇する。
自作ブロックたちを、最新の apiVersion 2 にしておこうかと、あれこれといじってた。と、ちょっとひっかかることにでくわすことになる。同じ自作ブロックで、あるサイトでは問題なく apiV2 にできるのに別のサイトではエラーがでる。一体何がちがうのだろうか?と。
その少し前に、プラグインのアップデートをしていて、そのプラグインが備えているブロックはサーバーサイドレンダーなのだけれど、それはあっけなく apiV2 の指定をすることができたので、他も同じようにできるような感覚を持っていたが、それがちょっと甘かったよう。HTML の生成を php 側で行う SSR には、api は関係ないのだろう。
とあるサイトにおいて、クリックしても選択状態にならないブロックがあるのを見つけた。そのブロックをクリックしても親ブロックの方にフォーカスがいってしまい、目的のブロックはコンテンツの文字列を変更することも、サイドバーにある設定できる内容にも全くアクセスすることができない。
そのブロック自体は、少し前に、6.0 の時に作ったもの。サイドバーでインラインスタイルが設定でき、改行を許してくれる paragraph のブロック。これはすでに apiVersion 2。一方、それが入っている親もインナーブロックを使った自作ブロックなのだけれど、そちらは旧バージョンのブロックだった。もしやこれか!と、親のブロックに apiV2 を指定してみると(インナーブロックも問題なく、全く意味の無いことのように apiV2 にできる)、これがあっさりと問題は解決された。
しかし、WordPress の サイトにある Gutenberg Handbook の Nested Blocks: Using InnerBlocks のページにある雛形には apiVersion 2 の指定はない。と、いうことからも、インナーブロックにおいてはあまり関係なさそうなんだけれど、この場合は結果的にこれにて問題は解決されるので、なにかしらの影響は有るものと思われる。
6.0 のときは全く問題なく機能していたので、親ブロックのバージョンが古い事で発生するこの件は、 6.1 RC1 での問題点の一つなのだろうとは思うし、多く出ていたエラーが解消されたのだけれど、それでも先述のエラーが全て解消されたわけでもなかった。結局、そのエラーは save においてデフォルトの className を設定するやり方がだめだったようで、既存のブロックにおいてエディタの方で別のクラスを設定していると、React によるバリデーションエラーとなってしまっていた。
と、いうことをいろいろやっていると、どのブロックが生きていて、いらないものはどれだろうかと、サイトで実際に必要な自作ブロックを簡単に知る方法はないものか、と、いうことになる。ならば、簡単にできそうなのでいくつかのサイトで使えるようにプラグインにしてみるか、となった。標準機能としてそういう関数など無いものだろうかと、思ったりしたがよくわからない。まぁ、自作ブロックをたくさん作ってしまう人にしか必要のないものではある。
プラグイン Active Block List
プラグインとしては、サイトの投稿により使用されている、稼働状況にある block を簡単に知るために、その一覧を取得して表示する、というシンプルなもの。SQL で各投稿の post_content を取得して、正規表現にてコメントを使ったブロックの形式である、<!– wp:heading {“level”:3} –> の共通部分を取得すればいいだろう。
管理画面のツールメニューに項目を一つ追加して、このプラグイン用のページを表示するようにした。そのページにおいて、調べたい投稿のタイプ( post、page とかカスタム投稿とか複数も可)を指定して、Ajax にてデータベースからデータを取得し、表示する。
ただ、そのサイトにある投稿の数がものすごく多くと、サーバーに大きな負荷をかけそうなので、その数に制限をつけたほうがよさそう。自分のサイトもそうであるが、全投稿が BlockEditor ということでもないので、BlockEditor の投稿だけ、ということも選択肢に入れる、ということも有効そう。
BlockEditor の投稿ということは、何で判別できるのだろう。まぁ、「 post_content LIKE ‘%<!– wp:%’ 」でもいいのではあるが、これだと全投稿の本文が対象になるので、投稿数が多いと負荷が大きそうではある。何しろ投稿本文が検索対象であるからにして。しかし、使用するテーブルは wp-posts だけの一つで済む。といいつつ、それと判別するデータがあるはずでもある。
たぶん、それは postmeta テーブルにありそう、とは誰でもが思いつくこと。これ、いくつかのサイトで見てみると、classic-editor のプラグインを入れてあり、BlockEditor と ClassicEditor を使い分けているサイトにおいては、postmeta テーブルにそれを識別するためのデータが存在する。しかし、デフォルトのまま、BlockEditor だけを使っている場合は、使い分ける必要などないのでそんなデータも必要無く存在しない、ということのよう。
と、いうことで、後者を使うことにした。まずは classic-editor プラグインが稼働しているか?というのが一つめの条件となり、それによって、postmeta テーブルに block-editor という値があるか?というのが二つ目の条件として判別できそう。CSS も Javascript も php で書き出すようにしたので、このプラグインのファイルは、その php ファイルただひとつだけ。シンプルな機能であるし、管理画面内のことであるし、わざわざそれらのスクリプトを読み込むための処理を書くのもさせるのも負荷がかかるだけのことであるし、ファイルの読み込みも一つだけですむ。と、いうことで、いざ。
active_block_list.php
<?php
// クラスが定義済みか調べる
if ( ! class_exists( 'Active_Block_List' ) ) {
class Active_Block_List {
public function __construct() {
// Ajax を受ける側の php 関数の登録
// 管理画面内だけのことなのでフックは一つだけでたりる
add_action( 'wp_ajax_getactiveblocklist', array( $this, 'get_active_block_list' ) );
}
public function get_active_block_list() {
// Ajax を受ける関数
// ログインしていて、対象のユーザーであるかの認証
if ( function_exists( 'wp_get_current_user' ) ) {
$user_id = get_current_user_id();
if ( 1 === $user_id ) {
//nonceを取得して認証を得る
//$nonce = isset( $_POST['nonce'] ) ? $_POST['nonce'] : null;
if ( check_ajax_referer( __FILE__, 'nonce', false ) ) {
$ptype = '';// 指定されているpost_type
if ( isset ( $_POST['ptype'] ) ) {
global $wpdb;
$ptype = $_POST['ptype'];
$acceptov = 0;// 指定去れている総投稿数をこえていても処理をさせるフラグ
// classic-editor プラグインが稼働しているかのフラグ
// プラグインの稼働を調べる is_plugin_active() は Ajax で呼ばれる側では使えない
$is_classic = 0;
if ( isset ( $_POST['acceptover'] ) ) {
$acceptov = ( int ) $_POST['acceptover'];
}
if ( isset ( $_POST['classic'] ) ) {
$is_classic = ( int ) $_POST['classic'];
}
// 指定されている post_type は ”,” 区切りの文字列なので、それを配列にする
$ptypes = explode( ',', $ptype );
// SQL 文条件式作成のため、指定されている post_type の数で値が全て %s の配列をつくる
$instr = array_fill( 0, count ( $ptypes ), '%s');
// classic-editor プラグイン稼働の有無で SQL文の条件を変える
if ( $is_classic ) {
$sql = "select count( ID ) from $wpdb->posts p, $wpdb->postmeta m where p.post_status = \"publish\" and p.ID = m.post_id and m.meta_value = 'block-editor' and p.post_type in (" . implode( ',', $instr ) . ')';
} else {
$sql = "select count( ID ) from $wpdb->posts p where p.post_status = \"publish\" and p.post_type in (" . implode( ',', $instr ) . ')';
}
$count = $wpdb->get_var( $wpdb->prepare( $sql, $ptypes ) );
// 取得した投稿数が200を超え、実行許可フラグが否定のときは処理しない
if ( $count > 200 and ! $acceptov ) {
echo 'warning: result post count over 200. post-count : ' . $count . 'pc';
} else {
if ( $is_classic ) {
$sql = "select ID, post_title, post_content from $wpdb->posts p, $wpdb->postmeta m where p.post_status = \"publish\" and p.ID = m.post_id and m.meta_value = 'block-editor' and p.post_type in (" . implode( ',', $instr ) . ')';
} else {
$sql = "select ID, post_title, post_content from $wpdb->posts p where p.post_status = \"publish\" and p.post_type in (" . implode( ',', $instr ) . ')';
}
$pt = $wpdb->get_results( $wpdb->prepare( $sql, $ptypes ) );
$bls_all = array();// block の名前と投稿ID を貯める配列
$bls_all_c = array();// block の名前とその個数を貯める配列、最後に数の多い順で sort させるため別配列にする
$i = 1;
$retstr = ( string ) $count . chr(1) . '<p class="gridinfo">No. : post ID : post title : active blocks</p><div class="gridparent" style="grid-template-columns:auto auto auto 1fr;">';
foreach ( $pt as $row ) {
$pattern = '/<!-- wp:([a-zA-Z_-].+?)\s/';
$bls = array();// 一時的 block 情報格納配列
if ( preg_match_all ( $pattern, $row->post_content, $matches ) ) {
// match した block の名前が個別に入れられているのは $matches[1]
foreach ( $matches[1] as $val ) {
// 我がサイトにおける自的理由による処理
// '/' より前部分に '-' が存在すればその前部分だけで block を識別できるが、それが無い場合は後部分もないと識別不可能なため
$valel = explode( '/', $val );
if ( false === strpos( $valel[0], '-' ) ) {
$tmpval = $val;
} else {
$tmpval = $valel[0];
}
// 配列にデータが存在するかは、array_search() よりも isset() のほうが高速のため key を使う
// key と value が同じ値なのは無駄のようでは有るが、あとで implode を使いたいがため
if ( ! isset ( $bls[ $tmpval ] ) ) {
$bls[ $tmpval ] = $tmpval;
}
if ( isset ( $bls_all[ $tmpval ] ) ) {
++$bls_all_c[ $tmpval ];
$bls_all[ $tmpval ][ $row->ID ] = $row->ID;
} else {
$bls_all_c[ $tmpval ] = 1;
$bls_all[ $tmpval ] = array( $row->ID => $row->ID );
}
}
// 各投稿別の出力
$retstr .= '<span>' . $i . '. </span><span class="pc">' . $row->ID . '</span><span class="bc">' . $row->post_title . '</span><span class="gc">' . implode( ' ~ ', $bls ) . '</span>';
++$i;
}
}
if ( $bls_all ) {
// 個数により多い順で sort
arsort( $bls_all_c );
$retstr .= '</div>' . "\n\n\n" . '<p class="gridinfo">block => pieces : post-ID</p><div class="gridparent" style="grid-template-columns:auto auto 1fr;">';
foreach ( $bls_all_c as $key => $val ) {
$retstr .= '<span class="bc">' . $key . '</span><span class="pc">' . $val . 'pc</span><span class="gc">' . implode( ' . ', $bls_all[ $key ] ) . '</span>';
}
$retstr .= '</div>';
} else {
echo 'error:_ No data return SQL.';
}
echo $retstr;
}
} else {
echo 'error:No sended data post_type.';
}
} else {
echo 'error:Access denied.';
}
} else {
echo 'error:none log-in';
}
} else {
echo 'error:All invalid';
}
}
// ↓ここから管理画面のメニューにオプション設定ページを登録する処理
public function actblclst_add_menu() {
add_management_page( 'Get Active Blocks List', 'Get Active Blocks List', 'administrator', 'active_block_list', array( $this, 'actblclst_page_output' ) );
}
public function actblclst_page_output() {
// 認証用 nonce を生成
$ajax_nonce = wp_create_nonce( __FILE__ );
// classic-editor プラグインが稼働しているか、この関数は Ajax を受ける側の関数では当然のこと使えない
$is_classic = 0;
if ( is_plugin_active( 'classic-editor/classic-editor.php' ) ) {
$is_classic = 1;
}
$classicstr = '<p>not active Classic-Editor</p>';
if ( $is_classic ) {
$classicstr = '<p>Classic-Editor is active</p>';
}
?>
<div class="wrap" id="custom-logo" style="padding:10px;font-size:18px;letter-spacing:1.5px;border-radius:10px;background:white;">
<h2 style="color:blue;text-shadow:2px 2px 2px rgb( 255, 255, 255 ), 3px 3px 2px rgba( 0, 0, 0, 0.4 );">Get Active Block List</h2>
<p>このプラグインは、そのサイトで稼働している Block Editor の block の一覧を表示します</p>
<p>ver. 20221015.01</p>
<?php echo $classicstr; ?>
<p style="font-size:18px;"><label for="targetposttype">target post type : </label><input type="text" id="targetposttype" name="targetposttype" value=""></p>
<p style="font-size:18px;">
<span>* 複数の post_type を指定する場合は、"," 区切りにて。空白は入れない</span><br>
<span>* 英小文字、アンダーバー"_"、カンマ"," 以外の文字がある場合は処理が拒否されます </span><br>
<span>* 取得する投稿の数が200を超える場合は、処理を中止します</span>
</p>
<p id="acptovrp" style="visibility:hidden;"><input type="checkbox" id="acceptover" name="acceptover"> : 取得する投稿の数が200を超えても処理を実行させる場合はチェックする</p>
<p id="gobtn"><button id="getlist" style="padding:5px;color:blue;border:solid 1px blue;background:linear-gradient( #f2f4f4, white, #f2f4f4 );border-radius:5px;box-shadow:2px 2px 2px rgba( 0, 0, 0, 0.3 );cursor:pointer;">Get List</button></p>
</div>
<div class="wrap" id="getlistresult" style="font-size:18px;">Result</div>
<div id="waitback" style="display:flex;justify-content:center;align-items:center;position:fixed;width:100vw;height:100vh;top:0px;left:0px;background:rgba(0,0,0,0.3);visibility:hidden;opacity:0;transition:all 0.5s;"><div id="waiticon" style="position:relative;width:100px;height:100px;background:url(<?php echo plugins_url( 'wait_icon.png', __FILE__ ); ?>) no-repeat center / contain;"></div></div>
<style>
.gridparent{
display:grid;
margin:20px auto;
padding:10px;
background:white;
border-radius:15px;
text-align:left;
}
.gridinfo{
width:300px;
padding:5px;
color:orange;
font-weight:bold;
background:white;
border-radius:5px;
}
.gridparent span{
margin:7px 7px;
padding:5px 10px;
border-radius:5px;
}
.bc{
color:blue;
background:linear-gradient( to right, transparent, rgba( 204, 231, 252, 0.5 ) );
}
.pc{
color:purple;
background:linear-gradient( to right, transparent, rgba( 247, 202, 249, 0.5 ) );
}
.gc{
color:green;
background:linear-gradient( to right, transparent, rgba( 207, 252, 230, 0.5 ) );
}
</style>
<script>
( function() {
// Ajax 用関数
function ajax_callback( str, callback, parent = 'getlistresult' ) {
if ( str ) {
const url = '<?php echo site_url(); ?>/wp-admin/admin-ajax.php';
const req=new XMLHttpRequest();
req.onreadystatechange = function() {
let result = '';
if (req.readyState == 4) { // 通信の完了時
if (req.status == 200) { // 通信の成功時
result = req.responseText;
callback( result );
} else {
resstr = '<p style="color:red;font-size:20px;">error : Disable Ajax send!</p>';
document.getElementById( parent ).innerHTML = resstr;
}
}
}
req.open( 'POST', url, true );
req.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' );
req.send( str );
} else {
resstr = '<p style="color:red;font-size:20px;">error : no send data!</p>';
document.getElementById( parent ).innerHTML = resstr;
}
}// ajax_callback()
const doc = document,
resultel = doc.getElementById( 'getlistresult' ),
target = doc.getElementById( 'getlist' );
target.onclick = () => {
const ptype = doc.getElementById( 'targetposttype' ).value,
acceptover = doc.getElementById( 'acceptover' ).checked ? '1' : '0';
if ( ptype ) {
// 正規表現で入力値の検証
// アルファベット小文字, アンダーバー'_', カンマ',' だけで一文字以上
const re = /^[a-z_,]+$/;
if ( re.test( ptype ) ) {
let resstr = '<p style="color:blue;font-size:20px;">Start get list Ajax process!</p>';
resultel.innerHTML = resstr;
const nonce = '<?php echo $ajax_nonce; ?>',
is_classic = '<?php echo $is_classic; ?>',
strsend = 'action=getactiveblocklist&ptype=' + ptype +'&acceptover=' + acceptover + '&classic=' + is_classic + '&nonce=' + nonce;
// このwaitback + waiticon に関する事は、待ち時間の表示に関することでどうでもいいこと
// javascript からぐるぐるまわるアニメーションを設定、起動
const waitback = doc.getElementById( 'waitback' ),
waiticon = doc.getElementById( 'waiticon' ),
iconrote= [// keyframes
{ transform: 'rotate( 0deg )' },
{ transform: 'rotate( 90deg )' },
{ transform: 'rotate( 180deg )' },
{ transform: 'rotate( 270deg )' },
{ transform: 'rotate( 360deg )' },
],
animOpt = {
duration: 3000,
iterations: Infinity,
};
const anim = waiticon.animate( iconrote, animOpt );
waitback.style.cssText += 'visibility:visible;opacity:1';
ajax_callback( strsend, function( result ) {
waitback.style.cssText += 'opacity:0;visibility:hidden;';
anim.cancel();
// 返り値の先頭5文字で処理を分ける
const ans = result.substring( 0, 5 );
// 返り値の最後に 0 がついているのでそれを消去
const ls = result.slice( -1 ) ;
if ( '0' === ls ) {
result = result.substring( 0, ( result.length -1 ) );
}
if ( 'error' === ans ) {
resstr = '<p style="color:red;font-size:18px">failed get active block list : ' + result.replace( 'error:_', '' ) + '</p>';
} else if ( 'warni' === ans ) {
resstr = '<p style="color:green;font-size:18px">Processing is temporarily suspended : ' + result.replace( 'warning:', '' ) + '</p>';
resstr += '<p>取得する投稿の数が200を超えても処理を実行させる場合は、上に表示されたチェックボックスをチェックしてください</p>';
doc.getElementById( 'acptovrp' ).style.visibility = 'visible';
} else {
const elem = result.split( '\u0001' );// 個数\u0001結果文字列
resstr = '<div style="font-size:18px;"><p style="font-size:18px;color:blue;">Success get active block list. all Block-Editor post count : ' + elem[0] + '</p>' + elem[1] + '</div>';
}
resultel.innerHTML = resstr;
});
} else {
let resstr = '<p style="color:red;font-size:20px;">An invalid string was entered.!' + ptype +'</p>';
resultel.innerHTML = resstr;
}
} else {
let resstr = '<p style="color:red;font-size:20px;">Direct post type none!</p>';
resultel.innerHTML = resstr;
}
return false;
}
})();
</script>
<?php
}//↑ここまで function actblclst_page_output
}// end class
} // if class
if ( is_admin() ) {
$get_active_block_list = new Active_Block_List();
//↓管理画面のメニューにオプション設定ページを登録する処理
add_action( 'admin_menu', array( $get_active_block_list, 'actblclst_add_menu' ) );
}
?>
と、言う具合。
ちなみに結果表示はこんな感じ。
Post : 2022/10/16 18:55
Comments feed
Trackback URL : https://strix.main.jp/wp-trackback.php?p=172705