メニュー

ETIENU NOTE

コーディングの実践メモ

ワードプレスで「ほぼプラグイン」を作った話

WordPress
#プラグイン
/
読み込んだディレクトリ:sample

プラグイン化してくれ

某所にて「このサイトにある機能をプラグイン化してくれる方募集」という案件がありました。

その時の参考サイトがこちら

【診断に使える】YES・NOチャートをWordPressで作る

https://ponhiro.com/yesno-chart/

その機能は、YesNoチャートと呼ばれるものでした。始まりの問題があり、選択肢を選んでいくと

問題が分岐していき、最後に答えが出るというものです。

「そのままコピペすれば使えるよ!」

という菩薩のようなありがたい内容なのですが、更にそれを簡単に扱えるようにプラグインとして開発して欲しいとの事です。
タイトル、画像、選択肢。問題の数だけ作り、答えも入力する。

「やれるかも」

プラグインの作り方が分れば、やれる幅が広がる。

そう思って提案しようと試みました。


しかし、これをプラグインで作り始めたとして、逆に入力と保存が複雑になりわずらわしいのではないか、という疑問を持ちます。

自分の中でこの機能の必要な条件を考えたところ

1.プラグインと同じぐらいに挿入/削除が楽

2.データ作成がシンプルで扱いやすい

3.とにかく短く書ける

4.記事への挿入が簡単

プラグイン化せずどう作るか?

できたもの

これがそのYes/Noチャートです。

診断

クイズ

言語名のおはなし

二問目:HTMLって何の略?

三問目:Java Scriptを発音すると

四問目:PHPのPはPHPのPです

診断結果

圧力から解放される旅に出よう
- Words Press - 言葉の圧力 END

もう一度診断する

診断結果

HaTeMaLuとはなんだろうか、そういいながらあなたは眠りについた。
- HaTeMaLu - 新たなる疑問 END

もう一度診断する

診断結果

そうだ、ジャワティ飲もう
- Jawa Tea - じゃわじゃわタイム END

もう一度診断する

診断結果

PHPのPがPHPのPなら、PHP(P(P(P(P...)HP)HP)HP)H...うっ頭が
- 再帰的頭字語 - END

もう一度診断する

診断結果

PHPは(PHP : Hypertext Preprocessor)です
お疲れさまでした。
- Clear -

もう一度診断する

要件に対して行った実装

1.プラグインと同じぐらいに挿入/削除が楽

フォルダごとテーマ以下の自由な場所に配置。

function.phpの最後に下記の1文追加のみ。

include "plugin/YesNoChart/plugin_YesNoChart.php";

「ほぼプラグイン」であるplugin_YesNoChart.phpをincludeするだけで完結します。

削除する時は、記事から省く事とfunction.phpでの読み込みを消すだけ。

2.データ作成がシンプルで扱いやすい

テキストファイル(UTF8)に記述して読み込む。

作成場所はプラグイン以下、拡張子は何でもOK

または固定ページに記述して固定ページを読み込む。

3.とにかく短く書ける

テキストファイルの中はショートコードで構築する。扱いとしてはPHPファイルです。

4.記事挿入が簡単

[LoadYesNoChartFile file="sample/tes.php"]

記事にショートコード1行。

コードのダウンロード

githubに置きました。

https://github.com/etienu/plugin_yesnochart

「ほぼプラグイン」の仕組み

function.phpで「ほぼプラグイン」が読み込まれると、次の動作をします。

1.cssの読み込みをヘッダーに追加

2.jsファイル読み込みをフッターに追加

3.ショートコード登録

4.記事中にショートコードがあると実行

コード解説

最初にファイルのパス取得関数

