<?php

use Acms\Services\Entry\Exceptions\NotFoundException;
use Acms\Services\Facades\Template as TplHelper;
use Acms\Services\Facades\Entry;
use Acms\Services\Facades\Login;
use Acms\Services\Facades\Preview;
use Acms\Services\Facades\Application;
use Acms\Services\Unit\Contracts\Model;

class ACMS_GET_Entry_Body extends ACMS_GET_Entry
{
    /**
     * 階層設定
     * @inheritDoc
     */
    public $_axis = [ // phpcs:ignore
        'bid' => 'descendant-or-self',
        'cid' => 'descendant-or-self',
    ];

    /**
     * スコープ設定
     * @inheritDoc
     */
    public $_scope = [ // phpcs:ignore
        'uid' => 'global',
        'cid' => 'global',
        'eid' => 'global',
        'keyword' => 'global',
        'tag' => 'global',
        'field' => 'global',
        'date' => 'global',
        'start' => 'global',
        'end' => 'global',
        'page' => 'global',
        'order' => 'global',
    ];

    /**
     * コンフィグの設定値
     *
     * @var array
     */
    protected $config = [];

    /**
     * フルテキストのEagerLoadingData
     *
     * @var array
     */
    protected $summaryFulltextEagerLoadingData = [];

    /**
     * メインイメージのEagerLoadingData
     *
     * @var array
     */
    protected $mainImageEagerLoadingData = [];

    /**
     * 関連エントリーのEagerLoadingData
     *
     * @var array
     */
    protected $relatedEntryEagerLoadingData = [];

    /**
     * 会員限定記事
     *
     * @var bool
     */
    protected $membersOnly = false;

    /**
     * コンフィグのセット
     *
     * @return bool
     */
    function setConfig(): bool
    {
        $this->config = $this->initConfig();
        if ($this->config === false) {
            return false;
        }
        return true;
    }

    /**
     * コンフィグのロード
     *
     * @return array
     */
    function initConfig(): array
    {
        return [
            'order' => [
                $this->order ? $this->order : config('entry_body_order'),
                config('entry_body_order2'),
            ],
            'limit' => config('entry_body_limit'),
            'offset' => config('entry_body_offset'),
            'image_viewer' => config('entry_body_image_viewer'),
            'indexing' => config('entry_body_indexing'),
            'members_only' => config('entry_summary_members_only'),
            'sub_category' => config('entry_body_sub_category'),
            'newtime' => config('entry_body_newtime'),
            'serial_navi_ignore_category_on' => config('entry_body_serial_navi_ignore_category'),
            'tag_on' => config('entry_body_tag_on'),
            'summary_on' => config('entry_body_summary_on'),
            'related_entry_on' => config('entry_body_related_entry_on', 'off'),
            'show_all_index' => config('entry_body_show_all_index'),
            'date_on' => config('entry_body_date_on'),
            'detail_date_on' => config('entry_body_detail_date_on'),
            'comment_on' => config('entry_body_comment_on'),
            'geolocation_on' => config('entry_body_geolocation_on'),
            'trackback_on' => config('entry_body_trackback_on'),
            'serial_navi_on' => config('entry_body_serial_navi_on'),
            'simple_pager_on' => config('entry_body_simple_pager_on'),
            'category_order' => config('entry_body_category_order'),
            'notfoundStatus404' => config('entry_body_notfound_status_404'),
            'micropager_on' => config('entry_body_micropage'),
            'micropager_delta' => config('entry_body_micropager_delta'),
            'micropager_cur_attr' => config('entry_body_micropager_cur_attr'),
            'pager_on' => config('entry_body_pager_on'),
            'pager_delta' => config('entry_body_pager_delta'),
            'pager_cur_attr' => config('entry_body_pager_cur_attr'),
            'entry_field_on' => config('entry_body_entry_field_on'),
            'user_field_on' => config('entry_body_user_field_on'),
            'category_field_on' => config('entry_body_category_field_on'),
            'blog_field_on' => config('entry_body_blog_field_on'),
            'user_info_on' => config('entry_body_user_info_on'),
            'category_info_on' => config('entry_body_category_info_on'),
            'blog_info_on' => config('entry_body_blog_info_on'),
            'loop_class' => config('entry_body_loop_class'),
        ];
    }

    /**
     * Main
     */
    public function get()
    {
        if (!$this->setConfig()) {
            return '';
        }

        $tpl = new Template($this->tpl, new ACMS_Corrector());
        $this->buildModuleField($tpl);

        if ($this->isEntryEditPage()) {
            // 編集ページ
            $this->editPage($tpl);
        } elseif ($this->isEntryFormEditPage()) {
            // 動的フォーム編集ページ
            $this->formEditPage($tpl);
        } elseif ($this->isEntryDetailPage()) {
            // エントリー詳細ページ
            try {
                $this->entryPage($tpl);
            } catch (NotFoundException $e) {
                return $this->resultsNotFound($tpl);
            }
        } else {
            // エントリー一覧ページ
            try {
                $this->entryIndex($tpl);
            } catch (NotFoundException $e) {
                return $this->resultsNotFound($tpl);
            }
        }

        return $tpl->get();
    }

    /**
     * エントリー編集ページ
     *
     * @param Template $tpl
     * @return void
     */
    protected function editPage(Template $tpl): void
    {
        $vars = [];
        if (ADMIN === 'entry_editor' && EID) {
            $tpl->add(['revision', 'adminEntryAction', 'entry:loop']);
            $tpl->add(['adminEntryAction', 'entry:loop']);
        }
        $action = !EID ? 'insert' : 'update';
        $tpl->add(['header#' . $action, 'adminEntryTitle']);
        $tpl->add(['description#' . $action, 'adminEntryTitle']);
        $tpl->add(['adminEntryTitle', 'adminEntryEdit', 'entry:loop']);
        $tpl->add(['adminEntryEdit', 'entry:loop']);
        $tpl->add('entry:loop', $vars);
    }

    /**
     * 動的フォーム編集ページ
     *
     * @param Template $tpl
     * @return void
     */
    protected function formEditPage(Template $tpl): void
    {
        $tpl->add(['adminFormEdit', 'entry:loop']);
        $tpl->add('entry:loop');
    }

