diff --git a/plugins/baser-core/src/Service/UtilitiesService.php b/plugins/baser-core/src/Service/UtilitiesService.php
index da1ac33d6e..5679500367 100644
--- a/plugins/baser-core/src/Service/UtilitiesService.php
+++ b/plugins/baser-core/src/Service/UtilitiesService.php
@@ -57,7 +57,23 @@ class UtilitiesService implements UtilitiesServiceInterface
public function verityContentsTree(): bool
{
$contentsTable = TableRegistry::getTableLocator()->get('BaserCore.Contents');
- $result = $this->_verify($contentsTable);
+ return $this->verityTree($contentsTable);
+ }
+
+ /**
+ * ツリー構造をチェックする
+ *
+ * 問題がある場合にはログを出力する
+ *
+ * @param Table $table TreeBehavior を利用しているテーブル
+ * @return bool
+ * @checked
+ * @noTodo
+ * @unitTest
+ */
+ public function verityTree(Table $table): bool
+ {
+ $result = $this->_verify($table);
if ($result !== true) {
foreach($result as $value) {
$this->log(implode(', ', $value));
@@ -97,7 +113,6 @@ protected function _verify(Table $table)
$left = 'lft';
$scope = '1 = 1';
$parent = 'parent_id';
- $plugin = 'BaserCore';
if (!$table->find()->applyOptions(['withDeleted'])->where([$scope])->count()) {
return true;
}
@@ -125,7 +140,7 @@ protected function _verify(Table $table)
}
$table->belongsTo('VerifyParent', [
- 'className' => $plugin . '.' . $table->getAlias(),
+ 'className' => $table->getRegistryAlias(),
'propertyName' => 'VerifyParent',
'foreignKey' => $parent
]);
diff --git a/plugins/baser-core/src/Service/UtilitiesServiceInterface.php b/plugins/baser-core/src/Service/UtilitiesServiceInterface.php
index a00ecf04f9..4cf4434ca9 100644
--- a/plugins/baser-core/src/Service/UtilitiesServiceInterface.php
+++ b/plugins/baser-core/src/Service/UtilitiesServiceInterface.php
@@ -14,6 +14,7 @@
use BaserCore\Annotation\UnitTest;
use BaserCore\Annotation\NoTodo;
use BaserCore\Annotation\Checked;
+use Cake\ORM\Table;
/**
* UtilitiesServiceInterface
@@ -31,6 +32,19 @@ interface UtilitiesServiceInterface
*/
public function verityContentsTree(): bool;
+ /**
+ * ツリー構造をチェックする
+ *
+ * 問題がある場合にはログを出力する
+ *
+ * @param Table $table TreeBehavior を利用しているテーブル
+ * @return bool
+ * @checked
+ * @noTodo
+ * @unitTest
+ */
+ public function verityTree(Table $table): bool;
+
/**
* コンテンツツリーをリセットし全て同階層にする
*
diff --git a/plugins/bc-admin-third/src/bc_blog/js/admin/blog_categories/index.js b/plugins/bc-admin-third/src/bc_blog/js/admin/blog_categories/index.js
index de41051265..33ff322b38 100755
--- a/plugins/bc-admin-third/src/bc_blog/js/admin/blog_categories/index.js
+++ b/plugins/bc-admin-third/src/bc_blog/js/admin/blog_categories/index.js
@@ -10,11 +10,231 @@
$(function () {
+ var scriptData = $("#AdminBlogCategoriesIndexScript");
+ var blogContentId = scriptData.attr('data-blogContentId');
+ var listType = scriptData.attr('data-listType');
+ var addUrl = scriptData.attr('data-addUrl');
+
+ if (listType === '1') {
+ // ツリー形式
+ initTree();
+ $("#GrpChangeTreeOpenClose").show();
+ } else {
+ // 表形式:一括処理
+ $.bcBatch.init({
+ batchUrl: $.bcUtil.apiAdminBaseUrl + 'bc-blog/blog_categories/batch.json'
+ });
+ }
+
+ // 表示形式の切り替え(ツリー形式 / 表形式)
+ $("input[name='ViewSetting[list_type]']").change(function () {
+ $.bcUtil.showLoader();
+ var selected = $("input[name='ViewSetting[list_type]']:checked").val();
+ location.href = $.bcUtil.adminBaseUrl + 'bc-blog/blog_categories/index/' + blogContentId + '?list_type=' + selected;
+ });
+
/**
- * 一括処理実装
+ * ノードに紐づくブログカテゴリのデータ(data-jstree)を返す
+ * @param node
+ * @returns {object}
*/
- $.bcBatch.init({
- batchUrl: $.bcUtil.apiAdminBaseUrl + 'bc-blog/blog_categories/batch.json'
- });
+ function nodeData(node) {
+ return (node && node.data) ? node.data.jstree : {};
+ }
+
+ /**
+ * ツリー(jstree)の初期化
+ */
+ function initTree() {
+ var treeDom = $('#BlogCategoryTreeList');
+ if (!treeDom.length) {
+ return;
+ }
+
+ treeDom.jstree({
+ 'core': {
+ 'themes': {'name': 'proton', 'stripes': true, 'variant': 'large'},
+ 'multiple': false,
+ 'force_text': true,
+ // 自分自身の子孫への移動は jstree 側が禁止する。それ以外は任意の親へ移動可。
+ 'check_callback': true
+ },
+ 'plugins': ['dnd', 'state', 'wholerow', 'contextmenu', 'types'],
+ 'types': {'default': {}},
+ 'state': {'key': 'blog-category-tree-' + blogContentId},
+ 'dnd': {
+ 'large_drop_target': true,
+ 'is_draggable': function () {
+ return true;
+ }
+ },
+ 'contextmenu': {
+ 'show_at_node': false,
+ 'items': buildContextMenu
+ }
+ });
+
+ var jstreeApi = treeDom.jstree(true);
+
+ // 展開・折りたたみ
+ $("#BtnOpenTree").click(function () {
+ jstreeApi.open_all();
+ });
+ $("#BtnCloseTree").click(function () {
+ jstreeApi.close_all();
+ });
+
+ // ドラッグ&ドロップによる移動(並び替え・再親付け)
+ treeDom.on('move_node.jstree', function (e, data) {
+ orderCategory(jstreeApi, data);
+ });
+ }
+
+ /**
+ * ドラッグ&ドロップ完了時に、移動内容をサーバへ保存する
+ * @param jstreeApi
+ * @param data move_node.jstree のイベントデータ
+ */
+ function orderCategory(jstreeApi, data) {
+ var node = data.node;
+ var origin = nodeData(node);
+
+ // 移動先の親カテゴリ(ルートは空)
+ var targetParentId = '';
+ if (data.parent !== '#') {
+ targetParentId = nodeData(jstreeApi.get_node(data.parent)).categoryId;
+ }
+ // 移動先で、自分の直後にくる兄弟(=この上に配置する対象)。無ければ末尾。
+ var parentChildren = jstreeApi.get_node(data.parent).children;
+ var nextNodeId = parentChildren[data.position + 1];
+ var targetId = nextNodeId ? nodeData(jstreeApi.get_node(nextNodeId)).categoryId : '';
+
+ $.bcToken.check(function () {
+ return $.ajax({
+ url: $.bcUtil.apiAdminBaseUrl + 'bc-blog/blog_categories/move.json',
+ type: 'PATCH',
+ dataType: 'json',
+ data: {
+ origin: {id: origin.categoryId, parentId: origin.parentId},
+ target: {id: targetId, parentId: targetParentId},
+ _csrfToken: $.bcToken.key
+ },
+ beforeSend: function () {
+ $.bcUtil.hideMessage();
+ $.bcUtil.showLoader();
+ },
+ success: function () {
+ // 移動先の親を記憶し、続けての移動に備える
+ origin.parentId = targetParentId;
+ },
+ error: function (XMLHttpRequest, textStatus, errorThrown) {
+ // サーバ状態は変わっていないため、再読込で元の並びへ戻す
+ var errorMessage = '';
+ if (XMLHttpRequest.status === 404) {
+ errorMessage = '
' + bcI18n.commonNotFoundProgramMessage;
+ } else if (XMLHttpRequest.responseText) {
+ errorMessage = '
' + JSON.parse(XMLHttpRequest.responseText).message;
+ } else {
+ errorMessage = '
' + errorThrown;
+ }
+ $.bcUtil.showAlertMessage(bcI18n.commonBatchExecFailedMessage + '(' + XMLHttpRequest.status + ')' + errorMessage);
+ location.reload();
+ },
+ complete: function () {
+ $.bcUtil.hideLoader();
+ }
+ });
+ }, {hideLoader: false});
+ }
+
+ /**
+ * 右クリックメニューを構築する
+ * @param node
+ * @returns {object}
+ */
+ function buildContextMenu(node) {
+ var data = nodeData(node);
+ var menu = {};
+
+ // 確認(フロントのカテゴリ一覧を別タブで開く)
+ if (data.previewUrl) {
+ menu.view = {
+ label: bcI18n.bcTreeCheck,
+ icon: 'bca-icon--preview',
+ action: function () {
+ window.open(data.previewUrl, '_blank');
+ }
+ };
+ }
+
+ // 編集
+ menu.edit = {
+ label: bcI18n.bcTreeEdit,
+ icon: 'bca-icon--edit',
+ action: function () {
+ location.href = data.editUrl;
+ }
+ };
+
+ // 子カテゴリを追加
+ menu.add = {
+ label: bcI18n.bcTreeAddChild,
+ icon: 'bca-icon--add',
+ action: function () {
+ location.href = addUrl + '?parent_id=' + data.categoryId;
+ }
+ };
+
+ // 削除
+ menu.delete = {
+ label: bcI18n.bcTreeDelete,
+ icon: 'bca-icon--delete',
+ action: function () {
+ deleteCategory(node, data);
+ }
+ };
+
+ return menu;
+ }
+
+ /**
+ * カテゴリを削除する
+ * @param node
+ * @param data
+ */
+ function deleteCategory(node, data) {
+ var title = $("#BlogCategoryTreeList").jstree(true).get_text(node);
+ if (!confirm(bcI18n.blogCategoryConfirmDelete.replace('%s', title))) {
+ return;
+ }
+ $.bcToken.check(function () {
+ return $.ajax({
+ url: $.bcUtil.apiAdminBaseUrl + 'bc-blog/blog_categories/delete/' + data.categoryId + '.json',
+ type: 'POST',
+ dataType: 'json',
+ data: {
+ _csrfToken: $.bcToken.key
+ },
+ beforeSend: function () {
+ $.bcUtil.hideMessage();
+ $.bcUtil.showLoader();
+ },
+ success: function () {
+ // 削除後はサーバの状態に合わせて再読込する
+ location.reload();
+ },
+ error: function (XMLHttpRequest) {
+ var errorMessage = '';
+ if (XMLHttpRequest.responseText) {
+ errorMessage = '
' + JSON.parse(XMLHttpRequest.responseText).message;
+ }
+ $.bcUtil.showAlertMessage(bcI18n.blogCategoryDeleteFailed + errorMessage);
+ },
+ complete: function () {
+ $.bcUtil.hideLoader();
+ }
+ });
+ }, {hideLoader: false});
+ }
});
diff --git a/plugins/bc-admin-third/templates/Admin/Utilities/index.php b/plugins/bc-admin-third/templates/Admin/Utilities/index.php
index 2a90d272e5..bbe315caee 100644
--- a/plugins/bc-admin-third/templates/Admin/Utilities/index.php
+++ b/plugins/bc-admin-third/templates/Admin/Utilities/index.php
@@ -67,6 +67,16 @@
]) ?>
+dispatchLayerEvent('afterUtilitiesIndex', [], ['layer' => 'View', 'class' => '', 'plugin' => '']);
+ if ($event !== false) {
+ echo ($event->getResult() === null || $event->getResult() === true)? '' : $event->getResult();
+ }
+}
+?>
+
BcAdminForm->secure() ?>
diff --git a/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/BlogCategories/index.php b/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/BlogCategories/index.php
index a0ae892f68..185d841f3c 100755
--- a/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/BlogCategories/index.php
+++ b/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/BlogCategories/index.php
@@ -13,13 +13,14 @@
* [ADMIN] ブログカテゴリ 一覧
* @var BcBlog\View\BlogAdminAppView $this
* @var BcBlog\Model\Entity\BlogContent $blogContent
+ * @var string $template 表示テンプレート(index_list: 表形式 / index_tree: ツリー形式)
* @checked
* @noTodo
* @unitTest
*/
$this->BcAdmin->setTitle(__d('baser_core', '{0}|カテゴリ一覧', $blogContent->content->title));
$this->BcAdmin->setHelp('blog_categories_index');
-$this->BcBaser->js('BcBlog.admin/blog_categories/index.bundle', false);
+$this->BcBaser->element('BlogCategories/index_setup', ['blogContent' => $blogContent, 'template' => $template]);
$this->BcAdmin->addAdminMainBodyHeaderLinks([
'url' => ['action' => 'add', $blogContent->id],
'title' => __d('baser_core', '新規追加'),
@@ -27,6 +28,10 @@
?>
+
+
+BcBaser->element('BlogCategories/index_view_setting') ?>
+
- BcBaser->element('BlogCategories/index_list') ?>
+ BcBaser->element('BlogCategories/' . $template) ?>
diff --git a/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/element/BlogCategories/index_list.php b/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/element/BlogCategories/index_list.php
index 7f95c21a50..c3fcb04b90 100755
--- a/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/element/BlogCategories/index_list.php
+++ b/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/element/BlogCategories/index_list.php
@@ -79,7 +79,7 @@
foreach($rowIdTmps as $rowIdTmp) {
$rowGroupId[] = 'row-group-' . $rowIdTmp;
}
- $rowGroupClass = ' class="depth-' . $blogCategory->depth . ' ' . implode(' ', $rowGroupId) . '"';
+ $rowGroupClass = ' class="sortable depth-' . $blogCategory->depth . ' ' . implode(' ', $rowGroupId) . '"';
?>
depth ?>
BcBaser->element('BlogCategories/index_row', ['blogCategory' => $blogCategory, 'rowGroupClass' => $rowGroupClass]) ?>
diff --git a/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/element/BlogCategories/index_list_tree.php b/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/element/BlogCategories/index_list_tree.php
new file mode 100644
index 0000000000..38f6e2a9c3
--- /dev/null
+++ b/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/element/BlogCategories/index_list_tree.php
@@ -0,0 +1,42 @@
+
+ * Copyright (c) NPO baser foundation
+ *
+ * @copyright Copyright (c) NPO baser foundation
+ * @link https://basercms.net baserCMS Project
+ * @since 5.0.0
+ * @license https://basercms.net/license/index.html MIT License
+ */
+
+/**
+ * [ADMIN] ブログカテゴリ 一覧 ツリー(再帰)
+ * @var BcBlog\View\BlogAdminAppView $this
+ * @var iterable $blogCategories 親子ネスト(children)付きのカテゴリ
+ * @var BcBlog\Model\Entity\BlogContent $blogContent
+ * @checked
+ * @noTodo
+ * @unitTest
+ */
+?>
+
+
+ BcBaser->getUrl(['action' => 'edit', $blogContent->id, $blogCategory->id]);
+ $previewUrl = $this->Blog->getCategoryUrl($blogCategory->id);
+ ?>
+ - title);
+ if (!empty($blogCategory->children)) {
+ $this->BcBaser->element('BlogCategories/index_list_tree', ['blogCategories' => $blogCategory->children, 'blogContent' => $blogContent]);
+ }
+ ?>
+
+
diff --git a/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/element/BlogCategories/index_setup.php b/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/element/BlogCategories/index_setup.php
new file mode 100644
index 0000000000..56f9b057c6
--- /dev/null
+++ b/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/element/BlogCategories/index_setup.php
@@ -0,0 +1,39 @@
+
+ * Copyright (c) NPO baser foundation
+ *
+ * @copyright Copyright (c) NPO baser foundation
+ * @link https://basercms.net baserCMS Project
+ * @since 5.0.0
+ * @license https://basercms.net/license/index.html MIT License
+ */
+
+/**
+ * [ADMIN] ブログカテゴリ 一覧 セットアップ(JS/CSS 読込)
+ * @var BcBlog\View\BlogAdminAppView $this
+ * @var BcBlog\Model\Entity\BlogContent $blogContent
+ * @var string $template
+ * @checked
+ * @noTodo
+ * @unitTest
+ */
+$this->BcBaser->i18nScript([
+ 'blogCategoryConfirmDelete' => __d('baser_core', "カテゴリ「%s」を削除してもよろしいですか?\n※ このカテゴリに関連する記事は、どのカテゴリにも関連しない状態として残ります。"),
+ 'blogCategoryDeleteSuccess' => __d('baser_core', 'カテゴリを削除しました。'),
+ 'blogCategoryDeleteFailed' => __d('baser_core', '削除に失敗しました。'),
+ 'bcTreeCheck' => __d('baser_core', '確認'),
+ 'bcTreeEdit' => __d('baser_core', '編集'),
+ 'bcTreeAddChild' => __d('baser_core', '子カテゴリを追加'),
+ 'bcTreeDelete' => __d('baser_core', '削除'),
+]);
+$this->BcBaser->js('BcBlog.admin/blog_categories/index.bundle', false, [
+ 'id' => 'AdminBlogCategoriesIndexScript',
+ 'data-blogContentId' => $blogContent->id,
+ 'data-listType' => ($template === 'index_tree') ? 1 : 2,
+ 'data-addUrl' => $this->BcBaser->getUrl(['action' => 'add', $blogContent->id]),
+]);
+if ($template === 'index_tree') {
+ $this->BcBaser->js(['vendor/jquery.jstree-3.3.8/jstree.min'], false);
+ $this->BcBaser->css('../js/vendor/jquery.jstree-3.3.8/themes/proton/style.min', false);
+}
diff --git a/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/element/BlogCategories/index_tree.php b/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/element/BlogCategories/index_tree.php
new file mode 100644
index 0000000000..90f9a666fe
--- /dev/null
+++ b/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/element/BlogCategories/index_tree.php
@@ -0,0 +1,30 @@
+
+ * Copyright (c) NPO baser foundation
+ *
+ * @copyright Copyright (c) NPO baser foundation
+ * @link https://basercms.net baserCMS Project
+ * @since 5.0.0
+ * @license https://basercms.net/license/index.html MIT License
+ */
+
+/**
+ * [ADMIN] ブログカテゴリ 一覧 ツリー表示
+ * @var BcBlog\View\BlogAdminAppView $this
+ * @var \Cake\Datasource\ResultSetInterface $blogCategories
+ * @var BcBlog\Model\Entity\BlogContent $blogContent
+ * @checked
+ * @noTodo
+ * @unitTest
+ */
+?>
+
+
+count()): ?>
+
+ BcBaser->element('BlogCategories/index_list_tree', ['blogCategories' => $blogCategories, 'blogContent' => $blogContent]) ?>
+
+
+
+
diff --git a/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/element/BlogCategories/index_view_setting.php b/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/element/BlogCategories/index_view_setting.php
new file mode 100644
index 0000000000..9c8b86d31a
--- /dev/null
+++ b/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/element/BlogCategories/index_view_setting.php
@@ -0,0 +1,43 @@
+
+ * Copyright (c) NPO baser foundation
+ *
+ * @copyright Copyright (c) NPO baser foundation
+ * @link https://basercms.net baserCMS Project
+ * @since 5.0.0
+ * @license https://basercms.net/license/index.html MIT License
+ */
+
+/**
+ * [ADMIN] ブログカテゴリ 一覧 表示切替
+ * @var BcBlog\View\BlogAdminAppView $this
+ * @checked
+ * @noTodo
+ * @unitTest
+ */
+$listType = $this->request->getQuery('list_type') ?: 2;
+?>
+
+
+
+
+
+
+ BcAdminForm->control('ViewSetting.list_type', [
+ 'type' => 'radio',
+ 'options' => [
+ 1 => __d('baser_core', 'ツリー形式'),
+ 2 => __d('baser_core', '表形式')
+ ],
+ 'value' => $listType,
+ 'hiddenField' => false
+ ]) ?>
+
+
+
+
+
+
+
+
diff --git a/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/element/Utilities/blog_categories_tree.php b/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/element/Utilities/blog_categories_tree.php
new file mode 100644
index 0000000000..a60fed62ad
--- /dev/null
+++ b/plugins/bc-admin-third/templates/plugin/BcBlog/Admin/element/Utilities/blog_categories_tree.php
@@ -0,0 +1,37 @@
+
+ * Copyright (c) NPO baser foundation
+ *
+ * @copyright Copyright (c) NPO baser foundation
+ * @link https://basercms.net baserCMS Project
+ * @since 5.2.0
+ * @license https://basercms.net/license/index.html MIT License
+ */
+
+/**
+ * [ADMIN] ユーティリティ ブログカテゴリのツリー構造チェック・リセット
+ * @var \BaserCore\View\BcAdminAppView $this
+ * @checked
+ * @noTodo
+ * @unitTest
+ */
+?>
+
+
+
+
+
+
+
+ BcAdminForm->postLink(
+ __d('baser_core', 'ツリー構造をチェックする'),
+ ['plugin' => 'BcBlog', 'controller' => 'BlogCategories', 'action' => 'verity_tree'],
+ ['class' => 'bca-btn']
+ ) ?>
+ BcAdminForm->postLink(
+ __d('baser_core', 'ツリー構造リセット'),
+ ['plugin' => 'BcBlog', 'controller' => 'BlogCategories', 'action' => 'reset_tree'],
+ ['class' => 'bca-btn', 'confirm' => __d('baser_core', 'ブログカテゴリのツリー構造をリセットします。本当によろしいですか?')]
+ ) ?>
+
diff --git a/plugins/bc-admin-third/webroot/bc_blog/js/admin/blog_categories/index.bundle.js b/plugins/bc-admin-third/webroot/bc_blog/js/admin/blog_categories/index.bundle.js
index 64fe184f96..b8a3f22887 100755
--- a/plugins/bc-admin-third/webroot/bc_blog/js/admin/blog_categories/index.bundle.js
+++ b/plugins/bc-admin-third/webroot/bc_blog/js/admin/blog_categories/index.bundle.js
@@ -7,5 +7,5 @@
* @since 5.0.0
* @license https://basercms.net/license/index.html MIT License
*/
-$(function(){$.bcBatch.init({batchUrl:$.bcUtil.apiAdminBaseUrl+"bc-blog/blog_categories/batch.json"})});
+$(function(){var e=$("#AdminBlogCategoriesIndexScript"),t=e.attr("data-blogContentId"),n=e.attr("data-listType"),o=e.attr("data-addUrl");function a(e){return e&&e.data?e.data.jstree:{}}function c(e){var t=a(e),n={};return t.previewUrl&&(n.view={label:bcI18n.bcTreeCheck,icon:"bca-icon--preview",action:function(){window.open(t.previewUrl,"_blank")}}),n.edit={label:bcI18n.bcTreeEdit,icon:"bca-icon--edit",action:function(){location.href=t.editUrl}},n.add={label:bcI18n.bcTreeAddChild,icon:"bca-icon--add",action:function(){location.href=o+"?parent_id="+t.categoryId}},n.delete={label:bcI18n.bcTreeDelete,icon:"bca-icon--delete",action:function(){!function(e,t){var n=$("#BlogCategoryTreeList").jstree(!0).get_text(e);confirm(bcI18n.blogCategoryConfirmDelete.replace("%s",n))&&$.bcToken.check(function(){return $.ajax({url:$.bcUtil.apiAdminBaseUrl+"bc-blog/blog_categories/delete/"+t.categoryId+".json",type:"POST",dataType:"json",data:{_csrfToken:$.bcToken.key},beforeSend:function(){$.bcUtil.hideMessage(),$.bcUtil.showLoader()},success:function(){location.reload()},error:function(e){var t="";e.responseText&&(t="
"+JSON.parse(e.responseText).message),$.bcUtil.showAlertMessage(bcI18n.blogCategoryDeleteFailed+t)},complete:function(){$.bcUtil.hideLoader()}})},{hideLoader:!1})}(e,t)}},n}"1"===n?(function(){var e=$("#BlogCategoryTreeList");if(e.length){e.jstree({core:{themes:{name:"proton",stripes:!0,variant:"large"},multiple:!1,force_text:!0,check_callback:!0},plugins:["dnd","state","wholerow","contextmenu","types"],types:{default:{}},state:{key:"blog-category-tree-"+t},dnd:{large_drop_target:!0,is_draggable:function(){return!0}},contextmenu:{show_at_node:!1,items:c}});var n=e.jstree(!0);$("#BtnOpenTree").click(function(){n.open_all()}),$("#BtnCloseTree").click(function(){n.close_all()}),e.on("move_node.jstree",function(e,t){!function(e,t){var n=t.node,o=a(n),c="";"#"!==t.parent&&(c=a(e.get_node(t.parent)).categoryId);var r=e.get_node(t.parent).children,i=r[t.position+1],l=i?a(e.get_node(i)).categoryId:"";$.bcToken.check(function(){return $.ajax({url:$.bcUtil.apiAdminBaseUrl+"bc-blog/blog_categories/move.json",type:"PATCH",dataType:"json",data:{origin:{id:o.categoryId,parentId:o.parentId},target:{id:l,parentId:c},_csrfToken:$.bcToken.key},beforeSend:function(){$.bcUtil.hideMessage(),$.bcUtil.showLoader()},success:function(){o.parentId=c},error:function(e,t,n){var o="";o=404===e.status?"
"+bcI18n.commonNotFoundProgramMessage:e.responseText?"
"+JSON.parse(e.responseText).message:"
"+n,$.bcUtil.showAlertMessage(bcI18n.commonBatchExecFailedMessage+"("+e.status+")"+o),location.reload()},complete:function(){$.bcUtil.hideLoader()}})},{hideLoader:!1})}(n,t)})}}(),$("#GrpChangeTreeOpenClose").show()):$.bcBatch.init({batchUrl:$.bcUtil.apiAdminBaseUrl+"bc-blog/blog_categories/batch.json"}),$("input[name='ViewSetting[list_type]']").change(function(){$.bcUtil.showLoader();var e=$("input[name='ViewSetting[list_type]']:checked").val();location.href=$.bcUtil.adminBaseUrl+"bc-blog/blog_categories/index/"+t+"?list_type="+e})});
//# sourceMappingURL=index.bundle.js.map
\ No newline at end of file
diff --git a/plugins/bc-admin-third/webroot/bc_blog/js/admin/blog_categories/index.bundle.js.map b/plugins/bc-admin-third/webroot/bc_blog/js/admin/blog_categories/index.bundle.js.map
index 57f64c84f5..10d3836ae1 100755
--- a/plugins/bc-admin-third/webroot/bc_blog/js/admin/blog_categories/index.bundle.js.map
+++ b/plugins/bc-admin-third/webroot/bc_blog/js/admin/blog_categories/index.bundle.js.map
@@ -1 +1 @@
-{"version":3,"file":"bc_blog/js/admin/blog_categories/index.bundle.js","mappings":";;;;;;;;;AAUAA,EAAE,WAKEA,EAAEC,QAAQC,KAAK,CACXC,SAAUH,EAAEI,OAAOC,gBAAkB,sCAG7C","sources":["webpack://bc-admin-third/./src/bc_blog/js/admin/blog_categories/index.js"],"sourcesContent":["/**\n * baserCMS : Based Website Development Project
\n * Copyright (c) NPO baser foundation \n *\n * @copyright Copyright (c) NPO baser foundation\n * @link https://basercms.net baserCMS Project\n * @since 5.0.0\n * @license https://basercms.net/license/index.html MIT License\n */\n\n$(function () {\n\n /**\n * 一括処理実装\n */\n $.bcBatch.init({\n batchUrl: $.bcUtil.apiAdminBaseUrl + 'bc-blog/blog_categories/batch.json'\n });\n\n});\n"],"names":["$","bcBatch","init","batchUrl","bcUtil","apiAdminBaseUrl"],"sourceRoot":""}
\ No newline at end of file
+{"version":3,"file":"bc_blog/js/admin/blog_categories/index.bundle.js","mappings":"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","sources":["webpack://bc-admin-third/./src/bc_blog/js/admin/blog_categories/index.js"],"sourcesContent":["/**\n * baserCMS : Based Website Development Project \n * Copyright (c) NPO baser foundation \n *\n * @copyright Copyright (c) NPO baser foundation\n * @link https://basercms.net baserCMS Project\n * @since 5.0.0\n * @license https://basercms.net/license/index.html MIT License\n */\n\n$(function () {\n\n var scriptData = $(\"#AdminBlogCategoriesIndexScript\");\n var blogContentId = scriptData.attr('data-blogContentId');\n var listType = scriptData.attr('data-listType');\n var addUrl = scriptData.attr('data-addUrl');\n\n if (listType === '1') {\n // ツリー形式\n initTree();\n $(\"#GrpChangeTreeOpenClose\").show();\n } else {\n // 表形式:一括処理\n $.bcBatch.init({\n batchUrl: $.bcUtil.apiAdminBaseUrl + 'bc-blog/blog_categories/batch.json'\n });\n }\n\n // 表示形式の切り替え(ツリー形式 / 表形式)\n $(\"input[name='ViewSetting[list_type]']\").change(function () {\n $.bcUtil.showLoader();\n var selected = $(\"input[name='ViewSetting[list_type]']:checked\").val();\n location.href = $.bcUtil.adminBaseUrl + 'bc-blog/blog_categories/index/' + blogContentId + '?list_type=' + selected;\n });\n\n /**\n * ノードに紐づくブログカテゴリのデータ(data-jstree)を返す\n * @param node\n * @returns {object}\n */\n function nodeData(node) {\n return (node && node.data) ? node.data.jstree : {};\n }\n\n /**\n * ツリー(jstree)の初期化\n */\n function initTree() {\n var treeDom = $('#BlogCategoryTreeList');\n if (!treeDom.length) {\n return;\n }\n\n treeDom.jstree({\n 'core': {\n 'themes': {'name': 'proton', 'stripes': true, 'variant': 'large'},\n 'multiple': false,\n 'force_text': true,\n // 自分自身の子孫への移動は jstree 側が禁止する。それ以外は任意の親へ移動可。\n 'check_callback': true\n },\n 'plugins': ['dnd', 'state', 'wholerow', 'contextmenu', 'types'],\n 'types': {'default': {}},\n 'state': {'key': 'blog-category-tree-' + blogContentId},\n 'dnd': {\n 'large_drop_target': true,\n 'is_draggable': function () {\n return true;\n }\n },\n 'contextmenu': {\n 'show_at_node': false,\n 'items': buildContextMenu\n }\n });\n\n var jstreeApi = treeDom.jstree(true);\n\n // 展開・折りたたみ\n $(\"#BtnOpenTree\").click(function () {\n jstreeApi.open_all();\n });\n $(\"#BtnCloseTree\").click(function () {\n jstreeApi.close_all();\n });\n\n // ドラッグ&ドロップによる移動(並び替え・再親付け)\n treeDom.on('move_node.jstree', function (e, data) {\n orderCategory(jstreeApi, data);\n });\n }\n\n /**\n * ドラッグ&ドロップ完了時に、移動内容をサーバへ保存する\n * @param jstreeApi\n * @param data move_node.jstree のイベントデータ\n */\n function orderCategory(jstreeApi, data) {\n var node = data.node;\n var origin = nodeData(node);\n\n // 移動先の親カテゴリ(ルートは空)\n var targetParentId = '';\n if (data.parent !== '#') {\n targetParentId = nodeData(jstreeApi.get_node(data.parent)).categoryId;\n }\n // 移動先で、自分の直後にくる兄弟(=この上に配置する対象)。無ければ末尾。\n var parentChildren = jstreeApi.get_node(data.parent).children;\n var nextNodeId = parentChildren[data.position + 1];\n var targetId = nextNodeId ? nodeData(jstreeApi.get_node(nextNodeId)).categoryId : '';\n\n $.bcToken.check(function () {\n return $.ajax({\n url: $.bcUtil.apiAdminBaseUrl + 'bc-blog/blog_categories/move.json',\n type: 'PATCH',\n dataType: 'json',\n data: {\n origin: {id: origin.categoryId, parentId: origin.parentId},\n target: {id: targetId, parentId: targetParentId},\n _csrfToken: $.bcToken.key\n },\n beforeSend: function () {\n $.bcUtil.hideMessage();\n $.bcUtil.showLoader();\n },\n success: function () {\n // 移動先の親を記憶し、続けての移動に備える\n origin.parentId = targetParentId;\n },\n error: function (XMLHttpRequest, textStatus, errorThrown) {\n // サーバ状態は変わっていないため、再読込で元の並びへ戻す\n var errorMessage = '';\n if (XMLHttpRequest.status === 404) {\n errorMessage = '
' + bcI18n.commonNotFoundProgramMessage;\n } else if (XMLHttpRequest.responseText) {\n errorMessage = '
' + JSON.parse(XMLHttpRequest.responseText).message;\n } else {\n errorMessage = '
' + errorThrown;\n }\n $.bcUtil.showAlertMessage(bcI18n.commonBatchExecFailedMessage + '(' + XMLHttpRequest.status + ')' + errorMessage);\n location.reload();\n },\n complete: function () {\n $.bcUtil.hideLoader();\n }\n });\n }, {hideLoader: false});\n }\n\n /**\n * 右クリックメニューを構築する\n * @param node\n * @returns {object}\n */\n function buildContextMenu(node) {\n var data = nodeData(node);\n var menu = {};\n\n // 確認(フロントのカテゴリ一覧を別タブで開く)\n if (data.previewUrl) {\n menu.view = {\n label: bcI18n.bcTreeCheck,\n icon: 'bca-icon--preview',\n action: function () {\n window.open(data.previewUrl, '_blank');\n }\n };\n }\n\n // 編集\n menu.edit = {\n label: bcI18n.bcTreeEdit,\n icon: 'bca-icon--edit',\n action: function () {\n location.href = data.editUrl;\n }\n };\n\n // 子カテゴリを追加\n menu.add = {\n label: bcI18n.bcTreeAddChild,\n icon: 'bca-icon--add',\n action: function () {\n location.href = addUrl + '?parent_id=' + data.categoryId;\n }\n };\n\n // 削除\n menu.delete = {\n label: bcI18n.bcTreeDelete,\n icon: 'bca-icon--delete',\n action: function () {\n deleteCategory(node, data);\n }\n };\n\n return menu;\n }\n\n /**\n * カテゴリを削除する\n * @param node\n * @param data\n */\n function deleteCategory(node, data) {\n var title = $(\"#BlogCategoryTreeList\").jstree(true).get_text(node);\n if (!confirm(bcI18n.blogCategoryConfirmDelete.replace('%s', title))) {\n return;\n }\n $.bcToken.check(function () {\n return $.ajax({\n url: $.bcUtil.apiAdminBaseUrl + 'bc-blog/blog_categories/delete/' + data.categoryId + '.json',\n type: 'POST',\n dataType: 'json',\n data: {\n _csrfToken: $.bcToken.key\n },\n beforeSend: function () {\n $.bcUtil.hideMessage();\n $.bcUtil.showLoader();\n },\n success: function () {\n // 削除後はサーバの状態に合わせて再読込する\n location.reload();\n },\n error: function (XMLHttpRequest) {\n var errorMessage = '';\n if (XMLHttpRequest.responseText) {\n errorMessage = '
' + JSON.parse(XMLHttpRequest.responseText).message;\n }\n $.bcUtil.showAlertMessage(bcI18n.blogCategoryDeleteFailed + errorMessage);\n },\n complete: function () {\n $.bcUtil.hideLoader();\n }\n });\n }, {hideLoader: false});\n }\n\n});\n"],"names":["$","scriptData","blogContentId","attr","listType","addUrl","nodeData","node","data","jstree","buildContextMenu","menu","previewUrl","view","label","bcI18n","bcTreeCheck","icon","action","window","open","edit","bcTreeEdit","location","href","editUrl","add","bcTreeAddChild","categoryId","bcTreeDelete","title","get_text","confirm","blogCategoryConfirmDelete","replace","bcToken","check","ajax","url","bcUtil","apiAdminBaseUrl","type","dataType","_csrfToken","key","beforeSend","hideMessage","showLoader","success","reload","error","XMLHttpRequest","errorMessage","responseText","JSON","parse","message","showAlertMessage","blogCategoryDeleteFailed","complete","hideLoader","deleteCategory","treeDom","length","jstreeApi","click","open_all","close_all","on","e","origin","targetParentId","parent","get_node","parentChildren","children","nextNodeId","position","targetId","id","parentId","target","textStatus","errorThrown","status","commonNotFoundProgramMessage","commonBatchExecFailedMessage","orderCategory","initTree","show","bcBatch","init","batchUrl","change","selected","val","adminBaseUrl"],"sourceRoot":""}
\ No newline at end of file
diff --git a/plugins/bc-blog/config/permission.php b/plugins/bc-blog/config/permission.php
index 6f50120ced..e1f7ac40bf 100755
--- a/plugins/bc-blog/config/permission.php
+++ b/plugins/bc-blog/config/permission.php
@@ -28,6 +28,8 @@
'Add' => ['title' => __d('baser_core', '新規追加'), 'url' => '/baser/admin/bc-blog/blog_categories/add/*', 'method' => 'POST', 'auth' => true],
'Edit' => ['title' => __d('baser_core', '編集'), 'url' => '/baser/admin/bc-blog/blog_categories/edit/*', 'method' => 'POST', 'auth' => true],
'Delete' => ['title' => __d('baser_core', '削除'), 'url' => '/baser/admin/bc-blog/blog_categories/delete/*', 'method' => 'POST', 'auth' => true],
+ 'VerityTree' => ['title' => __d('baser_core', 'ツリー構造チェック'), 'url' => '/baser/admin/bc-blog/blog_categories/verity_tree', 'method' => 'POST', 'auth' => false],
+ 'ResetTree' => ['title' => __d('baser_core', 'ツリー構造リセット'), 'url' => '/baser/admin/bc-blog/blog_categories/reset_tree', 'method' => 'POST', 'auth' => false],
]
],
@@ -93,6 +95,7 @@
'Edit' => ['title' => __d('baser_core', '編集'), 'url' => '/baser/api/admin/bc-blog/blog_categories/edit/*.json', 'method' => 'POST', 'auth' => true],
'Index' => ['title' => __d('baser_core', '一覧取得'), 'url' => '/baser/api/admin/bc-blog/blog_categories/index/*.json', 'method' => 'GET', 'auth' => true],
'List' => ['title' => __d('baser_core', 'リスト取得'), 'url' => '/baser/api/admin/bc-blog/blog_categories/list/*.json', 'method' => 'GET', 'auth' => true],
+ 'Move' => ['title' => __d('baser_core', '移動'), 'url' => '/baser/api/admin/bc-blog/blog_categories/move.json', 'method' => 'POST', 'auth' => true],
'View' => ['title' => __d('baser_core', '単一取得'), 'url' => '/baser/api/admin/bc-blog/blog_categories/view/*.json', 'method' => 'GET', 'auth' => true],
]
],
diff --git a/plugins/bc-blog/src/Controller/Admin/BlogCategoriesController.php b/plugins/bc-blog/src/Controller/Admin/BlogCategoriesController.php
index 72f1736e34..29033a0848 100755
--- a/plugins/bc-blog/src/Controller/Admin/BlogCategoriesController.php
+++ b/plugins/bc-blog/src/Controller/Admin/BlogCategoriesController.php
@@ -41,7 +41,12 @@ public function index(
int $blogContentId
)
{
- $this->set($service->getViewVarsForIndex($blogContentId));
+ // 表示形式(list_type)をセッションに保持し、追加・削除後のリダイレクトでも維持する
+ $this->setViewConditions('BlogCategories', ['default' => ['query' => [
+ 'list_type' => 2,
+ ]]]);
+ $listType = $this->getRequest()->getQuery('list_type');
+ $this->set($service->getViewVarsForIndex($blogContentId, $listType !== null ? (int)$listType : null));
}
/**
@@ -79,9 +84,10 @@ public function add(BlogCategoriesAdminServiceInterface $service, string $blogCo
$this->BcMessage->setError(__d('baser_core', '入力エラーです。内容を修正してください。'));
}
}
+ $parentId = $this->getRequest()->getQuery('parent_id');
$this->set($service->getViewVarsForAdd(
$blogContentId,
- $blogCategory ?? $service->getNew($blogContentId)
+ $blogCategory ?? $service->getNew((int)$blogContentId, $parentId? (int)$parentId : null)
));
}
@@ -152,4 +158,44 @@ public function delete(BlogCategoriesServiceInterface $service, int $blogContent
$this->redirect(['action' => 'index', $blogContentId]);
}
+ /**
+ * [ADMIN] ブログカテゴリのツリー構造のチェックを行う
+ *
+ * 問題がある場合にはログを出力する
+ *
+ * @param BlogCategoriesServiceInterface $service
+ * @checked
+ * @noTodo
+ * @unitTest
+ */
+ public function verity_tree(BlogCategoriesServiceInterface $service)
+ {
+ $this->request->allowMethod(['post']);
+ if (!$service->verityTree()) {
+ $this->BcMessage->setError(__d('baser_core', 'ブログカテゴリのツリー構造に問題があります。ログを確認してください。'));
+ } else {
+ $this->BcMessage->setSuccess(__d('baser_core', 'ブログカテゴリのツリー構造に問題はありません。'), false);
+ }
+ $this->redirect(['plugin' => 'BaserCore', 'controller' => 'Utilities', 'action' => 'index']);
+ }
+
+ /**
+ * [ADMIN] ブログカテゴリのツリー構造をリセットする
+ *
+ * @param BlogCategoriesServiceInterface $service
+ * @checked
+ * @noTodo
+ * @unitTest
+ */
+ public function reset_tree(BlogCategoriesServiceInterface $service)
+ {
+ $this->request->allowMethod(['post']);
+ if ($service->resetTree()) {
+ $this->BcMessage->setSuccess(__d('baser_core', 'ブログカテゴリのツリー構造をリセットしました。'));
+ } else {
+ $this->BcMessage->setError(__d('baser_core', 'ブログカテゴリのツリー構造のリセットに失敗しました。'));
+ }
+ $this->redirect(['plugin' => 'BaserCore', 'controller' => 'Utilities', 'action' => 'index']);
+ }
+
}
diff --git a/plugins/bc-blog/src/Controller/Api/Admin/BlogCategoriesController.php b/plugins/bc-blog/src/Controller/Api/Admin/BlogCategoriesController.php
index ec6093a384..88b1d805fa 100755
--- a/plugins/bc-blog/src/Controller/Api/Admin/BlogCategoriesController.php
+++ b/plugins/bc-blog/src/Controller/Api/Admin/BlogCategoriesController.php
@@ -278,4 +278,43 @@ public function delete(BlogCategoriesServiceInterface $service, $blogCategoryId)
]);
$this->viewBuilder()->setOption('serialize', ['blogCategory', 'message']);
}
+
+ /**
+ * [API] カテゴリの配置を移動する(並び替え・再親付け)
+ *
+ * @param BlogCategoriesServiceInterface $service
+ * @checked
+ * @noTodo
+ * @unitTest
+ */
+ public function move(BlogCategoriesServiceInterface $service)
+ {
+ $this->request->allowMethod(['post', 'put', 'patch']);
+ $blogCategory = $errors = null;
+ try {
+ $blogCategory = $service->move(
+ $this->request->getData('origin'),
+ $this->request->getData('target')
+ );
+ $message = __d('baser_core', 'ブログカテゴリー「{0}」の配置を移動しました。', $blogCategory->title);
+ $this->BcMessage->setSuccess($message, true, false);
+ } catch (PersistenceFailedException $e) {
+ $errors = $e->getEntity()->getErrors();
+ $message = __d('baser_core', "入力エラーです。内容を修正してください。");
+ $this->setResponse($this->response->withStatus(400));
+ } catch (RecordNotFoundException $e) {
+ $this->setResponse($this->response->withStatus(404));
+ $message = __d('baser_core', 'データが見つかりません。');
+ } catch (\Throwable $e) {
+ $message = __d('baser_core', 'データベース処理中にエラーが発生しました。' . $e->getMessage());
+ $this->setResponse($this->response->withStatus(500));
+ }
+
+ $this->set([
+ 'message' => $message,
+ 'blogCategory' => $blogCategory,
+ 'errors' => $errors
+ ]);
+ $this->viewBuilder()->setOption('serialize', ['message', 'blogCategory', 'errors']);
+ }
}
diff --git a/plugins/bc-blog/src/Event/BcBlogViewEventListener.php b/plugins/bc-blog/src/Event/BcBlogViewEventListener.php
index 23c8dc1d73..59fda6ce17 100755
--- a/plugins/bc-blog/src/Event/BcBlogViewEventListener.php
+++ b/plugins/bc-blog/src/Event/BcBlogViewEventListener.php
@@ -31,9 +31,27 @@ class BcBlogViewEventListener extends \BaserCore\Event\BcViewEventListener
*/
public $events = [
'beforeRender',
- 'leftOfToolbar'
+ 'leftOfToolbar',
+ 'afterUtilitiesIndex'
];
+ /**
+ * afterUtilitiesIndex
+ *
+ * ユーティリティ画面にブログカテゴリのツリー構造チェック・リセットのセクションを追加する
+ *
+ * @param Event $event
+ * @checked
+ * @noTodo
+ * @unitTest
+ */
+ public function afterUtilitiesIndex(Event $event)
+ {
+ if (!BcUtil::isAdminSystem()) return;
+ $view = $event->getSubject();
+ echo $view->element('BcBlog.Utilities/blog_categories_tree');
+ }
+
/**
* leftOfToolbar
* @param Event $event
diff --git a/plugins/bc-blog/src/Model/Table/BlogCategoriesTable.php b/plugins/bc-blog/src/Model/Table/BlogCategoriesTable.php
index 481b537d27..d75ea9d86b 100755
--- a/plugins/bc-blog/src/Model/Table/BlogCategoriesTable.php
+++ b/plugins/bc-blog/src/Model/Table/BlogCategoriesTable.php
@@ -486,4 +486,111 @@ public function getParent($parent_id)
{
return $this->find()->where(['id' => $parent_id])->first();
}
+
+ /**
+ * 同一階層(同じ親)における並び順を取得する
+ *
+ * 指定した親配下(親なしはルート)のカテゴリを lft 昇順に並べ、対象 id の 1 始まりの順位を返す。
+ * id が空の場合は件数(=末尾の位置)を返す。該当データが無い場合は false、
+ * id に一致するカテゴリが無い場合は null。
+ *
+ * TreeBehavior はスコープを持たず lft/rght は全ブログ横断の単一フォレストで管理されるため、
+ * ルート階層(親なし)では全ブログのルートカテゴリを対象とした順位を返す。
+ * moveUp()/moveDown() が実際に移動する兄弟の並びと一致させるための仕様。
+ *
+ * @param int|null $id 対象カテゴリID(null で末尾の位置=件数)
+ * @param int|null $parentId 親カテゴリID(null でルート)
+ * @return int|false|null
+ * @checked
+ * @noTodo
+ * @unitTest
+ */
+ public function getOrderSameParent($id, $parentId)
+ {
+ if ($parentId) {
+ $conditions = ['BlogCategories.parent_id' => $parentId];
+ } else {
+ $conditions = ['BlogCategories.parent_id IS' => null];
+ }
+ $categories = $this->find()
+ ->select(['BlogCategories.id', 'BlogCategories.parent_id', 'BlogCategories.title'])
+ ->where($conditions)
+ ->orderBy('BlogCategories.lft');
+ if ($categories->all()->isEmpty()) {
+ return false;
+ }
+ if (!$id) {
+ return $categories->all()->count();
+ }
+ $order = null;
+ foreach ($categories as $key => $category) {
+ if ($id == $category->id) {
+ $order = $key + 1;
+ break;
+ }
+ }
+ return $order;
+ }
+
+ /**
+ * オフセットを元にカテゴリを移動する
+ *
+ * TreeBehavior により、同一階層(兄弟)内で指定位置数だけ上下に移動する。
+ * オフセットが正の場合は下へ、負の場合は上へ移動し、0 の場合は何もしない。
+ * 子カテゴリを持つ場合は、その子孫も含めて移動する。
+ *
+ * @param int $id 移動対象のカテゴリID
+ * @param int $offset 移動する位置数(正: 下、負: 上)
+ * @return EntityInterface|bool 成功時は対象エンティティ、失敗時は false
+ * @checked
+ * @noTodo
+ * @unitTest
+ */
+ public function moveOffset(int $id, int $offset)
+ {
+ $category = $this->get($id);
+ if ($offset > 0) {
+ $result = $this->moveDown($category, abs($offset));
+ } elseif ($offset < 0) {
+ $result = $this->moveUp($category, abs($offset));
+ } else {
+ $result = true;
+ }
+ return $result ? $category : false;
+ }
+
+ /**
+ * ブログカテゴリのツリー構造をリセットする
+ *
+ * コンテンツ管理のリセットと同様に、全てのカテゴリをルート直下にフラット化し、
+ * lft / rght を現在の並び順(lft 昇順)で振り直す。
+ *
+ * @return bool
+ * @checked
+ * @noTodo
+ * @unitTest
+ */
+ public function resetTree(): bool
+ {
+ $this->removeBehavior('Tree');
+ $this->getConnection()->begin();
+ $result = true;
+ $categories = $this->find()->orderBy(['BlogCategories.lft' => 'ASC'])->all();
+ $count = 0;
+ foreach($categories as $category) {
+ $count++;
+ $category->parent_id = null;
+ $category->lft = $count;
+ $count++;
+ $category->rght = $count;
+ if (!$this->save($category)) $result = false;
+ }
+ $this->addBehavior('Tree');
+ if (!$result) {
+ $this->getConnection()->rollback();
+ return false;
+ }
+ $this->getConnection()->commit();
+ return true;
+ }
}
diff --git a/plugins/bc-blog/src/Service/Admin/BlogCategoriesAdminService.php b/plugins/bc-blog/src/Service/Admin/BlogCategoriesAdminService.php
index 43417a8486..6cc27680f1 100755
--- a/plugins/bc-blog/src/Service/Admin/BlogCategoriesAdminService.php
+++ b/plugins/bc-blog/src/Service/Admin/BlogCategoriesAdminService.php
@@ -36,18 +36,31 @@ class BlogCategoriesAdminService extends BlogCategoriesService implements BlogCa
* ブログカテゴリ一覧用の view 変数取得
*
* @param int $blogContentId
+ * @param int|null $listType 表示形式(1: ツリー形式 / 2: 表形式。null は表形式)
* @return array
* @noTodo
* @checked
* @unitTest
*/
- public function getViewVarsForIndex(int $blogContentId)
+ public function getViewVarsForIndex(int $blogContentId, ?int $listType = null)
{
$blogContentsService = $this->getService(BlogContentsServiceInterface::class);
- return [
+ $templates = [1 => 'index_tree', 2 => 'index_list'];
+ $listType = ($listType && isset($templates[$listType])) ? $listType : 2;
+ $vars = [
'blogContent' => $blogContentsService->get($blogContentId),
- 'blogCategories' => $this->getTreeIndex($blogContentId, [])
+ 'template' => $templates[$listType],
];
+ if ($listType === 1) {
+ // ツリー形式: 親子ネスト(children)付きで取得
+ $vars['blogCategories'] = $this->getIndex($blogContentId, [], 'threaded')
+ ->orderBy(['BlogCategories.lft'])
+ ->all();
+ } else {
+ // 表形式: 従来どおり階層をインデント表示したフラット配列
+ $vars['blogCategories'] = $this->getTreeIndex($blogContentId, []);
+ }
+ return $vars;
}
/**
diff --git a/plugins/bc-blog/src/Service/Admin/BlogCategoriesAdminServiceInterface.php b/plugins/bc-blog/src/Service/Admin/BlogCategoriesAdminServiceInterface.php
index 321a1434af..8e0f1a3d2d 100755
--- a/plugins/bc-blog/src/Service/Admin/BlogCategoriesAdminServiceInterface.php
+++ b/plugins/bc-blog/src/Service/Admin/BlogCategoriesAdminServiceInterface.php
@@ -24,14 +24,15 @@ interface BlogCategoriesAdminServiceInterface
/**
* ブログカテゴリ一覧用の view 変数取得
- *
+ *
* @param int $blogContentId
+ * @param int|null $listType 表示形式(1: ツリー形式 / 2: 表形式。null は表形式)
* @return array
* @noTodo
* @checked
* @unitTest
*/
- public function getViewVarsForIndex(int $blogContentId);
+ public function getViewVarsForIndex(int $blogContentId, ?int $listType = null);
/**
* ブログカテゴリー登録用の view 変数取得
diff --git a/plugins/bc-blog/src/Service/BlogCategoriesService.php b/plugins/bc-blog/src/Service/BlogCategoriesService.php
index eaebcd46e5..3d539d3345 100755
--- a/plugins/bc-blog/src/Service/BlogCategoriesService.php
+++ b/plugins/bc-blog/src/Service/BlogCategoriesService.php
@@ -12,6 +12,8 @@
namespace BcBlog\Service;
use BaserCore\Error\BcException;
+use BaserCore\Service\UtilitiesServiceInterface;
+use BaserCore\Utility\BcContainerTrait;
use BcBlog\Model\Entity\BlogCategory;
use BcBlog\Model\Table\BlogCategoriesTable;
use Cake\Datasource\EntityInterface;
@@ -30,6 +32,11 @@
class BlogCategoriesService implements BlogCategoriesServiceInterface
{
+ /**
+ * Trait
+ */
+ use BcContainerTrait;
+
/**
* @var BlogCategoriesTable|Table
*/
@@ -232,16 +239,18 @@ public function getControlSource(string $field, array $options): mixed
* 新規エンティティ取得
*
* @param int $blogContentId
+ * @param int|null $parentId 親カテゴリID(子カテゴリとして追加する場合に指定)
* @return EntityInterface
* @checked
* @noTodo
* @unitTest
*/
- public function getNew(int $blogContentId): EntityInterface
+ public function getNew(int $blogContentId, ?int $parentId = null): EntityInterface
{
return $this->BlogCategories->newEntity([
'blog_content_id' => $blogContentId,
- 'status' => true
+ 'status' => true,
+ 'parent_id' => $parentId
], [
'validate' => false,
]);
@@ -369,4 +378,91 @@ public function getList($blogContentId, array $queryParams = []): array
if ($blogContentId) $conditions = ['BlogCategories.blog_content_id' => $blogContentId];
return $query->where($conditions)->toArray();
}
+
+ /**
+ * カテゴリの配置を移動する(並び替え・再親付け)
+ *
+ * ツリー上のドラッグ&ドロップに対応する。基本的に target.id の上に移動する前提で、
+ * target.id が空の場合は同じ親の中の一番下に配置する。親が変わる場合は先に parent_id を
+ * 付け替え(TreeBehavior が末尾へ再配置)、その後オフセットで目標位置まで移動する。
+ * 移動元の親IDは DB 上のエンティティから取得するため、origin.parentId は利用しない。
+ *
+ * @param array $origin 移動元 ['id' => int](parentId は後方互換のため受け付けるが利用しない)
+ * @param array $target 移動先 ['id' => int|null, 'parentId' => int|null]
+ * @return EntityInterface|bool
+ * @checked
+ * @noTodo
+ * @unitTest
+ */
+ public function move(array $origin, array $target)
+ {
+ if (!$this->BlogCategories->exists(['BlogCategories.id' => $origin['id']])) {
+ throw new BcException(__d('baser_core', 'データが存在しません。'));
+ }
+
+ $category = $this->get($origin['id']);
+ $blogContentId = $category->blog_content_id;
+ // 親変更の判定はリクエスト値に依存せず、DB 上の現在の親IDを利用する
+ $originParentId = $category->parent_id? (int)$category->parent_id : null;
+ $targetParentId = $target['parentId']? (int)$target['parentId'] : null;
+ $targetId = $target['id']? (int)$target['id'] : null;
+
+ $targetSort = $this->BlogCategories->getOrderSameParent($targetId, $targetParentId);
+ if ($originParentId !== $targetParentId) {
+ // 親を変更(再親付け)
+ $category = $this->update($category, [
+ 'id' => $origin['id'],
+ 'name' => $category->name,
+ 'title' => $category->title,
+ 'parent_id' => $targetParentId,
+ 'blog_content_id' => $blogContentId,
+ ]);
+ // 移動先に子が無い、または target 未指定(末尾指定)の場合は末尾配置で終了
+ if (!$targetSort || !$targetId) {
+ return $category;
+ }
+ $currentSort = $this->BlogCategories->getOrderSameParent(null, $targetParentId);
+ } else {
+ $currentSort = $this->BlogCategories->getOrderSameParent($origin['id'], $targetParentId);
+ }
+
+ // 親変更後のオフセットを算出
+ $offset = $targetSort - $currentSort;
+ if ($originParentId === $targetParentId && $targetId && $offset > 0) {
+ $offset--;
+ }
+ return $this->BlogCategories->moveOffset($origin['id'], $offset);
+ }
+
+ /**
+ * ブログカテゴリのツリー構造をチェックする
+ *
+ * 問題がある場合にはログを出力する
+ *
+ * @return bool
+ * @checked
+ * @noTodo
+ * @unitTest
+ */
+ public function verityTree(): bool
+ {
+ /** @var UtilitiesServiceInterface $utilitiesService */
+ $utilitiesService = $this->getService(UtilitiesServiceInterface::class);
+ return $utilitiesService->verityTree($this->BlogCategories);
+ }
+
+ /**
+ * ブログカテゴリのツリー構造をリセットする
+ *
+ * 全てのカテゴリをルート直下にフラット化し、lft / rght を振り直す。
+ *
+ * @return bool
+ * @checked
+ * @noTodo
+ * @unitTest
+ */
+ public function resetTree(): bool
+ {
+ return $this->BlogCategories->resetTree();
+ }
}
diff --git a/plugins/bc-blog/src/Service/BlogCategoriesServiceInterface.php b/plugins/bc-blog/src/Service/BlogCategoriesServiceInterface.php
index 89de1fd3ff..b65c79c66f 100755
--- a/plugins/bc-blog/src/Service/BlogCategoriesServiceInterface.php
+++ b/plugins/bc-blog/src/Service/BlogCategoriesServiceInterface.php
@@ -76,12 +76,13 @@ public function getControlSource(string $field, array $options): mixed;
* 新規エンティティ取得
*
* @param int $blogContentId
+ * @param int|null $parentId 親カテゴリID(子カテゴリとして追加する場合に指定)
* @return EntityInterface
* @checked
* @noTodo
* @unitTest
*/
- public function getNew(int $blogContentId): EntityInterface;
+ public function getNew(int $blogContentId, ?int $parentId = null): EntityInterface;
/**
* 新規作成
@@ -151,4 +152,42 @@ public function getNamesById($ids): array;
* @unitTest
*/
public function getList($blogContentId, array $queryParams = []): array;
+
+ /**
+ * カテゴリの配置を移動する(並び替え・再親付け)
+ *
+ * 移動元の親IDは DB 上のエンティティから取得する
+ *
+ * @param array $origin 移動元 ['id' => int](parentId は後方互換のため受け付けるが利用しない)
+ * @param array $target 移動先 ['id' => int|null, 'parentId' => int|null]
+ * @return EntityInterface|bool
+ * @checked
+ * @noTodo
+ * @unitTest
+ */
+ public function move(array $origin, array $target);
+
+ /**
+ * ブログカテゴリのツリー構造をチェックする
+ *
+ * 問題がある場合にはログを出力する
+ *
+ * @return bool
+ * @checked
+ * @noTodo
+ * @unitTest
+ */
+ public function verityTree(): bool;
+
+ /**
+ * ブログカテゴリのツリー構造をリセットする
+ *
+ * 全てのカテゴリをルート直下にフラット化し、lft / rght を振り直す。
+ *
+ * @return bool
+ * @checked
+ * @noTodo
+ * @unitTest
+ */
+ public function resetTree(): bool;
}
diff --git a/plugins/bc-blog/tests/TestCase/Controller/Admin/BlogCategoriesControllerTest.php b/plugins/bc-blog/tests/TestCase/Controller/Admin/BlogCategoriesControllerTest.php
index f8e6e02b7a..50b5384741 100755
--- a/plugins/bc-blog/tests/TestCase/Controller/Admin/BlogCategoriesControllerTest.php
+++ b/plugins/bc-blog/tests/TestCase/Controller/Admin/BlogCategoriesControllerTest.php
@@ -15,9 +15,11 @@
use BaserCore\Test\Scenario\InitAppScenario;
use BaserCore\TestSuite\BcTestCase;
use BcBlog\Controller\Admin\BlogCategoriesController;
+use BcBlog\Event\BcBlogViewEventListener;
use BcBlog\Test\Factory\BlogCategoryFactory;
use BcBlog\Test\Factory\BlogContentFactory;
use Cake\Event\Event;
+use Cake\Event\EventManager;
use Cake\ORM\TableRegistry;
use Cake\TestSuite\IntegrationTestTrait;
use CakephpFixtureFactories\Scenario\ScenarioAwareTrait;
@@ -73,10 +75,23 @@ public function testAdmin_index()
{
BlogContentFactory::make(['id' => 1, 'eye_catch_size' => 'YTo0OntzOjExOiJ0aHVtYl93aWR0aCI7czozOiIzMDAiO3M6MTI6InRodW1iX2hlaWdodCI7czozOiIzMDAiO3M6MTg6Im1vYmlsZV90aHVtYl93aWR0aCI7czozOiIxMDAiO3M6MTk6Im1vYmlsZV90aHVtYl9oZWlnaHQiO3M6MzoiMTAwIjt9'])->persist();
ContentFactory::make(['id' => 1, 'entity_id' => 1, 'type' => 'BlogContent', 'plugin' => 'BcBlog'])->persist();
+ BlogCategoryFactory::make(['id' => 1, 'blog_content_id' => 1, 'name' => 'release', 'title' => 'プレスリリース', 'status' => 1, 'lft' => 1, 'rght' => 2])->persist();
$this->get('/baser/admin/bc-blog/blog_categories/index');
$this->assertResponseError();
+ // 表形式(デフォルト)
$this->get('/baser/admin/bc-blog/blog_categories/index/1');
$this->assertResponseOk();
+ // ツリー形式
+ $this->get('/baser/admin/bc-blog/blog_categories/index/1?list_type=1');
+ $this->assertResponseOk();
+ $this->assertResponseContains('id="BlogCategoryTreeList"');
+ // 表示形式はセッションに保存される
+ $this->assertSession('1', 'BcApp.viewConditions.BlogCategoriesIndex.query.list_type');
+ // セッションに保持された表示形式は、クエリなしのアクセスでも復元される
+ $this->session(['BcApp' => ['viewConditions' => ['BlogCategoriesIndex' => ['query' => ['list_type' => 1]]]]]);
+ $this->get('/baser/admin/bc-blog/blog_categories/index/1');
+ $this->assertResponseOk();
+ $this->assertResponseContains('id="BlogCategoryTreeList"');
}
/**
@@ -180,6 +195,73 @@ public function testAdmin_add()
$this->assertResponseContains('入力エラーです。内容を修正してください。');
}
+ /**
+ * [ADMIN] ブログカテゴリのツリー構造チェック
+ */
+ public function testAdmin_verity_tree()
+ {
+ $this->enableSecurityToken();
+ $this->enableCsrfToken();
+ // 正常なツリー
+ BlogCategoryFactory::make(['id' => 1, 'blog_content_id' => 1, 'name' => 'release', 'title' => 'プレスリリース', 'parent_id' => null, 'lft' => 1, 'rght' => 2])->persist();
+ $this->post('/baser/admin/bc-blog/blog_categories/verity_tree');
+ $this->assertResponseCode(302);
+ $this->assertRedirect(['plugin' => 'BaserCore', 'prefix' => 'Admin', 'controller' => 'Utilities', 'action' => 'index']);
+ $this->assertFlashMessage('ブログカテゴリのツリー構造に問題はありません。');
+
+ // 破損したツリー
+ BlogCategoryFactory::make(['id' => 2, 'blog_content_id' => 1, 'name' => 'broken', 'title' => 'B', 'parent_id' => null, 'lft' => 2, 'rght' => 1])->persist();
+ $this->post('/baser/admin/bc-blog/blog_categories/verity_tree');
+ $this->assertResponseCode(302);
+ $this->assertFlashMessage('ブログカテゴリのツリー構造に問題があります。ログを確認してください。');
+ }
+
+ /**
+ * [ADMIN] ブログカテゴリのツリー構造リセット
+ */
+ public function testAdmin_reset_tree()
+ {
+ $this->enableSecurityToken();
+ $this->enableCsrfToken();
+ BlogCategoryFactory::make(['id' => 1, 'blog_content_id' => 1, 'name' => 'parent1', 'title' => 'P1', 'parent_id' => null, 'lft' => 1, 'rght' => 4])->persist();
+ BlogCategoryFactory::make(['id' => 2, 'blog_content_id' => 1, 'name' => 'child1', 'title' => 'C1', 'parent_id' => 1, 'lft' => 2, 'rght' => 3])->persist();
+ $this->post('/baser/admin/bc-blog/blog_categories/reset_tree');
+ $this->assertResponseCode(302);
+ $this->assertRedirect(['plugin' => 'BaserCore', 'prefix' => 'Admin', 'controller' => 'Utilities', 'action' => 'index']);
+ $this->assertFlashMessage('ブログカテゴリのツリー構造をリセットしました。');
+ // 全てルート直下にフラット化されている
+ $blogCategories = $this->getTableLocator()->get('BcBlog.BlogCategories');
+ $this->assertEquals(2, $blogCategories->find()->where(['parent_id IS' => null])->count());
+ }
+
+ /**
+ * [ADMIN] ユーティリティ画面にブログカテゴリのツリー構造チェック・リセットが表示される
+ */
+ public function testUtilitiesIndexContainsBlogCategoriesTreeSection()
+ {
+ // BcTestCase が EventManager をリセットするため、プラグイン読込時に
+ // 自動登録されるリスナーをテストでは明示的に登録する
+ EventManager::instance()->on(new BcBlogViewEventListener());
+ $this->get('/baser/admin/baser-core/utilities/index');
+ $this->assertResponseOk();
+ $this->assertResponseContains('blog_categories/verity_tree');
+ $this->assertResponseContains('blog_categories/reset_tree');
+ }
+
+ /**
+ * [ADMIN] 登録処理(子カテゴリを追加:parent_id クエリの反映)
+ */
+ public function testAdmin_add_withParentId()
+ {
+ BlogContentFactory::make(['id' => 1, 'eye_catch_size' => 'YTo0OntzOjExOiJ0aHVtYl93aWR0aCI7czozOiIzMDAiO3M6MTI6InRodW1iX2hlaWdodCI7czozOiIzMDAiO3M6MTg6Im1vYmlsZV90aHVtYl93aWR0aCI7czozOiIxMDAiO3M6MTk6Im1vYmlsZV90aHVtYl9oZWlnaHQiO3M6MzoiMTAwIjt9'])->persist();
+ ContentFactory::make(['id' => 1, 'entity_id' => 1, 'type' => 'BlogContent', 'plugin' => 'BcBlog'])->persist();
+ BlogCategoryFactory::make(['id' => 1, 'blog_content_id' => 1, 'name' => 'release', 'title' => 'プレスリリース', 'status' => 1, 'lft' => 1, 'rght' => 2])->persist();
+ // クエリで指定した親カテゴリが初期選択される
+ $this->get('/baser/admin/bc-blog/blog_categories/add/1?parent_id=1');
+ $this->assertResponseOk();
+ $this->assertResponseRegExp('/