//--------------------------------------------
//      パス取得関数
//--------------------------------------------
function get_Plugin_URL() {
    //  1.テーマのURLを取得する
    //  http://xxxxx/wp-content/themes/テーマ名
    $fpath = get_template_directory_uri();
    //  2.テーマ名を取得する
    $ftm   = basename( $fpath );
    //  3.このディレクトリ名を取得する
    $fpos = strrpos(__DIR__, $ftm);
    //  4.ディレクトリ名からテーマ名より下の位置を切り出しカットする
    $plugindir = substr( __DIR__, $fpos + strlen($ftm)+1 );
    //  5.テーマURL+プラグインパスを返す
    return $fpath."/".$plugindir;
}

このphpでCSSやjsを読み込むのではなく、元記事で読み込んでいる事からディレクトリの指定で混乱します。ワードプレス上でディレクトリは指定できないので、URL+プラグインのディレクトリ名を切り貼りしてフルパスを書き出します。

ヘッダーにcss読み込み処理を追加

//--------------------------------------------
//
//  ヘッダー中でcssを読み込む
//
//--------------------------------------------
function insert_yesnochart_css() {
    global $post;

    if (is_page() || is_single()) {
        if (have_posts()) :
            echo '<link rel="stylesheet" href="'.get_Plugin_URL().'/YesNoChart.css" />';
        endif;
        rewind_posts();
    }
}
add_action('wp_head','insert_yesnochart_css');

function.phpで行っているように、関数をadd_action()で追加し、ヘッダーで実行しています。CSSは参考サイトの引用なので割愛。

フッターにJavaScriptの読み込みを追加

//--------------------------------------------------------
//  yn-chart a ボタン
//  クリックしたら現在のdivを非表示にし、指定idをフェードイン
//--------------------------------------------------------
    //  javascript element版
    function insert_yesnochart_js() {
        $fpath = get_Plugin_URL();
        echo '<script type="text/javascript" src="'.$fpath.'/yesnochart.js"></script>';
    }
    //  フッターへ記述する
    add_action( 'wp_print_footer_scripts', 'insert_yesnochart_js' );

同じくadd_actionで関数をフッターで実行しています。

Javascript

どうしてもJquery読み込み前になってしまったのでelementで処理しています。

フェードイン処理に関してはコメントにある記事を引用させて頂きました。

//------------------------------------------------
// 引用元
// 「脱jQuery」 生JSで.fadeIn()のように要素をフェードインで表示する
// https://spyweb.media/2018/01/12/jquery-fadein-pure-javascript/
//------------------------------------------------
var ync_fadeIn = function(node, duration) {
    // display: noneでないときは何もしない
    //if (getComputedStyle(node).display !== 'none') return;

    // style属性にdisplay: noneが設定されていたとき
    if (node.style.display === 'none') {
        node.style.display = '';
    } else {
        node.style.display = 'block';
    }
    node.style.opacity = 0;

    var start = performance.now();

    requestAnimationFrame(function tick(timestamp) {
        // イージング計算式(linear)
        var easing = (timestamp - start) / duration;

        // opacityが1を超えないように
        node.style.display = "block";
        node.style.opacity = Math.min(easing, 1);

        // opacityが1より小さいとき
        if (easing < 1) {
            requestAnimationFrame(tick);
        } else {
            node.style.opacity = '';
        }
    });
};

var btn = document.querySelectorAll('.yn-chart a');
if (btn && 0 < btn.length) {
    // .yn-chart a 全てに設定
    for (var i = 0; i < btn.length; i++) {
        btn[i].addEventListener('click', function(e) {
            // ボタンのaから遡って上のdivを取得
            const foo = e.target.closest('div');
            // div( 問題 )を非表示
            foo.style.display = "none";
            id = e.target.hash; // idの取得
            id = id.slice(1); // #を削る
            //  idのセレクタを取得
            var tar = document.getElementById(id);
            tar.style.opacity = 0; // 透明化
            tar.style.display = "block"; // 表示する
            ync_fadeIn(tar, 100);
            return false;
        })
    }
}

ショートコード登録

以下は全てショートコードの追加です

固定ページの本文を読み込み、記事に差し込む