    /**
     * エントリー詳細ページ
     *
     * @param Template $tpl
     * @return void
     * @throws NotFoundException
     */
    protected function entryPage(Template $tpl): void
    {
        if (RVID) {
            $sql = SQL::newSelect('entry_rev');
            $sql->addWhereOpr('entry_rev_id', RVID);
        } else {
            $sql = SQL::newSelect('entry');
        }
        $sql->addWhereOpr('entry_id', $this->eid);
        /**
         * @var false|array{
         *   entry_id: int,
         *   entry_title: string,
         *   entry_members_only: string,
         * } $entry
         */
        $entry = DB::query($sql->get(dsn()), 'row');

        if (empty($entry)) {
            throw new NotFoundException();
        }
        $entry['entry_title'] = $this->getFixTitle($entry['entry_title']);
        $eid = intval($entry['entry_id']);
        $this->membersOnly = $entry['entry_members_only'] === 'on';
        $vars = [];

        // ユニットを組み立て
        $micropageInfo = $this->buildEntryUnit($tpl, $entry);

        // フルテキストのEagerLoading
        if ($this->config['summary_on'] === 'on') {
            $this->summaryFulltextEagerLoadingData = TplHelper::eagerLoadFullText([$eid]);
        }
        // メインイメージのEagerLoading
        if (config('entry_body_image_on') === 'on') {
            $this->mainImageEagerLoadingData = TplHelper::eagerLoadMainImage([$entry]);
        }
        // 関連エントリーのEagerLoading
        if ($this->config['related_entry_on'] === 'on') {
            $this->relatedEntryEagerLoadingData = TplHelper::eagerLoadRelatedEntry([$eid]);
        }
        // テンプレートを組み立て
        $this->buildBodyField($tpl, $vars, $entry);

        // 動的フォームを表示・非表示
        $this->buildFormBody($tpl, $entry);

        // エントリー一件を表示
        $tpl->add('entry:loop', $vars);

        // 前後リンクを組み立て
        $this->buildSerialNavi($tpl, $entry);

        // マイクロページを組み立て
        $this->buildMicroPage($tpl, $micropageInfo);

        $rootVars = [];
        if ($this->config['serial_navi_on'] === 'on') {
            $rootVars = array_merge($rootVars, [
                'upperUrl' => acmsLink([
                    'eid' => null,
                ]),
            ]);
        }
        $tpl->add(null, $rootVars);
    }

    /**
     * エントリー一覧ページ
     *
     * @param Template $tpl
     * @return void
     * @throws NotFoundException
     */
    protected function entryIndex(Template $tpl): void
    {
        $limit = idval($this->config['limit']);
        $from = ($this->page - 1) * $limit;

        $sql = $this->buildIndexQuery();

        $entryAmountSql = new SQL_Select($sql);
        $entryAmountSql->setSelect('DISTINCT(entry_id)', 'entry_amount', null, 'COUNT');

        $offset = intval($this->config['offset']);
        $_limit = $limit + 1;
        $sortFd = ACMS_Filter::entryOrder($sql, $this->config['order'], $this->uid, $this->cid);
        $sql->setLimit($_limit, $from + $offset);
        if (!empty($sortFd)) {
            $sql->setGroup($sortFd);
        }
        $sql->addGroup('entry_id');

        $q = $sql->get(dsn());
        $entries = DB::query($q, 'all');

        $nextPage = false;
        if (count($entries) > $limit) {
            array_pop($entries);
            $nextPage = true;
        }
        $this->buildEntryIndex($tpl, $entries, $nextPage, $entryAmountSql);
    }

    /**
     * エントリー一覧を組み立て
     *
     * @param Template $tpl
     * @param array $entries
     * @param bool $nextPage
     * @param SQL_Select $entryAmountSql
     * @return void
     * @throws NotFoundException
     */
    protected function buildEntryIndex(Template $tpl, array $entries, bool $nextPage, SQL_Select $entryAmountSql): void
    {
        // not Found
        if (empty($entries)) {
            throw new NotFoundException();
        }
        // simple pager
        if ($this->config['simple_pager_on'] === 'on') {
            // prev page
            if ($this->page > 1) {
                $tpl->add('prevPage', [
                    'url' => acmsLink([
                        'page' => $this->page - 1,
                    ], true),
                ]);
            } else {
                $tpl->add('prevPageNotFound');
            }
            // next page
            if ($nextPage) {
                $tpl->add('nextPage', [
                    'url' => acmsLink([
                        'page' => $this->page + 1,
                    ], true),
                ]);
            } else {
                $tpl->add('nextPageNotFound');
            }
        }
        $entryIds = [];
        foreach ($entries as $entry) {
            $entryIds[] = $entry['entry_id'];
        }

        // フルテキストのEagerLoading
        if ($this->config['summary_on'] === 'on') {
            $this->summaryFulltextEagerLoadingData = Tpl::eagerLoadFullText($entryIds);
        }
        // メインイメージのEagerLoading
        if (config('entry_body_image_on') === 'on') {
            $this->mainImageEagerLoadingData = Tpl::eagerLoadMainImage($entries);
        }
        // 関連エントリーのEagerLoading
        if ($this->config['related_entry_on'] === 'on') {
            $this->relatedEntryEagerLoadingData = Tpl::eagerLoadRelatedEntry($entryIds);
        }
        // build summary tpl
        foreach ($entries as $i => $row) {
            $serial = ++$i;
            $eid = intval($row['entry_id']);
            $row['entry_title'] = $this->getFixTitle($row['entry_title']);
            $continueName = $row['entry_title'];
            $summaryRange = strval(config('entry_body_fix_summary_range'));
            if (!strlen($summaryRange)) {
                $summaryRange = strval($row['entry_summary_range']);
            }
            $summaryRange = !!strlen($summaryRange) ? intval($summaryRange) : null;
            $inheritUrl = acmsLink([
                'bid' => $row['entry_blog_id'],
                'eid' => $eid,
            ]);

            $vars = [];
            $rvid_ = RVID;
            if (!RVID && $row['entry_approval'] === 'pre_approval') {
                $rvid_  = 1;
            }
            // column
            if ($this->config['show_all_index'] === 'on') {
                $summaryRange = null;
            }

            /** @var \Acms\Services\Unit\Repository $unitRepository */
            $unitRepository = Application::make('unit-repository');
            /** @var \Acms\Services\Unit\Rendering\Front $unitRenderingService */
            $unitRenderingService = Application::make('unit-rendering-front');

            $amount = $unitRepository->countUnitsTrait($eid);

            if ($units = $unitRepository->loadUnits($eid, $rvid_, $summaryRange)) {
                $unitRenderingService->render($units, $tpl, $eid);
                if (!empty($summaryRange)) {
                    if ($summaryRange < $amount) {
                        $vars['continueUrl'] = $inheritUrl;
                        $vars['continueName'] = $continueName;
                    }
                }
            } elseif ($amount > 0) {
                $vars['continueUrl'] = $inheritUrl;
                $vars['continueName'] = $continueName;
            }
            $this->buildBodyField($tpl, $vars, $row, $serial);
            $tpl->add('entry:loop', $vars);
        }

        $rootVars = [];
        if ('random' <> strtolower($this->config['order'][0]) && ($this->config['pager_on'] === 'on')) {
            $itemsAmount = intval(DB::query($entryAmountSql->get(dsn()), 'one'));

            $delta = intval($this->config['pager_delta']);
            $curAttr = $this->config['pager_cur_attr'];
            if (is_numeric($this->config['offset'])) {
                $itemsAmount -= $this->config['offset'];
            }
            $limit = idval($this->config['limit']);
            $rootVars += $this->buildPager($this->page, $limit, $itemsAmount, $delta, $curAttr, $tpl);
        }
        $tpl->add(null, $rootVars);
    }

