2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

これは「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の記事を終わります。

2
0
0

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
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?