search
LoginSignup
0
Help us understand the problem. What are the problem?

posted at

a-blog cmsで現在のエントリーを「関連エントリー」に登録しているエントリーを表示する

これは「a-blog cms Advent Calendar 2021」の、17日目の記事です。

前置き

管理しているサイトで、数年前に「今表示しているエントリーが関連付けられているエントリーを表示できませんか」と相談されました。言い換えると現在のエントリーを関連エントリーに登録しているエントリー、ということになります。

そのサイトでは、ブログを横断した関連エントリーを多用していました。ここではA→Bとします。
数十件くらい投稿してから、B→Aの関連付けも追加して相互リンクにしようとしたところ、a-blog cmsの仕様上、エントリーBの編集画面へ行っても「関連付けをしているのがエントリーAである」ことがわからないので、AとBの編集画面を両方表示して作業している、とのことでした。

これは非常に難しいです。a-blog cmsのタグだけでは「関連付けられているエントリー」を調べる方法がないからです。相談された方は「これができたら助かるなー」レベルのご相談だったので、まとまった作業になるとお返事して、そのときは終了となりました。

WsEntryRelatedモジュールの仕様

スクリーンショット 2021-12-15 18.21.32.png

今年のAdvent Calendarのテーマは拡張アプリにしよう、ということで、当時の課題を実現しました。
「WsEntryRelated」アプリの仕様は、以下のとおりです。

  • 現在のエントリーを「関連エントリー」に登録しているエントリーを検索し表示する
  • すべてのブログを対象とする
  • 日付、主カテゴリー、タイトルを公開日降順で表示する
  • エントリー、カテゴリー、ブログが非公開、下書き、予約投稿になっている場合は、ログイン中かつ編集者以上のときのみ表示する

独自のSQL文を書きます。処理の流れは以下のとおりです。

  • relationshipテーブルのrelation_eidに現在のエントリーIDが入っている行をすべて取得する
  • entryテーブルのentry_idにそのID群が入っている行を再度取得する

SQL文を複雑にすれば一回のリクエストでできた気がしますが、今のところ試作なので二段階に分けます。

参考記事

a-blog cmsは基本的にPHPを書きませんが、拡張アプリ作成のリファレンスは一通りあります。

開発 | ドキュメント | a-blog cms developer

拡張アプリを実際に開発している、ジェノベーゼさんの記事も参考になると思います。

a-blog cmsの拡張アプリ開発時によく使う記述 | a-blog cms | MR. GRADATION

ファイル構造

拡張アプリの中身は定義とGET関連の処理だけです。WsEntryRelatedディレクトリはa-blog cmsの /extension/plugins/ 以下に置きます。

WsEntryRelated
├ ServiceProvider.php
└ GET
   └ WsEntryRelated.php

アプリの内容

実際のコードを公開します。細かい解説は後ほど。

ServiceProvider.php

DBテーブルを作らないので、定義ファイルには最低限の処理しか書いていません。

ServiceProvider.php
<?php

namespace Acms\Plugins\WsEntryRelated;

use ACMS_App;
use Acms\Services\Common\HookFactory;
use Acms\Services\Common\CorrectorFactory;
use Acms\Services\Common\ValidatorFactory;
use Acms\Services\Common\InjectTemplate;

class ServiceProvider extends ACMS_App
{
    /**
     * @var string
     */
    public $version = '1.0.0';

    /**
     * @var string
     */
    public $name = 'WsEntryRelated';

    /**
     * @var string
     */
    public $author = 'webbingstudio';

    /**
     * @var bool
     */
    public $module = false;

    /**
     * @var bool|string
     */
    public $menu = false;

    /**
     * @var string
     */
    public $desc = '現在のエントリーを関連付けているエントリーを表示します。';

    /**
     * サービスの初期処理
     */
    public function init()
    {

    }

    /**
     * インストールする前の環境チェック処理
     *
     * @return bool
     */
    public function checkRequirements()
    {
        return true;
    }

    /**
     * インストールするときの処理
     * データベーステーブルの初期化など
     *
     * @return void
     */
    public function install()
    {

    }

    /**
     * アンインストールするときの処理
     * データベーステーブルの始末など
     *
     * @return void
     */
    public function uninstall()
    {

    }

    /**
     * アップデートするときの処理
     *
     * @return bool
     */
    public function update()
    {
        return true;
    }

    /**
     * 有効化するときの処理
     *
     * @return bool
     */
    public function activate()
    {
        return true;
    }

    /**
     * 無効化するときの処理
     *
     * @return bool
     */
    public function deactivate()
    {
        return true;
    }
}

GET/WsEntryRelated.php

アプリの本体です。ACMS_GETやDB関連を継承しています。ACMS_Entryを継承するともっと高度な処理ができるようになります。

WsEntryRelated.php
<?php

namespace Acms\Plugins\WsEntryRelated\GET;

use ACMS_GET;
use Template;
use DB;
use SQL;
use SQL_Select;
use ACMS_Corrector;