    /**
     * エントリー一覧のクエリを組み立て
     *
     * @return SQL_Select
     */
    protected function buildIndexQuery(): SQL_Select
    {
        $multiId = false;
        $blogSub = null;
        $categorySub = null;

        $sql = SQL::newSelect('entry');
        $sql->addLeftJoin('blog', 'blog_id', 'entry_blog_id');
        $sql->addLeftJoin('category', 'category_id', 'entry_category_id');
        if (!empty($this->cid)) {
            $categorySub = SQL::newSelect('category');
            $categorySub->setSelect('category_id');
            if (is_int($this->cid)) {
                ACMS_Filter::categoryTree($categorySub, $this->cid, $this->categoryAxis());
            } elseif (strpos($this->cid, ',') !== false) {
                $categorySub->addWhereIn('category_id', explode(',', $this->cid));
                $multiId = true;
            }
            ACMS_Filter::categoryStatus($categorySub);
        } else {
            ACMS_Filter::categoryStatus($sql);
        }
        ACMS_Filter::entrySpan($sql, $this->start, $this->end);
        ACMS_Filter::entrySession($sql);
        if (!empty($this->Field)) {
            ACMS_Filter::entryField($sql, $this->Field);
        }
        if (!empty($this->tags)) {
            ACMS_Filter::entryTag($sql, $this->tags);
        }
        if (!empty($this->keyword)) {
            ACMS_Filter::entryKeyword($sql, $this->keyword);
        }
        if ($this->config['indexing'] === 'on') {
            $sql->addWhereOpr('entry_indexing', 'on');
        }
        if ($this->config['members_only'] === 'on') {
            $sql->addWhereOpr('entry_members_only', 'on');
        }
        if (!empty($this->uid)) {
            if (is_int($this->uid)) {
                $sql->addWhereOpr('entry_user_id', $this->uid);
            } elseif (strpos($this->uid, ',') !== false) {
                $sql->addWhereIn('entry_user_id', explode(',', $this->uid));
                $multiId = true;
            }
        }
        if (!empty($this->eid) && !is_int($this->eid)) {
            $sql->addWhereIn('entry_id', explode(',', $this->eid));
            $multiId = true;
        }
        if (!empty($this->bid)) {
            $blogSub = SQL::newSelect('blog');
            $blogSub->setSelect('blog_id');
            if (is_int($this->bid)) {
                if ($multiId) {
                    ACMS_Filter::blogTree($blogSub, $this->bid, 'descendant-or-self');
                } else {
                    ACMS_Filter::blogTree($blogSub, $this->bid, $this->blogAxis());
                }
            } elseif (strpos($this->bid, ',') !== false) {
                $blogSub->addWhereIn('blog_id', explode(',', $this->bid));
            }
            ACMS_Filter::blogStatus($blogSub);
        } else {
            ACMS_Filter::blogStatus($sql);
        }
        if ($blogSub) {
            $sql->addWhereIn('entry_blog_id', DB::subQuery($blogSub));
        }
        if ($categorySub) {
            if ($this->config['sub_category'] === 'on') {
                $sub = SQL::newWhere();
                $sub->addWhereIn('entry_category_id', DB::subQuery($categorySub), 'OR');

                $sub2 = SQL::newSelect('entry_sub_category');
                $sub2->addSelect('entry_sub_category_eid');
                $sub2->addWhereIn('entry_sub_category_id', DB::subQuery($categorySub));
                $sub->addWhereIn('entry_id', DB::subQuery($sub2), 'OR');

                $sql->addWhere($sub);
            } else {
                $sql->addWhereIn('entry_category_id', DB::subQuery($categorySub));
            }
        }
        return $sql;
    }

    /**
     * 動的フォームの表示・非表示
     *
     * @param Template $tpl
     * @param array $entry
     * @return void
     */
    protected function buildFormBody(Template $tpl, array $entry): void
    {
        if (
            1
            && isset($entry['entry_form_id'])
            && !empty($entry['entry_form_id'])
            && isset($entry['entry_form_status'])
            && $entry['entry_form_status'] === 'open'
            && config('form_edit_action_direct') == 'on'
        ) {
            $tpl->add('formBody');
        }
    }

    /**
     * 修正したエントリータイトルを取得
     *
     * @param string $title
     * @return string
     */
    protected function getFixTitle(string $title): string
    {
        if (!IS_LICENSED) {
            return '[test]' . $title;
        }
        return $title;
    }