//--------------------------------------------------------
//  固定ページを読み込み、記事に差し込む
//--------------------------------------------------------
function func_LoadYesNoChart( $atts ){
    shortcode_atts( array( 'slug' => '', ), $atts );
    //固定ページのスラッグ名
    $page_id = get_page_by_path( $atts['slug'] );
    $page = get_post( $page_id );

    ob_start();
    //ページの本文を取得してショートコード処理をして表示
    echo do_shortcode( $page -> post_content );
    return ob_get_clean();
}
add_shortcode('LoadYesNoChart', 'func_LoadYesNoChart');

最初に思いついた外部読み込みの方法です。問題点としては、固定ページはURLにアクセスされても大丈夫なように管理者のみに見える非表示にする必要があるのと、検索indexに載らないようnoindex設定をする必要がある事です。

また、開発者ではない利用者にも分かりやすい方法に見えますが、お題の数だけ固定ページを増やすというのが管理が煩雑になって、スマートではないと感じます。

外部テキストファイルを読み込み、記事に差し込む

//--------------------------------------------------------
//
//  外部PHPを読み込み、記事に差し込む
//
//--------------------------------------------------------
function func_LoadYesNoChartPHPFile( $atts ){
    shortcode_atts( array( 'file' => ''), $atts );
    global $ync_filedir;
    $dir = __DIR__."/";
    $url = $dir.$atts['file'];

    //  指定ファイル名にフォルダがあれば/で分離する
    $exp = explode("/", $atts['file']);
    //  分離に成功していれば保存
    if( 0 < count( $exp ) ){
        $ync_filedir = $exp[0];
    }
    if (!file_exists($url)) {
        echo 'ファイルがない : '.$url."<br>";
        return false;
    } 

    //  読み込んだ内容の出力
    ob_start();
    include $url;

    return do_shortcode(ob_get_clean() );
}
add_shortcode( 'LoadYesNoChartFile', 'func_LoadYesNoChartPHPfile' );

次に考えたのが、プラグインフォルダ直下にデータフォルダを設け、そこにテキストファイルを書いて読み込む方法。開発者でないとイマイチ分かりづらいのが難点なのですが、テキストファイルで完結できるのでスマートさで言うとこの上ないと思います。

読み込み方ですが、ob_start()でテキスト出力を保存しながら、includeをするだけです。ob_get_clean()で保存したテキストを返しています。

ob_start()とは、文字列を出力する命令を行った場合、実際には出力されずに内部バッファに溜め続ける処理です。

ob_get_clean()は溜めたバッファを出力せずに消去する処理ですが、returnによって本記事に返す事によって出力しています。

また、ファイルの読み込みにフォルダを指定している場合はフォルダ名を保存しておき、画像の読み込み時に使用して指定を短縮します。

詳しくは以下の記事

ob_start

https://www.php.net/manual/ja/function.ob-start.php

結果的にはシンプルでしたが、たどり着くまでに試行錯誤しました。

画像の短縮読み込み

//--------------------------------------------------------
//
//  YesNoChart Img 画像の出力
//
//--------------------------------------------------------
function func_YesNoChart_Img( $atts ){
    shortcode_atts( array( 'img' => '','imgu' => '','imgt' => '','i' => '',), $atts );
    global $ync_filedir;
    //  コンテンツのフォルダ
    if( array_key_exists( 'img'  , $atts ) ){
        echo '<figure class="wp-block-image ync-aligncenter size-full"><img src="'.content_url().'/'.$atts['img'].'" alt=""></figure>';
    //  メディアライブラリ(uploads)のフォルダ
    }else if( array_key_exists( 'imgu'  , $atts ) ){
        echo '<figure class="wp-block-image ync-aligncenter size-full"><img src="'.wp_upload_dir()['baseurl'].'/'.$atts['imgu'].'" alt=""></figure>';
    //  親テーマのフォルダ
    }else if( array_key_exists( 'imgt'  , $atts ) ){
        echo '<figure class="wp-block-image ync-aligncenter size-full"><img src="'.get_template_directory_uri().'/'.$atts['imgt'].'" alt=""></figure>';
    //  フォルダ直下
    }else if( array_key_exists( 'i' , $atts ) ){
        $nowdir = get_Plugin_URL();
        // 保存フォルダ名が空でなければ基準にする
        if( $ync_filedir !== "" ) $nowdir .= "/".$ync_filedir;
        echo '<figure class="wp-block-image ync-aligncenter size-full"><img src="'.$nowdir."/".$atts['i'].'" alt=""></figure>';
    }
}