class WsEntryRelated extends ACMS_GET
{
    public $_scope = array(
        'bid' => 'global',
        'cid' => 'global',
        'eid' => 'global',
    );

    function get()
    {
        // 表示件数の上限
        $this->limit = 50;
        // 関連エントリータイプ
        $this->relation_type = 'default';

        $Tpl = new Template($this->tpl, new ACMS_Corrector());
        $DB = DB::singleton(dsn());

        $output = array();

        // 現在のエントリーを関連付けているエントリーのIDを配列で取得
        $SQL = SQL::newSelect('relationship');
        $SQL->addSelect('*');
        $SQL->addWhereOpr('relation_eid', $this->eid);
        $SQL->addWhereOpr('relation_type', $this->relation_type);
        $SQL->addOrder('relation_eid', 'ASC');
        $SQL->setLimit($this->limit, 0);
        $q = $SQL->get(dsn());

        $this->related = $DB->query($q, 'all');
        $this->related_eids = array_column($this->related, 'relation_id');
        // var_dump($this->related);

        // 1件以上ならエントリー情報を取得
        if( $this->related_eids && is_array($this->related_eids) ) {
            $SQL = SQL::newSelect('entry');
            $SQL->addSelect('*');
            $SQL->addLeftJoin('blog', 'blog_id', 'entry_blog_id');
            $SQL->addLeftJoin('category', 'entry_category_id', 'category_id');
            $SQL->addWhereIn('entry_id', $this->related_eids);
            $SQL->addWhereOpr('entry_status', 'trash', '<>');
            $SQL->addWhereOpr('entry_indexing', 'on');
            $SQL->addOrder('entry_posted_datetime', 'DESC');
            $SQL->setLimit($this->limit, 0);
            $q = $SQL->get(dsn());

            $this->entries = $DB->query($q, 'all');
            // var_dump($this->entries);

            foreach( $this->entries as $row ) {
                $show = false;
                if(
                    ($row['blog_status'] == '' || $row['blog_status'] == 'open')
                    && ($row['category_status'] == '' || $row['category_status'] == 'open')
                    && ($row['entry_status'] == '' || $row['entry_status'] == 'open')
                    && (strToTime($row['entry_start_datetime']) <= REQUEST_TIME )
                ) {
                    $show = true;
                } else if( sessionWithCompilation($this->bid) ) {
                    $show = true;
                }
                if( $show ) {
                    $output[] = array(
                        'eid' => $row['entry_id'],
                        'title' => addPrefixEntryTitle(
                                    $row['entry_title']
                                    , $row['entry_status']
                                    , $row['entry_start_datetime']
                                    , $row['entry_end_datetime']
                                    , $row['entry_approval']
                                ),
                        'date' => $row['entry_datetime'],
                        'cid' => $row['category_id'],
                        'categoryCode' => $row['category_code'],
                        'categoryName' => $row['category_name'],
                        'url' => acmsLink(array(
                            'eid'   => $row['entry_id'],
                            'cid'   => $row['category_id']
                        ))
                    );
                }
            }
        }

        $obj = array(
            'entry' => $output,
            'count' => count($output),
        );

        return $Tpl->render($obj);
    }

}

表示部分のテンプレートタグ

上のアプリを管理ページ「拡張アプリ」からインストールして、テーマの表示したい箇所に以下のテンプレートタグを書きます。

変数名はa-blog cmsのエントリー関連モジュールと同じですが、対象が存在しているかを判定する entry:veil category:veil オプションを有効にする方法がわからなかったので、IFブロックで代用しています。

WsEntryRelated.html
<!-- BEGIN_MODULE WsEntryRelated -->
<!-- BEGIN_IF [{count}/gte/1] -->
<section class="module-section">
    <div class="module-header clearfix">
        <h2 class="module-heading">「%{ENTRY_TITLE}」を関連付けているエントリー</h2>
    </div>
    <ul class="headline acms-list-group clearfix"><!-- BEGIN entry:loop -->
        <li class="headline-item">
            <a href="{url}" class="acms-list-group-item headline-link">
                <time class="headline-dat" datetime="{date}[date('c')]">{date}[date('Y年m月d日')]</time>
                <!-- BEGIN_IF [{cid}/nem] --><span class="acms-label">{categoryName}</span><!-- END_IF -->
                <span class="headline-title">{title}</span>
            </a>
        </li><!-- END entry:loop -->
    </ul>
</section>
<!-- END_IF -->
<!-- END_MODULE WsEntryRelated -->

アプリの解説

関連付けられを調べる

基本的な話ですが、生のSQL文を書いてはいけません。a-blog cmsのSQLクラスを利用します。$SQL->add***で条件を追加していき、$SQL->get(dsn())を実行すると、連想配列で結果が返ってきます。var_dumpをコメントアウトしてあるので、結果を出力してチェックしてください。

データベース関連のクラスの解説はこちら。

データベース関係クラスの使用(2) SQL Select編 | 開発 | ドキュメント | a-blog cms developer