    /**
     * ユニットを組み立て
     *
     * @param Template $tpl
     * @param array $entry
     * @return array{
     *   amount: int,
     *   unit: \Acms\Services\Unit\Contracts\Model|null,
     * }
     */
    protected function buildEntryUnit(Template $tpl, array $entry): array
    {
        $micropage = $this->page;
        $micropageAmount = 0;
        $breakUnit = null;
        $eid = intval($entry['entry_id']);
        $RVID_ = RVID;
        if (!RVID && $entry['entry_approval'] === 'pre_approval') {
            $RVID_ = 1;
        }

        /** @var \Acms\Services\Unit\Repository $unitService */
        $unitService = Application::make('unit-repository');
        /** @var \Acms\Services\Unit\Rendering\Front $unitRenderingService */
        $unitRenderingService = Application::make('unit-rendering-front');

        $units = $unitService->loadUnits($eid, $RVID_);
        $publicUnits = $units;

        $summaryRange = null;
        if ($this->membersOnly) {
            // 会員限定記事対応
            $summaryRange = strval($entry['entry_summary_range']);
            $summaryRange = !!strlen($summaryRange) ? intval($summaryRange) : null;
            $tpl->add(['membersOnly', 'entry:loop']);

            if ($summaryRange !== null) {
                if ($this->containsMembersOnlyUnitOnMicroPage($units, $summaryRange, $micropage)) {
                    // 会員限定ユニットが表示ページに含まれている場合、continueLinkブロックを追加
                    $tpl->add(['continueLink', 'entry:loop'], [
                        'dummy' => 'dummy',
                    ]);
                }
                // 会員限定ユニットを除外
                $publicUnits = array_slice($units, 0, $summaryRange);
            }
        }
        if ($this->config['micropager_on'] === 'on') {
            // マイクロページャー有効時
            $micropageAmount = $this->countMicroPageAmount($units);
            $breakUnit = $this->getBreakUnitOnMicroPage($units, $micropage);
            $publicUnits = $this->filterUnitsByMicroPage($publicUnits, $micropage);
        }
        if (count($publicUnits) > 0) {
            $unitRenderingService->render($publicUnits, $tpl, $eid);
        } else {
            // ユニットがない場合
            $tpl->add('unit:loop');
            if (Entry::isDirectEditEnabled()) {
                $tpl->add('edit_inplace', [
                    'class' => 'js-edit_inplace'
                ]);
            }
        }
        return [
            'amount' => $micropageAmount,
            'unit' => $breakUnit
        ];
    }

    /**
     * 一個前のリンクを組み立て
     *
     * @param Template $tpl
     * @param string $sortFieldName
     * @param string $sortOrder
     * @param SQL_Select $baseSql
     * @param string $field
     * @param string $value
     * @return void
     */
    protected function buildPrevLink(Template $tpl, string $sortFieldName, string $sortOrder, SQL_Select $baseSql, string $field, string $value): void
    {
        $sql = new SQL_Select($baseSql);
        $where1 = SQL::newWhere();
        $where1->addWhereOpr($field, $value, '=');
        $where1->addWhereOpr('entry_id', $this->eid, '<');
        $where2 = SQL::newWhere();
        $where2->addWhere($where1);
        if ($sortOrder === 'desc') {
            $where2->addWhereOpr($field, $value, '<', 'OR');
        } else {
            $where2->addWhereOpr($field, $value, '>', 'OR');
        }
        $sql->addWhere($where2);
        if ($sortOrder === 'desc') {
            ACMS_Filter::entryOrder($sql, [$sortFieldName . '-desc', 'id-desc'], $this->uid, $this->cid);
        } else {
            ACMS_Filter::entryOrder($sql, [$sortFieldName . '-asc', 'id-asc'], $this->uid, $this->cid);
        }
        ACMS_Filter::entrySession($sql);
        $q = $sql->get(dsn());

        if ($row = DB::query($q, 'row')) {
            $tpl->add('prevLink', [
                'name' => addPrefixEntryTitle(
                    $row['entry_title'],
                    $row['entry_status'],
                    $row['entry_start_datetime'],
                    $row['entry_end_datetime'],
                    $row['entry_approval']
                ),
                'url' => acmsLink([
                    '_inherit' => true,
                    'eid' => $row['entry_id'],
                ]),
                'eid' => $row['entry_id'],
            ]);
        } else {
            $tpl->add('prevNotFound');
        }
    }

    /**
     * 次のリンクを組み立て
     *
     * @param Template $tpl
     * @param string $sortFieldName
     * @param string $sortOrder
     * @param SQL_Select $baseSql
     * @param string $field
     * @param string $value
     * @return void
     */
    protected function buildNextLink(Template $tpl, string $sortFieldName, string $sortOrder, SQL_Select $baseSql, string $field, string $value): void
    {
        $sql = new SQL_Select($baseSql);
        $where1 = SQL::newWhere();
        $where1->addWhereOpr($field, $value, '=');
        $where1->addWhereOpr('entry_id', $this->eid, '>');
        $where2 = SQL::newWhere();
        $where2->addWhere($where1);
        if ($sortOrder === 'desc') {
            $where2->addWhereOpr($field, $value, '>', 'OR');
        } else {
            $where2->addWhereOpr($field, $value, '<', 'OR');
        }
        $sql->addWhere($where2);
        if ($sortOrder === 'desc') {
            ACMS_Filter::entryOrder($sql, [$sortFieldName . '-asc', 'id-asc'], $this->uid, $this->cid);
        } else {
            ACMS_Filter::entryOrder($sql, [$sortFieldName . '-desc', 'id-desc'], $this->uid, $this->cid);
        }
        ACMS_Filter::entrySession($sql);
        $q = $sql->get(dsn());

        if ($row = DB::query($q, 'row')) {
            $tpl->add('nextLink', [
                'name' => addPrefixEntryTitle(
                    $row['entry_title'],
                    $row['entry_status'],
                    $row['entry_start_datetime'],
                    $row['entry_end_datetime'],
                    $row['entry_approval']
                ),
                'url' => acmsLink([
                    '_inherit' => true,
                    'eid' => $row['entry_id'],
                ]),
                'eid' => $row['entry_id'],
            ]);
        } else {
            $tpl->add('nextNotFound');
        }
    }