どのフォルダを基準に読み込むかは人によって違う可能性があります。そこで、複数の基準を用意して1文字違い程度で修正できるようにしました。基本は"i"一文字でプラグインフォルダ直下で運用するイメージです。

テキストファイルを読み込んだ際にフォルダ名を保存して使用する事で、画像が同じフォルダであればフォルダ名を省略できるようにしました。

YesNoChart大枠

//--------------------------------------------------------
//
//  YesNoChart 大枠
//
//  YESNOチャート参考記事
//  https://ponhiro.com/yesno-chart/
//--------------------------------------------------------
function YesNoChart_Frame( $atts, $content = null ){
    shortcode_atts( array(
        'tag' => '',
        'title' => '',
        'ex' => '',
    ), $atts );

    ob_start();
    echo '<div class="yn-chart">';
    //  引数が空ではない
    if( !empty($atts) ){
        if( array_key_exists( 'tag'  , $atts ) ) echo '<p class="yn-chart__add">'.$atts['tag'].'</p>';
        if( array_key_exists( 'title', $atts ) ) echo '<p class="yn-chart__title">'.$atts['title'].'</p>';
        if( array_key_exists( 'ex'   , $atts ) ) echo '<p class="yn-chart__ex">'.$atts['ex'].'</p>';
    }
    do_shortcode( $content );
    echo '</div>';
    
    return ob_get_clean();
}

add_shortcode('YesNo-Chart', 'YesNoChart_Frame');

$attsはショートコードの引数です。shortcode_atts()はデフォルトの属性を指定しています。以前はextractで囲っていたようですが、警告が出る場合もあり現在は非推奨のようです。

$contentは[shortcode][/shortcode]の間の文字列です。ショートコード内でdo_shortcode($content)とする事で、ショートコードの多重構造を実現しています。

また、ファイル読み込みでもあったob_start()を利用してdivが終わるまでの下層ショートコード含めて全ての文字列を溜めており、最後のob_get_clean()で文字列を全て返しています。

問題

//--------------------------------------------------------
//  YesNoChart Question
//
//  id   : a hrefに対応するID
//  title: 問題文
//
//  画像を表示する場合のパス
//  img  : wp-contentまで省略( http://~/wp-content/)以降を入力。自由なフォルダを指定可能
//  imgu : メディアライブラリまで省略( http://~/wp-content/upload/)以降を入力。
//  imgt : テーマまで省略( http://~/wp-content/themes/現在のテーマ/)以降を入力。
//  i    : プラグイン直下
//--------------------------------------------------------
function func_YesNoChart_Q( $i_first = false, $atts, $content = null ){
    shortcode_atts( array( 'id' => '', 'title' => '', 'img' => '','imgu' => '','imgt' => '','i' => '',), $atts );

    //  "Q1"指定された場合、一個目の表示クラスを指定
    if( $i_first ){
        echo '<div id="'.$atts['id'].'" class="yn-chart__display">';
    }else{
        echo '<div id="'.$atts['id'].'">';
    }
    //  画像
    func_YesNoChart_Img( $atts );
    //  タイトル
    echo '<p>'.$atts['title'].'</p>';
    //  選択肢
    echo '<ul>';
    do_shortcode( $content );
    echo '</ul>';
    echo '</div>';
    
    return;
}