SQL_Select | a-blog cms PHP

現在表示しているページのエントリーIDは$this->eidで取れます。
a-blog cmsの関連エントリーは複数のグループを作れるので、グループ名(relation_type)で絞り込む必要があります。ここでは固定値として、最初からある「default」を$this->relation_typeに入れています。

// 現在のエントリーを関連付けているエントリーのIDを配列で取得
$SQL = SQL::newSelect('relationship');
$SQL->addSelect('*');
$SQL->addWhereOpr('relation_eid', $this->eid);
$SQL->addWhereOpr('relation_type', $this->relation_type);
$SQL->addOrder('relation_eid', 'ASC');
$SQL->setLimit($this->limit, 0);
$q = $SQL->get(dsn());

IDの配列からエントリーを調べる

続いて結果から再度、エントリーを返すSQL文を作成します。$SQL->addLeftJoinでブログとカテゴリーの情報も引っ張れます。あとで非公開判定に使用します。
$SQL->addWhereInは配列を条件にすることができます。

「ゴミ箱に入っているエントリー」「一覧に表示しないエントリー(エントリーの編集画面でインデキシングがオフになっている)」を除外することに注意してください。

$SQL = SQL::newSelect('entry');
$SQL->addSelect('*');
$SQL->addLeftJoin('blog', 'blog_id', 'entry_blog_id');
$SQL->addLeftJoin('category', 'entry_category_id', 'category_id');
$SQL->addWhereIn('entry_id', $this->related_eids);
$SQL->addWhereOpr('entry_status', 'trash', '<>');
$SQL->addWhereOpr('entry_indexing', 'on');
$SQL->addOrder('entry_posted_datetime', 'DESC');
$SQL->setLimit($this->limit, 0);
$q = $SQL->get(dsn());

非公開判定

管理目的で使用する場合は、非公開判定を実装しなければなりません。

  • 日付、主カテゴリー、タイトルを公開日降順で表示する
  • エントリー、カテゴリー、ブログが非公開、下書き、予約投稿になっている場合は、ログイン中かつ編集者以上のときのみ表示する

このアプリでは一つひとつ判定していく方向でif文を書きましたが、もしかしたらACMS_Entryクラスに関数があるかもしれないです。
$showがtrueならそのエントリーを表示します。

$show = false;
if(
    ($row['blog_status'] == '' || $row['blog_status'] == 'open')
    && ($row['category_status'] == '' || $row['category_status'] == 'open')
    && ($row['entry_status'] == '' || $row['entry_status'] == 'open')
    && (strToTime($row['entry_start_datetime']) <= REQUEST_TIME )
) {
    $show = true;
} else if( sessionWithCompilation($this->bid) ) {
    $show = true;
}

テンプレートのレンダリング

取得したエントリーからテンプレート変数用の連想配列を組み立て、$Tpl->render を通してreturnすればアプリの処理は完了です。配列名はWordPressぽく$outputとしていますが何でもいいです。

$output[] = array(
...
);

$obj = array(
    'entry' => $output,
    'count' => count($output),
);

return $Tpl->render($obj);

キーが変数名になりますから、ここでは以下の変数が使えることになります。a-blog cmsのビルトインモジュールと同じように、自動でHTMLタグがエスケープされ、校正オプションも使えます。

{eid},{title},{date},{cid},{categoryCode},{categoryName},{url}

ログイン中、制限されたエントリーのタイトルには【非公開】や【公開予約】が付きます。この処理はaddPrefixEntryTitle関数でできます。

'title' => addPrefixEntryTitle(
               $row['entry_title']
               , $row['entry_status']
               , $row['entry_start_datetime']
               , $row['entry_end_datetime']
               , $row['entry_approval']
           ),

また、パーマリンクの生成もacmsLink関数でできます。a-blog cmsのパーマリンクはカテゴリーに依存するのでカテゴリーIDも渡します。

'url' => acmsLink(array(
             'eid'   => $row['entry_id'],
             'cid'   => $row['category_id']
         ))

実用にはまだまだ…

アプリの解説は以上です。
リンクを表示するだけでしたら充分使えますので、ご自由に使用・改良してください。ブログの構成によっては想定外の挙動があるかもしれません。必ず利用予定の環境で動作テストをしてください。

そんなこんなで要件の達成はできましたが、実際のサイトでがっつり使うための機能は不足しています。正式な拡張アプリにするなら、以下も実装しなければならないでしょう。

  • メイン画像とメディア管理
  • カスタムフィールド
  • 使用する関連エントリーグループ名を管理ページで選択、もしくはモジュールIDで渡す
  • date#Y などのフォーマット変数
  • 関連エントリーで既に表示されていた場合は除外する
  • キャッシュの考慮(これは大丈夫かも)

拡張が必要になるのはだいたいエントリーの処理関係なので、ACMS_Entryの関数の解説資料がもう少しあればいいなあ♪と無茶を言って今年のAdvent Calendarの記事を終わります。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
0
Help us understand the problem. What are the problem?