    /**
     * 前後リンクを組み立て
     *
     * @param Template $tpl
     * @param array $entry
     * @return void
     */
    protected function buildSerialNavi(Template $tpl, array $entry): void
    {
        if ($this->config['serial_navi_on'] !== 'on') {
            return;
        }
        $sql = SQL::newSelect('entry');
        $sql->addLeftJoin('category', 'category_id', 'entry_category_id');
        $sql->setLimit(1);
        $sql->addWhereOpr('entry_link', ''); // リンク先URLが設定されているエントリーはリンクに含まないようにする
        $sql->addWhereOpr('entry_blog_id', $this->bid);
        if ($this->config['serial_navi_ignore_category_on'] !== 'on') {
            ACMS_Filter::categoryTree($sql, $this->cid, $this->categoryAxis());
        }
        ACMS_Filter::entrySession($sql);
        ACMS_Filter::entrySpan($sql, $this->start, $this->end);
        if (!empty($this->tags)) {
            ACMS_Filter::entryTag($sql, $this->tags);
        }
        if (!empty($this->keyword)) {
            ACMS_Filter::entryKeyword($sql, $this->keyword);
        }
        if (!empty($this->Field)) {
            ACMS_Filter::entryField($sql, $this->Field);
        }
        $sql->addWhereOpr('entry_indexing', 'on');
        $aryOrder1 = explode('-', $this->config['order'][0]);
        $sortFieldName = isset($aryOrder1[0]) ? $aryOrder1[0] : null;
        $sortOrder = isset($aryOrder1[1]) ? $aryOrder1[1] : 'desc';

        if ('random' <> $sortFieldName) {
            switch ($sortFieldName) {
                case 'datetime':
                    $field = 'entry_datetime';
                    $value = ACMS_RAM::entryDatetime($this->eid);
                    break;
                case 'updated_datetime':
                    $field = 'entry_updated_datetime';
                    $value = ACMS_RAM::entryUpdatedDatetime($this->eid);
                    break;
                case 'posted_datetime':
                    $field = 'entry_posted_datetime';
                    $value = ACMS_RAM::entryPostedDatetime($this->eid);
                    break;
                case 'code':
                    $field = 'entry_code';
                    $value = ACMS_RAM::entryCode($this->eid);
                    break;
                case 'sort':
                    if ($this->uid) {
                        $field = 'entry_user_sort';
                        $value = ACMS_RAM::entryUserSort($this->eid);
                    } elseif ($this->cid) {
                        $field = 'entry_category_sort';
                        $value = ACMS_RAM::entryCategorySort($this->eid);
                    } else {
                        $field = 'entry_sort';
                        $value = ACMS_RAM::entrySort($this->eid);
                    }
                    break;
                case 'field':
                case 'intfield':
                    $entryField = loadEntryField($this->eid);
                    $entryFieldKey = $this->Field->listFields()[0] ?? null;
                    if ($entryFieldKey !== null) {
                        $field = $sortFieldName === 'field' ? 'strfield_sort' : 'intfield_sort';
                        $value = $entryField->get($entryFieldKey);
                    } else {
                        $field = 'entry_id';
                        $value = $this->eid;
                    }
                    break;
                case 'id':
                default:
                    $field = 'entry_id';
                    $value = $this->eid;
            }

            // build prevLink
            $this->buildPrevLink($tpl, $sortFieldName, $sortOrder, $sql, $field, $value);

            // build nextLink
            $this->buildNextLink($tpl, $sortFieldName, $sortOrder, $sql, $field, $value);
        }
    }

    /**
     * マイクロページネーションを組み立て
     *
     * @param Template $tpl
     * @param array{
     *   amount: int,
     *   unit: \Acms\Services\Unit\Contracts\Model|null,
     * } $info
     * @return void
     */
    protected function buildMicroPage(Template $tpl, array $info): void
    {
        if ($this->config['micropager_on'] !== 'on') {
            return;
        }
        if ($info['amount'] < 1) {
            return;
        }
        $micropage = $this->page;
        // micropage
        if (!is_null($info['unit'])) {
            $linkVars = [];
            $info['unit']->formatMultiLangUnitData($info['unit']->getField1(), $linkVars, 'label');
            $linkVars['url'] = acmsLink([
                '_inherit' => true,
                'eid' => $this->eid,
                'page' => $micropage + 1,
            ]);
            $tpl->add('micropageLink', $linkVars);
        }

        // micropager
        $vars = [];
        $delta = $this->config['micropager_delta'];
        $curAttr = $this->config['micropager_cur_attr'];
        $vars += $this->buildPager($micropage, 1, $info['amount'], $delta, $curAttr, $tpl, 'micropager');
        $tpl->add('micropager', $vars);
    }

    /**
     * カテゴリーを組み立て
     *
     * @param Template $tpl
     * @param int $cid
     * @param int $bid
     * @return void
     */
    function buildCategory(Template $tpl, int $cid, int $bid): void
    {
        $sql = SQL::newSelect('category');
        $sql->addSelect('category_id');
        $sql->addSelect('category_name');
        $sql->addSelect('category_code');
        $sql->addWhereOpr('category_indexing', 'on');
        ACMS_Filter::categoryTree($sql, $cid, 'ancestor-or-self');
        $sql->addOrder('category_left', 'DESC');
        $q  = $sql->get(dsn());
        DB::query($q, 'fetch');

        $_all = [];
        while ($row = DB::fetch($q)) {
            $_all[] = $row;
        }
        switch ($this->config['category_order']) {
            case 'child_order':
                break;
            case 'parent_order':
                $_all = array_reverse($_all);
                break;
            case 'current_order':
                $_all = [array_shift($_all)];
                break;
            default:
                break;
        }
        while ($_row = array_shift($_all)) {
            if (!empty($_all[0])) {
                $tpl->add(['glue', 'category:loop']);
            }
            $tpl->add('category:loop', [
                'name' => $_row['category_name'],
                'code' => $_row['category_code'],
                'url' => acmsLink([
                    'bid' => $bid,
                    'cid' => $_row['category_id'],
                ]),
            ]);
            $_all[] = DB::fetch($q);
        }
    }

    /**
     * サブカテゴリーを組み立て
     *
     * @param Template $tpl
     * @param int $eid
     * @param int|null $rvid
     * @return void
     */
    function buildSubCategory(Template $tpl, int $eid, ?int $rvid): void
    {
        $subCategories = loadSubCategoriesAll($eid, $rvid);
        foreach ($subCategories as $i => $category) {
            if ($i !== count($subCategories) - 1) {
                $tpl->add(['glue', 'sub_category:loop']);
            }
            $tpl->add('sub_category:loop', [
                'name' => $category['category_name'],
                'code' => $category['category_code'],
                'url' => acmsLink([
                    'cid' => $category['category_id'],
                ]),
            ]);
        }
    }

    /**
     * コメント件数を組み立て
     *
     * @param int $eid
     * @return array
     */
    function buildCommentAmount(int $eid): array
    {
        $sql = SQL::newSelect('comment');
        $sql->addSelect('*', 'comment_amount', null, 'COUNT');
        $sql->addWhereOpr('comment_entry_id', $eid);
        if (
            1
            && !sessionWithCompilation()
            && SUID !== ACMS_RAM::entryUser($eid)
        ) {
            $sql->addWhereOpr('comment_status', 'close', '<>');
        }
        return [
            'commentAmount' => intval(DB::query($sql->get(dsn()), 'one')),
            'commentUrl' => acmsLink([
                'eid' => $eid,
            ]),
        ];
    }