//--------------------------------------------------------
//
//  YesNoChart Q 最初の問題( 初めに表示される問題にクラスが付与される )
//
//--------------------------------------------------------
function YesNoChart_Q1( $atts, $content = null ){
    func_YesNoChart_Q( true, $atts, $content );
    return;
}

//--------------------------------------------------------
//
//  YesNoChart Q 以降の問題
//
//--------------------------------------------------------
function YesNoChart_Q( $atts, $content = null ){
    func_YesNoChart_Q( false, $atts, $content );
    return;
}


add_shortcode('YesNo-Q1', 'YesNoChart_Q1');
add_shortcode('YesNo-Q', 'YesNoChart_Q');

最初の問題のみ表示されている必要があるので、classを付与する為1問目だけ別のショートコードにしています。

選択肢

//--------------------------------------------------------
//
//  YesNoChart Answer
//
//--------------------------------------------------------
function YesNoChart_Answer( $atts, $content = null ){
    shortcode_atts( array( 'href' => '', 'text' => '',), $atts );
    if( $atts['href'] ){
        echo '<li><a href="'.$atts['href'].'">'.$atts['text'].'</a></li>';
    }
    return;
}

add_shortcode('YesNo-A', 'YesNoChart_Answer');

問題選択肢、「ul li」のliの部分です。

結果

//--------------------------------------------------------
//
//  YesNoChart Result
//
//--------------------------------------------------------
function YesNoChart_Result( $atts, $content = null ){
    shortcode_atts( array( 'id' => '',
        'img' => '','imgu' => '','imgt' => '','i' => '',
        'title' => '', 'btnhref' => '', 'btntxt' => '',), $atts );
    
    echo '<div id="'.$atts['id'].'">';
    //  画像
    func_YesNoChart_Img( $atts );

    echo '<div class="yn-chart__result">';
    echo '<p class="yn-chart__result-title">'.$atts['title'].'</p>';
    echo '<p>'.$content.'</p>';
    echo '</div>';

    //  ボタンの処理
    //  ID指定がある場合
    if( array_key_exists( 'btnhref', $atts )){
        if( array_key_exists( 'btntext', $atts ) ){
            echo '<p class="p-check-btn"><a href="'.$atts['btnhref'].'">'.$atts['btntxt'].'</a></p>';
        }else{
            echo '<p class="p-check-btn"><a href="'.$atts['btnhref'].'">もう一度診断する</a></p>';
        }
    //  指定がない場合
    }else{
        echo '<p class="p-check-btn"><a href="#q1">もう一度診断する</a></p>';
    }
    echo '</div>';
    
    return;
}

add_shortcode('YesNo-R', 'YesNoChart_Result');

投稿画面に追加機能を付ける

固定ページの読み込みという選択肢を作ったはよいのですが、データとして扱わる固定ページも、検索インデックスに入ってしまう恐れがあります。noindexの付与をこのプラグイン単体で完結させるには?

以下のサイトの記事でnoindexのチェックボックスを追加する方法が書かれていました。

https://tcd-theme.com/2021/07/wordpress-noindex.html


これを応用すれば、記事ごとに指定の機能を自動付与するかといったオプションも作れそうですね。

まとめ

結局案件には応募しなかったのですが、今回の事により

・function.phpからPHPファイル一つの読み込みでcssやjsも読み込み、プラグインのような事ができる

・固定ページの本文や外部テキストファイルをPHPの追加記事として読み込める

・ショートコードは多重構造で組める

・投稿編集画面に機能を追加する事ができる

といった事が分り、とても勉強になりました。

改善点としては、開発者からすると入力が楽に見えますが、利用者からすると「テキストファイルを作成して配置する」のが分りづらいかも・・というのはありますね。

そのテキストファイルをプラグインでワードプレス側から編集できるようにする・・といった案が考えられますが、今回はこんな感じで。

参考になれば幸いです。