    /**
     * 位置情報を組み立て
     *
     * @param int $eid
     * @return array
     */
    protected function buildGeolocation(int $eid): array
    {
        $sql = SQL::newSelect('geo');
        $sql->addSelect('geo_geometry', 'latitude', null, POINT_Y);
        $sql->addSelect('geo_geometry', 'longitude', null, POINT_X);
        $sql->addSelect('geo_zoom');
        $sql->addWhereOpr('geo_eid', $eid);

        if ($row = DB::query($sql->get(dsn()), 'row')) {
            return [
                'geo_lat' => $row['latitude'],
                'geo_lng' => $row['longitude'],
                'geo_zoom' => $row['geo_zoom'],
            ];
        }
        return [];
    }

    /**
     * タグを組み立て
     *
     * @param Template $tpl
     * @param int $eid
     * @return void
     */
    protected function buildEntryTag(Template $tpl, int $eid): void
    {
        if (RVID) {
            $sql = SQL::newSelect('tag_rev');
        } else {
            $sql = SQL::newSelect('tag');
        }
        $sql->addSelect('tag_name');
        $sql->addSelect('tag_blog_id');
        $sql->addWhereOpr('tag_entry_id', $eid);
        if (RVID) {
            $sql->addWhereOpr('tag_rev_id', RVID);
        }
        $sql->addOrder('tag_sort');
        $q = $sql->get(dsn());

        do {
            if (!DB::query($q, 'fetch')) {
                break;
            }
            if (!$row = DB::fetch($q)) {
                break;
            }
            $stack = [];
            $stack[] = $row;
            $stack[] = DB::fetch($q);
            while ($row = array_shift($stack)) {
                if (!empty($stack[0])) {
                    $tpl->add(['glue', 'tag:loop']);
                }
                $tpl->add('tag:loop', [
                    'name' => $row['tag_name'],
                    'url' => acmsLink([
                        'bid' => $row['tag_blog_id'],
                        'tag' => $row['tag_name'],
                    ]),
                ]);
                $stack[] = DB::fetch($q);
            }
        } while (false);
    }

    /**
     * 編集画面を組み立て
     *
     * @param int $bid
     * @param int $uid
     * @param int|null $cid
     * @param int $eid
     * @param Template $tpl
     * @param string|string[] $block
     * @return void
     */
    protected function buildAdminEntryAction(
        int $bid,
        int $uid,
        ?int $cid,
        int $eid,
        Template $tpl,
        $block = []
    ): void {
        if (!Login::isLoggedIn()) {
            // ユーザーがログインしていない場合は処理を終了
            return;
        }
        $block = empty($block) ? [] : (is_array($block) ? $block : [$block]);

        if (!$this->canEditEntry($bid, $uid, $eid)) {
            // 編集権限がない場合は処理を終了
            return;
        }
        if (ADMIN && 'entry-add' === substr(ADMIN, 0, 9)) {
            $tpl->add(array_merge(['adminEntryEdit'], $block));
            return;
        }
        $block = array_merge(['adminEntryAction'], $block);

        $entry = $this->createAdminEntry($eid, $bid, $cid);

        if (!sessionWithApprovalAdministrator() || $entry['status.approval'] !== 'pre_approval') {
            // 最終承認者ではないか、エントリーが承認前でない場合に編集ブロックを追加
            $this->buildEditBlock($tpl, $block, $entry);
        }

        if (BID === $bid) {
            $this->buildUnitBlock($tpl, $block, $entry);
            // エントリーのステータスに応じたブロックを追加
            $this->buildStatusBlock($tpl, $block, $eid, $entry);
        }

        // 削除オプションの追加
        if (Entry::canDelete($eid)) {
            $this->buildDeleteBlock($tpl, $block, $entry);
        }

        $tpl->add($block);
    }

    /**
     * エントリー編集用の情報を組み立てる
     *
     * @param int $eid
     * @param int $bid
     * @param int|null $cid
     * @return array
     */
    private function createAdminEntry(int $eid, int $bid, ?int $cid): array
    {
        return [
            'bid' => $bid,
            'cid' => $cid,
            'eid' => $eid,
            'status.approval' => ACMS_RAM::entryApproval($eid),
            'status.title' => ACMS_RAM::entryTitle($eid),
            'status.category' => ACMS_RAM::categoryName($cid),
            'status.url' => acmsLink(['bid' => $bid, 'cid' => $cid, 'eid' => $eid]),
        ];
    }

    /**
     * 編集ブロックの組み立て
     *
     * @param Template $tpl
     * @param array $block
     * @param array $entry
     */
    private function buildEditBlock(Template $tpl, array $block, array $entry): void
    {
        $tpl->add(array_merge(['edit'], $block), $entry);
        $tpl->add(array_merge(['revision'], $block), $entry);
    }

    /**
     * ユニット追加ブロックを組み立て
     *
     * @param Template $tpl
     * @param array $block
     * @param array $entry
     */
    private function buildUnitBlock(Template $tpl, array $block, array $entry): void
    {
        $types = configArray('column_add_type');
        $labels = configArray('column_add_type_label');

        foreach ($types as $i => $type) {
            if (!$type || !$label = $labels[$i]) {
                // タイプまたはラベルが無効な場合はスキップ
                continue;
            }
            $tpl->add(array_merge(['add:loop'], $block), $entry + [
                'label' => $label,
                'type' => $type,
                'className' => config('column_add_type_class', '', $i),
                'icon' => config('column_add_type_icon', '', $i),
            ]);
        }
    }

    /**
     * ステータスブロックを組み立て
     *
     * @param Template $tpl
     * @param array $block
     * @param int $eid
     * @param array $entry
     */
    private function buildStatusBlock(Template $tpl, array $block, int $eid, array $entry): void
    {
        // エントリーのステータスに応じてブロックを追加
        $statusBlock = ('open' === ACMS_RAM::entryStatus($eid)) ? 'close' : 'open';
        $tpl->add(array_merge([$statusBlock], $block), $entry);
    }

    /**
     * 削除オプションを追加
     *
     * @param Template $tpl
     * @param array $block
     * @param array $entry
     */
    private function buildDeleteBlock(Template $tpl, array $block, array $entry): void
    {
        $tpl->add(array_merge(['delete'], $block), $entry);
    }

    /**
     * エントリーの編集権限があるかを判定
     *
     * @param int $bid
     * @param int $uid
     * @param int $eid
     * @return bool
     */
    public function canEditEntry(int $bid, int $uid, int $eid): bool
    {
        if (Preview::isPreviewMode()) {
            return false;
        }
        if (timemachineMode()) {
            return false;
        }

        if (defined('LAYOUT_EDIT') && LAYOUT_EDIT === 'ON') {
            return false;
        }
        // ロール機能を利用している場合
        if (roleAvailableUser()) {
            // 全エントリーの編集権限があるか、自分のエントリー編集権限がある場合
            return roleAuthorization('entry_edit_all', $bid) || (roleAuthorization('entry_edit', $bid, $eid));
        }

        // 承認機能が有効で、かつ投稿者が自分のエントリーのみ編集可能な設定が無効な場合
        if (config('approval_contributor_edit_auth') !== 'on' && enableApproval()) {
            return true;
        }

        // 編集者権限がある場合
        if (sessionWithCompilation()) {
            return true;
        }

        // 投稿者権限があり、自分のエントリーの場合
        if (sessionWithContribution() && $uid === SUID) { // @phpstan-ignore-line
            return true;
        }

        // それ以外は編集権限なし
        return false;
    }


    /**
     * Bodyを組み立て
     *
     * @param Template $tpl
     * @param array $vars
     * @param array $row
     * @param int $serial
     * @return void
     */
    protected function buildBodyField(Template $tpl, array &$vars, array $row, int $serial = 0): void
    {
        $bid = intval($row['entry_blog_id']);
        $uid = intval($row['entry_user_id']);
        $cid = $row['entry_category_id'] ? intval($row['entry_category_id']) : null;
        $eid = intval($row['entry_id']);
        $inheritUrl = acmsLink([
            'bid' => $bid,
            'eid' => $eid,
        ]);
        $permalink = acmsLink([
            'bid' => $bid,
            'cid' => $cid,
            'eid' => $eid,
            'sid' => null,
        ], false);

        $RVID_ = RVID;
        if (!RVID && $row['entry_approval'] === 'pre_approval') {
            $RVID_ = 1;
        }
        if ($serial != 0) {
            if ($serial % 2 == 0) {
                $oddOrEven = 'even';
            } else {
                $oddOrEven = 'odd';
            }
            $vars['iNum'] = $serial;
            $vars['sNum'] = (($this->page - 1) * idval($this->config['limit'])) + $serial;
            $vars['oddOrEven'] = $oddOrEven;
        }
        // build tag
        if ($this->config['tag_on'] === 'on') {
            $this->buildEntryTag($tpl, $eid);
        }
        // build category loop
        if (!empty($cid) && $this->config['category_info_on'] === 'on') {
            $this->buildCategory($tpl, $cid, $bid);
        }
        // build sub category loop
        if ($this->config['category_info_on'] === 'on') {
            $this->buildSubCategory($tpl, $eid, $RVID_);
        }
        // build comment/trackbak/geolocation
        if ('on' == config('comment') && $this->config['comment_on'] === 'on') {
            $vars += $this->buildCommentAmount($eid);
        }
        if ($this->config['geolocation_on'] === 'on') {
            $vars += $this->buildGeolocation($eid);
        }
        // build summary
        if ($this->config['summary_on'] === 'on') {
            $vars = TplHelper::buildSummaryFulltext($vars, $eid, $this->summaryFulltextEagerLoadingData);
            if (
                1
                && isset($vars['summary'])
                && intval(config('entry_body_fulltext_width')) > 0
            ) {
                $width = intval(config('entry_body_fulltext_width'));
                $marker = config('entry_body_fulltext_marker');
                $vars['summary'] = mb_strimwidth($vars['summary'], 0, $width, $marker, 'UTF-8');
            }
        }
        // build primary image
        $clid = intval($row['entry_primary_image']);
        if (config('entry_body_image_on') === 'on') {
            $config = [
                'imageX' => config('entry_body_image_x', 200),
                'imageY' => config('entry_body_image_y', 200),
                'imageTrim' => config('entry_body_image_trim', 'off'),
                'imageCenter' => config('entry_body_image_zoom', 'off'),
                'imageZoom' => config('entry_body_image_center', 'off'),
            ];
            $tpl->add('mainImage', TplHelper::buildImage($tpl, $clid, $config, $this->mainImageEagerLoadingData));
        }
        // build related entry
        if ($this->config['related_entry_on'] === 'on') {
            TplHelper::buildRelatedEntriesList($tpl, $eid, $this->relatedEntryEagerLoadingData, ['relatedEntry', 'entry:loop']);
        } else {
            $tpl->add(['relatedEntry', 'entry:loop']);
        }
        // admin
        $this->buildAdminEntryAction($bid, $uid, $cid, $eid, $tpl, 'entry:loop');
        // build entry field
        if ($this->config['entry_field_on'] === 'on') {
            $vars += $this->buildField(loadEntryField($eid, $RVID_, true), $tpl, 'entry:loop', 'entry');
        }
        // build user field
        if ($this->config['user_info_on'] === 'on') {
            $Field = ($this->config['user_field_on'] === 'on') ? loadUserField($uid) : new Field();
            $Field->setField('fieldUserName', ACMS_RAM::userName($uid));
            $Field->setField('fieldUserCode', ACMS_RAM::userCode($uid));
            $Field->setField('fieldUserStatus', ACMS_RAM::userStatus($uid));
            $Field->setField('fieldUserMail', ACMS_RAM::userMail($uid));
            $Field->setField('fieldUserMailMobile', ACMS_RAM::userMailMobile($uid));
            $Field->setField('fieldUserUrl', ACMS_RAM::userUrl($uid));
            $Field->setField('fieldUserIcon', loadUserIcon($uid));
            if ($large = loadUserLargeIcon($uid)) {
                $Field->setField('fieldUserLargeIcon', $large);
            }
            if ($orig = loadUserOriginalIcon($uid)) {
                $Field->setField('fieldUserOrigIcon', $orig);
            }
            $tpl->add('userField', $this->buildField($Field, $tpl));
        }
        // build category field
        if ($this->config['category_info_on'] === 'on') {
            if ($cid !== null) {
                $Field = ($this->config['category_field_on'] === 'on') ? loadCategoryField($cid) : new Field();
                $Field->setField('fieldCategoryName', ACMS_RAM::categoryName($cid));
                $Field->setField('fieldCategoryCode', ACMS_RAM::categoryCode($cid));
                $Field->setField('fieldCategoryUrl', acmsLink([
                    'bid' => $bid,
                    'cid' => $cid,
                ]));
                $Field->setField('fieldCategoryId', $cid);
                $tpl->add('categoryField', $this->buildField($Field, $tpl));
            }
        }
        // build blog field
        if ($this->config['blog_info_on'] === 'on') {
            $Field = ($this->config['blog_field_on'] === 'on') ? loadBlogField($bid) : new Field();
            $Field->setField('fieldBlogName', ACMS_RAM::blogName($bid));
            $Field->setField('fieldBlogCode', ACMS_RAM::blogCode($bid));
            $Field->setField('fieldBlogUrl', acmsLink(['bid' => $bid]));
            $tpl->add('blogField', $this->buildField($Field, $tpl));
        }
        $link = (config('entry_body_link_url') === 'on') ? $row['entry_link'] : '';
        $vars += [
            'status' => $row['entry_status'],
            'titleUrl' => !empty($link) ? $link : $permalink,
            'title' => addPrefixEntryTitle(
                $row['entry_title'],
                $row['entry_status'],
                $row['entry_start_datetime'],
                $row['entry_end_datetime'],
                $row['entry_approval']
            ),
            'inheritUrl' => $inheritUrl,
            'permalink' => $permalink,
            'posterName' => ACMS_RAM::userName($uid),
            'entry:loop.bid' => $bid,
            'entry:loop.uid' => $uid,
            'entry:loop.cid' => $cid,
            'entry:loop.eid' => $eid,
            'entry:loop.bcd' => ACMS_RAM::blogCode($bid),
            'entry:loop.ucd' => ACMS_RAM::userCode($uid),
            'entry:loop.ccd' => ACMS_RAM::categoryCode($cid),
            'entry:loop.ecd' => ACMS_RAM::entryCode($eid),
            'entry:loop.class' => $this->config['loop_class'],
            'sort' => $row['entry_sort'],
            'usort' => $row['entry_user_sort'],
            'csort' => $row['entry_category_sort']
        ];
        if ($link !== '') {
            $vars += [
                'link' => $link,
            ];
        }
        // build date
        if ($this->config['date_on'] === 'on') {
            $vars += $this->buildDate($row['entry_datetime'], $tpl, 'entry:loop');
        }
        if ($this->config['detail_date_on'] === 'on') {
            $vars += $this->buildDate($row['entry_updated_datetime'], $tpl, 'entry:loop', 'udate#');
            $vars += $this->buildDate($row['entry_posted_datetime'], $tpl, 'entry:loop', 'pdate#');
            $vars += $this->buildDate($row['entry_start_datetime'], $tpl, 'entry:loop', 'sdate#');
            $vars += $this->buildDate($row['entry_end_datetime'], $tpl, 'entry:loop', 'edate#');
        }
        // build new
        if (strtotime($row['entry_datetime']) + intval($this->config['newtime']) > requestTime()) {
            $tpl->add(['new:touch', 'entry:loop']); // 後方互換
            $tpl->add(['new', 'entry:loop']);
        }
    }

    /**
     * Not Found
     *
     * @param Template $tpl
     * @return string
     */
    protected function resultsNotFound(Template $tpl): string
    {
        $tpl->add('notFound');
        if ($this->config['notfoundStatus404'] === 'on') {
            httpStatusCode('404 Not Found');
        }
        return $tpl->get();
    }

    /**
     * 指定したマイクロページで会員限定ユニットが含まれているかどうか
     * @param \Acms\Services\Unit\Contracts\Model[] $units エントリーが持つ全てのユニットを含む配列
     * @param int $summaryRange
     * @param int $micropage
     * @return bool
     */
    protected function containsMembersOnlyUnitOnMicroPage(array $units, int $summaryRange, int $micropage): bool
    {
        if ($summaryRange >= count($units)) {
            // 会員限定記事のバーが最後の場合は、会員限定ユニットは含まれない（ページ内のユニットはすべて公開ユニット）
            return false;
        }

        // 公開ユニット内の合計ページ数を取得
        $publicPageAmount = 1;
        foreach ($units as $i => $unit) {
            if ($unit->getUnitType() === 'break' && $i < $summaryRange) {
                // 会員限定記事のバーより前のユニット（= 公開ユニット）の場合のみカウント
                $publicPageAmount += 1;
            }
        }
        // 公開ユニット内の合計ページ数が表示ページより大きい場合は、会員限定ユニットは含まれない（ページ内のユニットはすべて公開ユニット）
        if ($publicPageAmount > $micropage) {
            return false;
        }

        return true;
    }

    /**
     * マイクロページの総ページ数をカウント
     * @param \Acms\Services\Unit\Contracts\Model[] $units
     * @return int
     */
    protected function countMicroPageAmount(array $units): int
    {
        $page = 1;
        foreach ($units as $unit) {
            if ($unit->getUnitType() === 'break') {
                $page += 1;
            }
        }
        return $page;
    }

    /**
     * 指定したマイクロページに表示するユニットで絞り込んで取得
     *
     * @param \Acms\Services\Unit\Contracts\Model[] $units
     * @param int<1, max> $micropage マイクロページ番号
     * @return \Acms\Services\Unit\Contracts\Model[]
     */
    protected function filterUnitsByMicroPage(array $units, int $micropage): array
    {
        $filteredUnits = [];
        $micropageCount = 1;
        foreach ($units as $unit) {
            if ($unit->getUnitType() === 'break') {
                $micropageCount += 1;
            }
            if ($micropageCount === $micropage) {
                $filteredUnits[] = $unit;
            }
            if ($micropageCount > $micropage) {
                break;
            }
        }
        return $filteredUnits;
    }

    /**
     * 指定したマイクロページを分割する改ページユニットを取得
     *
     * @param \Acms\Services\Unit\Contracts\Model[] $units
     * @param int<1, max> $micropage マイクロページ番号
     * @return Acms\Services\Unit\Contracts\Model|null
     */
    protected function getBreakUnitOnMicroPage(array $units, int $micropage): ?Model
    {
        $breakUnits = array_filter($units, function ($unit) {
            return $unit->getUnitType() === 'break';
        });
        $micropageCount = 1;
        foreach ($breakUnits as $breakUnit) {
            if ($micropageCount === $micropage) {
                return $breakUnit;
            }
            $micropageCount += 1;
        }

        return null;
    }

    /**
     * 編集ページかどうか
     *
     * @return bool
     */
    protected function isEntryEditPage(): bool
    {
        return in_array(ADMIN, ['entry-edit', 'entry_editor'], true);
    }

    /**
     * 動的フォーム編集ページかどうか
     *
     * @return bool
     */
    protected function isEntryFormEditPage(): bool
    {
        return ADMIN === 'form2-edit';
    }

    /**
     * エントリー詳細ページかどうか
     *
     * @return bool
     */
    protected function isEntryDetailPage(): bool
    {
        return intval($this->eid) > 0 && strval($this->eid) === strval(intval($this->eid));
    }
}
