Browse Source

Menu Management - process

master
Egorka 6 years ago
parent
commit
4105cd2a3c
  1. 32
      backend/components/menu/assets/MenuAsset.php
  2. 7
      backend/components/menu/css/jquery-ui.min.css
  3. 196
      backend/components/menu/css/menu.css
  4. 9
      backend/components/menu/js/jquery-ui.min.js
  5. 484
      backend/components/menu/js/jquery.nestable.js
  6. 60
      backend/components/menu/js/menu.js
  7. 48
      backend/components/menu/widgets/MenuEditorWidget.php
  8. 41
      backend/components/menu/widgets/views/_item.php
  9. 155
      backend/components/menu/widgets/views/menu.php
  10. 4
      backend/config/main.php
  11. 195
      backend/controllers/MenuController.php
  12. 4
      backend/controllers/PageController.php
  13. 2
      backend/messages/ru/buttons.php
  14. 1
      backend/messages/ru/main.php
  15. 12
      backend/messages/ru/menu.php
  16. 7
      backend/views/layouts/left.php
  17. 27
      backend/views/menu/_form.php
  18. 16
      backend/views/menu/create.php
  19. 114
      backend/views/menu/menu.php
  20. 51
      backend/views/menu/select_menu.php
  21. 18
      backend/views/menu/update.php
  22. 13
      common/modules/blog/BlogModule.php
  23. 19
      common/modules/blog/controllers/manage/PostController.php
  24. 3
      common/modules/blog/messages/ru/blog.php
  25. 30
      common/modules/blog/widgets/MenuItemCreatorWidget.php
  26. 117
      common/modules/blog/widgets/views/menu-item/creator.php
  27. 4
      composer.json
  28. 33
      console/migrations/m180709_202649_create_menu_table.php
  29. 55
      console/migrations/m180709_202659_create_menu_items_table.php
  30. 7
      core/entities/ModuleRecord.php
  31. 4
      core/entities/Page.php
  32. 44
      core/entities/menu/Menu.php
  33. 103
      core/entities/menu/MenuItem.php
  34. 15
      core/entities/queries/ModuleRecordQuery.php
  35. 37
      core/forms/menu/MenuForm.php
  36. 68
      core/forms/menu/MenuItemForm.php
  37. 25
      core/forms/menu/MenuSelectForm.php
  38. 31
      core/repositories/menu/MenuItemRepository.php
  39. 31
      core/repositories/menu/MenuRepository.php
  40. 67
      core/services/menu/MenuItemManageService.php
  41. 41
      core/services/menu/MenuManageService.php

32
backend/components/menu/assets/MenuAsset.php

@ -0,0 +1,32 @@
<?php
/**
* Created by Error202
* Date: 11.07.2018
*/
namespace backend\components\menu\assets;
use yii\web\AssetBundle;
class MenuAsset extends AssetBundle
{
public $sourcePath = '@backend/components/menu';
public $css = [
'css/menu.css',
];
public $js = [
'js/jquery.nestable.js',
'js/menu.js'
];
public $depends = [
'yii\web\YiiAsset',
'yii\bootstrap\BootstrapPluginAsset',
];
public $publishOptions = [
'forceCopy' => YII_ENV_DEV ? true : false,
];
}

7
backend/components/menu/css/jquery-ui.min.css vendored

File diff suppressed because one or more lines are too long

196
backend/components/menu/css/menu.css

@ -0,0 +1,196 @@
/*.cf:after { visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; }
* html .cf { zoom: 1; }
*:first-child+html .cf { zoom: 1; }
html { margin: 0; padding: 0; }
body { font-size: 100%; margin: 0; padding: 1.75em; font-family: 'Helvetica Neue', Arial, sans-serif; }
h1 { font-size: 1.75em; margin: 0 0 0.6em 0; }
a { color: #2996cc; }
a:hover { text-decoration: none; }
p { line-height: 1.5em; }
.small { color: #666; font-size: 0.875em; }
.large { font-size: 1.25em; }*/
/**
* Nestable
*/
.dd { position: relative; display: block; margin: 0; padding: 0; max-width: 600px; list-style: none; font-size: 13px; line-height: 20px; }
.dd-list { display: block; position: relative; margin: 0; padding: 0; list-style: none; }
.dd-list .dd-list {
padding-left: 30px;
}
.dd-collapsed .dd-list {
display: none;
}
.dd-item,
.dd-empty,
.dd-placeholder {
display: block;
position: relative;
margin: 0; padding: 1px;
min-height: 20px;
font-size: 13px;
line-height: 10px;
}
.dd-handle {
display: block;
height: 100%;
margin: 5px 0;
padding: 5px 10px;
color: #333;
text-decoration: none;
font-weight: bold;
/*border: 1px solid #ccc;*/
background: #fafafa;
background: -webkit-linear-gradient(top, #fafafa 0%, #eee 100%);
background: -moz-linear-gradient(top, #fafafa 0%, #eee 100%);
background: linear-gradient(top, #fafafa 0%, #eee 100%);
-webkit-border-radius: 3px;
border-radius: 3px;
box-sizing: border-box;
-moz-box-sizing: border-box;
}
.dd-handle:hover { color: #2ea8e5; background: #fff; }
.dd-item > button {
display: block;
position: relative;
cursor: pointer;
float: left;
width: 25px; height: 30px;
margin: 9px 0; padding: 0;
text-indent: 100%;
white-space: nowrap;
overflow: hidden;
border: 0;
background: transparent;
font-size: 20px;
line-height: 1;
text-align: center;
font-weight: bold;
z-index: 5;
color: #999;
}
.dd-item > button:before {
content: '+';
display: block;
position: absolute;
width: 100%;
text-align: center;
text-indent: 0;
}
.dd-item > button[data-action="collapse"]:before { content: '-'; }
.dd-placeholder,
.dd-empty {
margin: 5px 0;
padding: 0;
min-height: 30px;
background: #f2fbff;
border: 1px dashed #b6bcbf;
box-sizing: border-box;
-moz-box-sizing: border-box;
}
.dd-empty {
border: 1px dashed #bbb;
min-height: 100px;
background-color: #e5e5e5;
background-image: -webkit-linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff),
-webkit-linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff);
background-image: -moz-linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff),
-moz-linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff);
background-image: linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff),
linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff);
background-size: 60px 60px;
background-position: 0 0, 30px 30px;
}
.dd-dragel { position: absolute; pointer-events: none; z-index: 9999; }
.dd-dragel > .dd-item .dd-handle { margin-top: 0; }
.dd-dragel .dd-handle {
-webkit-box-shadow: 2px 4px 6px 0 rgba(0,0,0,.1);
box-shadow: 2px 4px 6px 0 rgba(0,0,0,.1);
}
@media only screen and (min-width: 700px) {
.dd { float: left; width: 48%; }
.dd + .dd { margin-left: 2%; }
}
.dd-hover > .dd-handle { background: #2ea8e5 !important; }
/**
* Nestable Draggable Handles
*/
.dd3-content {
display: block;
margin: 5px 0;
padding: 0;
color: #333;
text-decoration: none;
font-weight: bold;
background: #fafafa;
background: -webkit-linear-gradient(top, #fafafa 0%, #eee 100%);
background: -moz-linear-gradient(top, #fafafa 0%, #eee 100%);
background: linear-gradient(top, #fafafa 0%, #eee 100%);
-webkit-border-radius: 3px;
border-radius: 3px;
box-sizing: border-box; -moz-box-sizing: border-box;
}
.dd3-content:hover {
color: #2ea8e5;
background: #fff;
}
.dd-dragel > .dd3-item > .dd3-content { margin: 0; }
.dd3-item > button { margin-left: 30px; }
.dd3-handle { position: absolute; margin: 0; left: 0; top: 0; cursor: pointer; width: 30px; text-indent: 100%; white-space: nowrap; overflow: hidden;
border: 1px solid #aaa;
background: #ddd;
background: -webkit-linear-gradient(top, #ddd 0%, #bbb 100%);
background: -moz-linear-gradient(top, #ddd 0%, #bbb 100%);
background: linear-gradient(top, #ddd 0%, #bbb 100%);
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.dd3-handle:before {
content: "\f047";
font-family: FontAwesome;
display: block;
position: absolute;
left: 0; top: 10px;
width: 100%;
text-align: center;
text-indent: 0;
color: #fff;
font-size: 15px;
font-weight: normal;
}
.dd3-handle:hover { background: #ddd; }
.panel-group {
margin-bottom: 0 !important;
}
.panel-heading {
position: relative;
}
.panel-heading h4 {
padding-left: 25px;
}

9
backend/components/menu/js/jquery-ui.min.js vendored

File diff suppressed because one or more lines are too long

484
backend/components/menu/js/jquery.nestable.js

@ -0,0 +1,484 @@
/*!
* Nestable jQuery Plugin - Copyright (c) 2012 David Bushell - http://dbushell.com/
* Dual-licensed under the BSD or MIT licenses
*/
;(function($, window, document, undefined)
{
var hasTouch = 'ontouchstart' in document;
/**
* Detect CSS pointer-events property
* events are normally disabled on the dragging element to avoid conflicts
* https://github.com/ausi/Feature-detection-technique-for-pointer-events/blob/master/modernizr-pointerevents.js
*/
var hasPointerEvents = (function()
{
var el = document.createElement('div'),
docEl = document.documentElement;
if (!('pointerEvents' in el.style)) {
return false;
}
el.style.pointerEvents = 'auto';
el.style.pointerEvents = 'x';
docEl.appendChild(el);
var supports = window.getComputedStyle && window.getComputedStyle(el, '').pointerEvents === 'auto';
docEl.removeChild(el);
return !!supports;
})();
var defaults = {
listNodeName : 'ol',
itemNodeName : 'li',
rootClass : 'dd',
listClass : 'dd-list',
itemClass : 'dd-item',
dragClass : 'dd-dragel',
handleClass : 'dd-handle',
collapsedClass : 'dd-collapsed',
placeClass : 'dd-placeholder',
noDragClass : 'dd-nodrag',
emptyClass : 'dd-empty',
expandBtnHTML : '<button data-action="expand" type="button">Expand</button>',
collapseBtnHTML : '<button data-action="collapse" type="button">Collapse</button>',
group : 0,
maxDepth : 5,
threshold : 20
};
function Plugin(element, options)
{
this.w = $(document);
this.el = $(element);
this.options = $.extend({}, defaults, options);
this.init();
}
Plugin.prototype = {
init: function()
{
var list = this;
list.reset();
list.el.data('nestable-group', this.options.group);
list.placeEl = $('<div class="' + list.options.placeClass + '"/>');
$.each(this.el.find(list.options.itemNodeName), function(k, el) {
list.setParent($(el));
});
list.el.on('click', 'button', function(e) {
if (list.dragEl) {
return;
}
var target = $(e.currentTarget),
action = target.data('action'),
item = target.parent(list.options.itemNodeName);
if (action === 'collapse') {
list.collapseItem(item);
}
if (action === 'expand') {
list.expandItem(item);
}
});
var onStartEvent = function(e)
{
var handle = $(e.target);
if (!handle.hasClass(list.options.handleClass)) {
if (handle.closest('.' + list.options.noDragClass).length) {
return;
}
handle = handle.closest('.' + list.options.handleClass);
}
if (!handle.length || list.dragEl) {
return;
}
list.isTouch = /^touch/.test(e.type);
if (list.isTouch && e.touches.length !== 1) {
return;
}
e.preventDefault();
list.dragStart(e.touches ? e.touches[0] : e);
};
var onMoveEvent = function(e)
{
if (list.dragEl) {
e.preventDefault();
list.dragMove(e.touches ? e.touches[0] : e);
}
};
var onEndEvent = function(e)
{
if (list.dragEl) {
e.preventDefault();
list.dragStop(e.touches ? e.touches[0] : e);
}
};
if (hasTouch) {
list.el[0].addEventListener('touchstart', onStartEvent, false);
window.addEventListener('touchmove', onMoveEvent, false);
window.addEventListener('touchend', onEndEvent, false);
window.addEventListener('touchcancel', onEndEvent, false);
}
list.el.on('mousedown', onStartEvent);
list.w.on('mousemove', onMoveEvent);
list.w.on('mouseup', onEndEvent);
},
serialize: function()
{
var data,
depth = 0,
list = this;
step = function(level, depth)
{
var array = [ ],
items = level.children(list.options.itemNodeName);
items.each(function()
{
var li = $(this),
item = $.extend({}, li.data()),
sub = li.children(list.options.listNodeName);
if (sub.length) {
item.children = step(sub, depth + 1);
}
array.push(item);
});
return array;
};
data = step(list.el.find(list.options.listNodeName).first(), depth);
return data;
},
serialise: function()
{
return this.serialize();
},
reset: function()
{
this.mouse = {
offsetX : 0,
offsetY : 0,
startX : 0,
startY : 0,
lastX : 0,
lastY : 0,
nowX : 0,
nowY : 0,
distX : 0,
distY : 0,
dirAx : 0,
dirX : 0,
dirY : 0,
lastDirX : 0,
lastDirY : 0,
distAxX : 0,
distAxY : 0
};
this.isTouch = false;
this.moving = false;
this.dragEl = null;
this.dragRootEl = null;
this.dragDepth = 0;
this.hasNewRoot = false;
this.pointEl = null;
},
expandItem: function(li)
{
li.removeClass(this.options.collapsedClass);
li.children('[data-action="expand"]').hide();
li.children('[data-action="collapse"]').show();
li.children(this.options.listNodeName).show();
},
collapseItem: function(li)
{
var lists = li.children(this.options.listNodeName);
if (lists.length) {
li.addClass(this.options.collapsedClass);
li.children('[data-action="collapse"]').hide();
li.children('[data-action="expand"]').show();
li.children(this.options.listNodeName).hide();
}
},
expandAll: function()
{
var list = this;
list.el.find(list.options.itemNodeName).each(function() {
list.expandItem($(this));
});
},
collapseAll: function()
{
var list = this;
list.el.find(list.options.itemNodeName).each(function() {
list.collapseItem($(this));
});
},
setParent: function(li)
{
if (li.children(this.options.listNodeName).length) {
li.prepend($(this.options.expandBtnHTML));
li.prepend($(this.options.collapseBtnHTML));
}
li.children('[data-action="expand"]').hide();
},
unsetParent: function(li)
{
li.removeClass(this.options.collapsedClass);
li.children('[data-action]').remove();
li.children(this.options.listNodeName).remove();
},
dragStart: function(e)
{
var mouse = this.mouse,
target = $(e.target),
dragItem = target.closest(this.options.itemNodeName);
this.placeEl.css('height', dragItem.height());
mouse.offsetX = e.offsetX !== undefined ? e.offsetX : e.pageX - target.offset().left;
mouse.offsetY = e.offsetY !== undefined ? e.offsetY : e.pageY - target.offset().top;
mouse.startX = mouse.lastX = e.pageX;
mouse.startY = mouse.lastY = e.pageY;
this.dragRootEl = this.el;
this.dragEl = $(document.createElement(this.options.listNodeName)).addClass(this.options.listClass + ' ' + this.options.dragClass);
this.dragEl.css('width', dragItem.width());
dragItem.after(this.placeEl);
dragItem[0].parentNode.removeChild(dragItem[0]);
dragItem.appendTo(this.dragEl);
$(document.body).append(this.dragEl);
this.dragEl.css({
'left' : e.pageX - mouse.offsetX,
'top' : e.pageY - mouse.offsetY
});
// total depth of dragging item
var i, depth,
items = this.dragEl.find(this.options.itemNodeName);
for (i = 0; i < items.length; i++) {
depth = $(items[i]).parents(this.options.listNodeName).length;
if (depth > this.dragDepth) {
this.dragDepth = depth;
}
}
},
dragStop: function(e)
{
var el = this.dragEl.children(this.options.itemNodeName).first();
el[0].parentNode.removeChild(el[0]);
this.placeEl.replaceWith(el);
this.dragEl.remove();
this.el.trigger('change');
if (this.hasNewRoot) {
this.dragRootEl.trigger('change');
}
this.reset();
},
dragMove: function(e)
{
var list, parent, prev, next, depth,
opt = this.options,
mouse = this.mouse;
this.dragEl.css({
'left' : e.pageX - mouse.offsetX,
'top' : e.pageY - mouse.offsetY
});
// mouse position last events
mouse.lastX = mouse.nowX;
mouse.lastY = mouse.nowY;
// mouse position this events
mouse.nowX = e.pageX;
mouse.nowY = e.pageY;
// distance mouse moved between events
mouse.distX = mouse.nowX - mouse.lastX;
mouse.distY = mouse.nowY - mouse.lastY;
// direction mouse was moving
mouse.lastDirX = mouse.dirX;
mouse.lastDirY = mouse.dirY;
// direction mouse is now moving (on both axis)
mouse.dirX = mouse.distX === 0 ? 0 : mouse.distX > 0 ? 1 : -1;
mouse.dirY = mouse.distY === 0 ? 0 : mouse.distY > 0 ? 1 : -1;
// axis mouse is now moving on
var newAx = Math.abs(mouse.distX) > Math.abs(mouse.distY) ? 1 : 0;
// do nothing on first move
if (!mouse.moving) {
mouse.dirAx = newAx;
mouse.moving = true;
return;
}
// calc distance moved on this axis (and direction)
if (mouse.dirAx !== newAx) {
mouse.distAxX = 0;
mouse.distAxY = 0;
} else {
mouse.distAxX += Math.abs(mouse.distX);
if (mouse.dirX !== 0 && mouse.dirX !== mouse.lastDirX) {
mouse.distAxX = 0;
}
mouse.distAxY += Math.abs(mouse.distY);
if (mouse.dirY !== 0 && mouse.dirY !== mouse.lastDirY) {
mouse.distAxY = 0;
}
}
mouse.dirAx = newAx;
/**
* move horizontal
*/
if (mouse.dirAx && mouse.distAxX >= opt.threshold) {
// reset move distance on x-axis for new phase
mouse.distAxX = 0;
prev = this.placeEl.prev(opt.itemNodeName);
// increase horizontal level if previous sibling exists and is not collapsed
if (mouse.distX > 0 && prev.length && !prev.hasClass(opt.collapsedClass)) {
// cannot increase level when item above is collapsed
list = prev.find(opt.listNodeName).last();
// check if depth limit has reached
depth = this.placeEl.parents(opt.listNodeName).length;
if (depth + this.dragDepth <= opt.maxDepth) {
// create new sub-level if one doesn't exist
if (!list.length) {
list = $('<' + opt.listNodeName + '/>').addClass(opt.listClass);
list.append(this.placeEl);
prev.append(list);
this.setParent(prev);
} else {
// else append to next level up
list = prev.children(opt.listNodeName).last();
list.append(this.placeEl);
}
}
}
// decrease horizontal level
if (mouse.distX < 0) {
// we can't decrease a level if an item preceeds the current one
next = this.placeEl.next(opt.itemNodeName);
if (!next.length) {
parent = this.placeEl.parent();
this.placeEl.closest(opt.itemNodeName).after(this.placeEl);
if (!parent.children().length) {
this.unsetParent(parent.parent());
}
}
}
}
var isEmpty = false;
// find list item under cursor
if (!hasPointerEvents) {
this.dragEl[0].style.visibility = 'hidden';
}
this.pointEl = $(document.elementFromPoint(e.pageX - document.body.scrollLeft, e.pageY - (window.pageYOffset || document.documentElement.scrollTop)));
if (!hasPointerEvents) {
this.dragEl[0].style.visibility = 'visible';
}
if (this.pointEl.hasClass(opt.handleClass)) {
this.pointEl = this.pointEl.parent(opt.itemNodeName);
}
if (this.pointEl.hasClass(opt.emptyClass)) {
isEmpty = true;
}
else if (!this.pointEl.length || !this.pointEl.hasClass(opt.itemClass)) {
return;
}
// find parent list of item under cursor
var pointElRoot = this.pointEl.closest('.' + opt.rootClass),
isNewRoot = this.dragRootEl.data('nestable-id') !== pointElRoot.data('nestable-id');
/**
* move vertical
*/
if (!mouse.dirAx || isNewRoot || isEmpty) {
// check if groups match if dragging over new root
if (isNewRoot && opt.group !== pointElRoot.data('nestable-group')) {
return;
}
// check depth limit
depth = this.dragDepth - 1 + this.pointEl.parents(opt.listNodeName).length;
if (depth > opt.maxDepth) {
return;
}
var before = e.pageY < (this.pointEl.offset().top + this.pointEl.height() / 2);
parent = this.placeEl.parent();
// if empty create new list to replace empty placeholder
if (isEmpty) {
list = $(document.createElement(opt.listNodeName)).addClass(opt.listClass);
list.append(this.placeEl);
this.pointEl.replaceWith(list);
}
else if (before) {
this.pointEl.before(this.placeEl);
}
else {
this.pointEl.after(this.placeEl);
}
if (!parent.children().length) {
this.unsetParent(parent.parent());
}
if (!this.dragRootEl.find(opt.itemNodeName).length) {
this.dragRootEl.append('<div class="' + opt.emptyClass + '"/>');
}
// parent root list has changed
if (isNewRoot) {
this.dragRootEl = pointElRoot;
this.hasNewRoot = this.el[0] !== this.dragRootEl[0];
}
}
}
};
$.fn.nestable = function(params)
{
var lists = this,
retval = this;
lists.each(function()
{
var plugin = $(this).data("nestable");
if (!plugin) {
$(this).data("nestable", new Plugin(this, params));
$(this).data("nestable-id", new Date().getTime());
} else {
if (typeof params === 'string' && typeof plugin[params] === 'function') {
retval = plugin[params]();
}
}
});
return retval || lists;
};
})(window.jQuery || window.Zepto, window, document);

60
backend/components/menu/js/menu.js

@ -0,0 +1,60 @@
$(document).ready(function()
{
$('#nestable3').nestable();
$('#serialize').click(function(){
serializeTree();
});
//$('#nestable3').on('change', function() {
/* on change event */
//alert('Order changed');
//});
//$('.panel-group').mouseover(function(ev) {
// $(this).find('.dd-item').trigger(ev);
//});
//$('.panel-group, .panel, .panel-heading, .panel-title, .panel-collapse, .panel-body').trigger('mouseover');
//$('.panel-group, .panel, .panel-heading, .panel-title, .panel-collapse, .panel-body, .dd-handle').mouseover();
//$('.panel-group, .panel, .panel-heading, .panel-title, .panel-collapse, .panel-body, .dd-handle').mouseenter();
});
function serializeTree() {
//if (window.JSON) {
//var serialized = JSON.stringify($('#nestable3').nestable('serialize'));
//$('#output').text(serialized+'\n\n');
var arr = getPositions($('#nestable3').nestable('serialize'));
return JSON.stringify(arr);
//$('#output').text(JSON.stringify(arr));
//} else {
//$('#output').text('JSON browser support required for this demo.');
//}
}
function getPositions(array, parent_id) {
parent_id = parent_id || 0;
var new_array = [];
for (var i = 0; i < array.length; i++) {
new_array.push([array[i].id, parent_id]);
if (array[i].hasOwnProperty('children')) {
new_array = new_array.concat(getPositions( array[i].children, array[i].id ));
}
}
return new_array;
}
function sendTree(menu_id, url, redirect) {
var json = serializeTree();
$.ajax({
url: url,
type: 'POST',
data: { json : json },
success: function(){
document.location.href = redirect;
},
error: function(){
alert('Error: ' + res.message);
}
});
}

48
backend/components/menu/widgets/MenuEditorWidget.php

@ -0,0 +1,48 @@
<?php
/**
* Created by Error202
* Date: 11.07.2018
*/
namespace backend\components\menu\widgets;
use core\entities\menu\Menu;
use core\entities\menu\MenuItem;
use yii\base\Widget;
class MenuEditorWidget extends Widget
{
public $menu_id;
public function run()
{
$menu = Menu::findOne($this->menu_id);
$items = $menu->items;
return $this->render('menu', [
'items' => $this->getMenu($items),
'menu' => $menu,
]);
}
/**
* @param $items MenuItem[]
*
* @return array
*/
private function getMenu($items, $paren_id = null)
{
$array = [];
foreach ($items as $item) {
if ($item->parent_id != $paren_id) {
continue;
}
$array[$item->id]['item'] = $item;
if ($children = $item->children) {
$array[$item->id]['children'] = $this->getMenu($children, $item->id);
}
}
return $array;
}
}

41
backend/components/menu/widgets/views/_item.php

@ -0,0 +1,41 @@
<?php
/**
* Created by Error202
* Date: 11.07.2018
*/
/**
* @var $this \yii\web\View
* @var $item array
*/
/* @var $menu_item \core\entities\menu\MenuItem */
$menu_item = $item['item'];
?>
<li class="dd-item dd3-item" data-id="<?= $menu_item->id ?>">
<div class="dd3-content">
<div class="panel-group" id="accordion">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<div class="dd-handle dd3-handle"> </div>
<a data-toggle="collapse" data-parent="#accordion" href="#collapse<?= $menu_item->id ?>"><?= $menu_item->name ?>
<i class="fa fa-angle-down pull-right" aria-hidden="true"></i></a>
</h4>
</div>
<div id="collapse<?= $menu_item->id ?>" class="panel-collapse collapse">
<div class="panel-body">
Content Panel
</div>
</div>
</div>
</div>
</div>
<?php if (isset($item['children'])): ?>
<?= menu_generate($item['children']) ?>
<?php endif; ?>
</li>

155
backend/components/menu/widgets/views/menu.php

@ -0,0 +1,155 @@
<?php
/**
* Created by Error202
* Date: 11.07.2018
*/
/**
* @var $this \yii\web\View
* @var $items array
* @var $menu \core\entities\menu\Menu
*/
use backend\components\menu\assets\MenuAsset;
use yii\helpers\Html;
use yii\helpers\Url;
use yii\web\JsExpression;
MenuAsset::register($this);
function menu_generate($items) {
$html = '<ol class="dd-list">';
foreach ($items as $item) {
$html.=Yii::$app->getView()->render( '_item', [
'item' => $item,
] );
}
return $html . '</ol>';
}
$url = Url::to(['/menu/save-menu-items']);
$redirect = Url::to(['menu/index', 'id' => $menu->id]);
?>
<div class="dd" id="nestable3">
<?= menu_generate($items) ?>
</div>
<!--
<div class="dd" id="nestable2">
<ol class="dd-list">
<li class="dd-item dd3-item" data-id="13">
<div class="dd3-content">
<div class="panel-group" id="accordion">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<div class="dd-handle dd3-handle"> </div>
<a data-toggle="collapse" data-parent="#accordion" href="#collapse1">Item 13
<i class="fa fa-angle-down pull-right" aria-hidden="true"></i></a>
</h4>
</div>
<div id="collapse1" class="panel-collapse collapse">
<div class="panel-body">
Content Panel
</div>
</div>
</div>
</div>
</div>
</li>
<li class="dd-item dd3-item" data-id="14">
<div class="dd3-content">
<div class="panel-group" id="accordion">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<div class="dd-handle dd3-handle"> </div>
<a data-toggle="collapse" data-parent="#accordion" href="#collapse2">Item 14
<i class="fa fa-angle-down pull-right" aria-hidden="true"></i></a>
</h4>
</div>
<div id="collapse2" class="panel-collapse collapse">
<div class="panel-body">
Content Panel
</div>
</div>
</div>
</div>
</div>
</li>
<li class="dd-item dd3-item" data-id="15">
<div class="dd3-content">
<div class="panel-group" id="accordion">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<div class="dd-handle dd3-handle"> </div>
<a data-toggle="collapse" data-parent="#accordion" href="#collapse3">Item 15
<i class="fa fa-angle-down pull-right" aria-hidden="true"></i></a>
</h4>
</div>
<div id="collapse3" class="panel-collapse collapse">
<div class="panel-body">
Content Panel
</div>
</div>
</div>
</div>
</div>
<ol class="dd-list">
<li class="dd-item dd3-item" data-id="16">
<div class="dd3-content">
<div class="panel-group" id="accordion">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<div class="dd-handle dd3-handle"> </div>
<a data-toggle="collapse" data-parent="#accordion" href="#collapse4">Item 16
<i class="fa fa-angle-down pull-right" aria-hidden="true"></i></a>
</h4>
</div>
<div id="collapse4" class="panel-collapse collapse">
<div class="panel-body">
Content Panel
</div>
</div>
</div>
</div>
</div>
</li>
</ol>
</li>
</ol>
</div>
</div>
<div style="clear: both;"></div>
<hr>
<button class="btn btn-primary" id="serialize">Serialize</button>
<hr>
<textarea class="form-control" id="output"></textarea>
<hr>
<textarea class="form-control" id="output2"></textarea>
-->
<div style="clear: both;"></div>
<hr>
<?= Html::button(Yii::t('buttons', 'Save'), [
'class' => 'btn btn-success pull-right',
'onclick' => new JsExpression('sendTree('.$menu->id.', "'.$url.'", "'.$redirect.'")'),
]) ?>

4
backend/config/main.php

@ -97,6 +97,10 @@ return [
'class' => 'yii\i18n\PhpMessageSource',
'basePath' => '@backend/messages',
],
'menu' => [
'class' => 'yii\i18n\PhpMessageSource',
'basePath' => '@backend/messages',
],
'page' => [
'class' => 'yii\i18n\PhpMessageSource',
'basePath' => '@backend/messages',

195
backend/controllers/MenuController.php

@ -0,0 +1,195 @@
<?php
/**
* Created by Error202
* Date: 10.07.2018
*/
namespace backend\controllers;
use common\modules\blog\BlogModule;
use core\entities\menu\Menu;
use core\entities\menu\MenuItem;
use core\entities\ModuleRecord;
use core\forms\menu\MenuForm;
use core\forms\menu\MenuItemForm;
use core\forms\menu\MenuSelectForm;
use core\services\menu\MenuItemManageService;
use core\services\menu\MenuManageService;
use yii\filters\AccessControl;
use yii\filters\VerbFilter;
use yii\helpers\Json;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use Yii;
class MenuController extends Controller
{
public $menu_service;
public $menu_item_service;
public function __construct( string $id, $module, MenuManageService $menu_service, MenuItemManageService $menu_item_service, array $config = [] ) {
parent::__construct( $id, $module, $config );
$this->menu_service = $menu_service;
$this->menu_item_service = $menu_item_service;
}
public function behaviors(): array
{
return [
'access' => [
'class' => AccessControl::class,
'rules' => [
[
'actions' => ['create', 'update', 'delete', 'index', 'save-menu-items'],
'allow' => true,
'roles' => ['MenuManagement'],
],
[ // all the action are accessible to admin
'allow' => true,
'roles' => ['admin'],
],
],
],
'verbs' => [
'class' => VerbFilter::class,
'actions' => [
'delete' => ['POST'],
],
],
];
}
public function actionIndex($id = null)
{
$menus = Menu::find()->all();
$form = new MenuSelectForm();
if ($form->load(Yii::$app->request->get()) && $form->validate()) {
return $this->redirect(['menu/index', 'id' => $form->id]);
}
elseif ($id) {
$this->createMenuItem(); // create menu item if MenuItemForm sent
$menu = $this->findModel($id);
$creatorWidgets = $this->getCreatorWidgets($menu->id);
return $this->render('menu', [
'model' => $form,
'menus' => $menus,
'menu' => $menu,
'creator' => $creatorWidgets,
]);
}
else {
return $this->render('select_menu', [
'model' => $form,
'menus' => $menus,
]);
}
}
public function actionCreate()
{
$form = new MenuForm();
if ($form->load(Yii::$app->request->post()) && $form->validate()) {
try {
$menu = $this->menu_service->create($form);
return $this->redirect(['index', 'id' => $menu->id]);
} catch (\DomainException $e) {
Yii::$app->errorHandler->logException($e);
Yii::$app->session->setFlash('error', $e->getMessage());
}
}
return $this->render('create', [
'model' => $form,
]);
}
public function actionUpdate($id)
{
$menu = $this->findModel($id);
$form = new MenuForm($menu);
if ($form->load(Yii::$app->request->post()) && $form->validate()) {
try {
$this->menu_service->edit($menu->id, $form);
return $this->redirect(['index', 'id' => $menu->id]);
} catch (\DomainException $e) {
Yii::$app->errorHandler->logException($e);
Yii::$app->session->setFlash('error', $e->getMessage());
}
}
return $this->render('update', [
'model' => $form,
'menu' => $menu,
]);
}
public function actionDelete($id)
{
try {
$this->menu_service->remove($id);
} catch (\DomainException $e) {
Yii::$app->errorHandler->logException($e);
Yii::$app->session->setFlash('error', $e->getMessage());
}
return $this->redirect(['index']);
}
public function actionSaveMenuItems()
{
$json = Yii::$app->request->post('json');
Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
if (Yii::$app->request->isAjax) {
try {
$order = [];
$items = Json::decode($json, true);
foreach ($items as $item) {
$order[$item[1]] = isset($order[$item[1]]) ? $order[$item[1]]+1 : 0;
$this->menu_item_service->setPosition($item, $order[$item[1]]);
}
return [ 'result' => 'success' ];
}
catch (\RuntimeException $e) {
return [ 'result' => 'error', 'message' => $e->getMessage() ];
}
}
return ['result' => 'error', 'message' => 'Request error'];
}
private function getCreatorWidgets($menu_id): array
{
$widgets = [];
$modules = ModuleRecord::find()->active()->all();
foreach ($modules as $module) {
if (method_exists($module->class, 'getMenuItemCreator')) {
$module_widgets = call_user_func_array($module->class . '::getMenuItemCreator', [$menu_id]);
$widgets = is_array($module_widgets) ? array_merge($widgets, $module_widgets) : $widgets;
}
}
return $widgets;
}
private function createMenuItem()
{
$form = new MenuItemForm();
if ($form->load(Yii::$app->request->post()) && $form->validate()) {
try {
$this->menu_item_service->create($form);
} catch (\DomainException $e) {
Yii::$app->errorHandler->logException($e);
}
}
}
protected function findModel($id): Menu
{
if (($model = Menu::findOne($id)) !== null) {
return $model;
}
throw new NotFoundHttpException('The requested menu does not exist.');
}
}

4
backend/controllers/PageController.php

@ -26,7 +26,7 @@ class PageController extends Controller
{
return [
'access' => [
'class' => AccessControl::className(),
'class' => AccessControl::class,
'rules' => [
[
'actions' => ['create', 'view', 'index', 'update', 'delete', 'move-up', 'move-down'],
@ -40,7 +40,7 @@ class PageController extends Controller
],
],
'verbs' => [
'class' => VerbFilter::className(),
'class' => VerbFilter::class,
'actions' => [
'delete' => ['POST'],
],

2
backend/messages/ru/buttons.php

@ -9,4 +9,6 @@ return [
'Editing' => 'Редактирование',
'All Settings' => 'Все настройки',
'Create Setting' => 'Новый параметр',
'Go' => 'Перейти',
'Add to menu' => 'Добавить в меню',
];

1
backend/messages/ru/main.php

@ -27,4 +27,5 @@ return [
'Online' => 'В сети',
'Search results' => 'Поиск',
'Search...' => 'Поиск...',
'Name' => 'Название',
];

12
backend/messages/ru/menu.php

@ -0,0 +1,12 @@
<?php
return [
'Menu' => 'Меню',
'Select menu...' => 'Укажите меню',
'Create Menu' => 'Новое меню',
'Update Menu' => 'Изменить меню',
'Delete Menu' => 'Удалить меню',
'Update Menu: {name}' => 'Редактирование меню: {name}',
'Current Menu' => 'Текущее меню',
'Available Blocks' => 'Доступные блоки',
'Menu Items' => 'Элементы меню',
];

7
backend/views/layouts/left.php

@ -77,6 +77,13 @@ $model = new SearchForm();
], 'visible' => \Yii::$app->user->can('admin') || \Yii::$app->user->can('UserManagement')],
[
'label' => Yii::t('menu', 'Menu'),
'icon' => 'bars', 'url' => ['/menu/index'],
'active' => $this->context->id == 'menu',
'visible' => \Yii::$app->user->can('admin') || \Yii::$app->user->can('MenuManagement'),
],
[
'label' => Yii::t('page', 'Pages'),
'icon' => 'file-o', 'url' => ['/page/index'],
'active' => $this->context->id == 'page',

27
backend/views/menu/_form.php

@ -0,0 +1,27 @@
<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
/* @var $this yii\web\View */
/* @var $model \core\forms\menu\MenuForm */
/* @var $form yii\widgets\ActiveForm */
?>
<div class="menu-form">
<?php $form = ActiveForm::begin(); ?>
<div class="box box-default">
<div class="box-body">
<?= $form->field($model, 'name')->textInput(['maxlength' => true]) ?>
</div>
</div>
<div class="form-group">
<?= Html::submitButton(Yii::t('buttons', 'Save'), ['class' => 'btn btn-success']) ?>
</div>
<?php ActiveForm::end(); ?>
</div>

16
backend/views/menu/create.php

@ -0,0 +1,16 @@
<?php
/* @var $this yii\web\View */
/* @var $model \core\forms\menu\MenuForm */
$this->title = Yii::t('menu', 'Create Menu');
$this->params['breadcrumbs'][] = ['label' => Yii::t('menu', 'Menu'), 'url' => ['index']];
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="menu-create">
<?= $this->render('_form', [
'model' => $model,
]) ?>
</div>

114
backend/views/menu/menu.php

@ -0,0 +1,114 @@
<?php
/**
* Created by Error202
* Date: 10.07.2018
*/
use kartik\form\ActiveForm;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
/**
* @var $this \yii\web\View
* @var $model \core\forms\menu\MenuSelectForm
* @var $menus \core\entities\menu\Menu[]
* @var $menu \core\entities\menu\Menu
* @var $creator array
*/
$this->title = Yii::t('menu', 'Menu');
$this->params['breadcrumbs'][] = Html::encode($menu->name);
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="menu">
<div class="box box-default">
<div class="box-header with-border"><?= Yii::t('menu', 'Current Menu') ?></div>
<div class="box-body">
<div class="row">
<div class="col-md-6">
<?php $form = ActiveForm::begin([
'enableClientValidation' => false,
'method' => 'get',
'id' => 'select_menu',
]); ?>
<?= $form->field($model, 'id')->dropDownList(ArrayHelper::map($menus, 'id', 'name'), [
//'prompt' => Yii::t('menu', 'Select menu...'),
'value' => $menu->id,
'onchange' => 'this.form.submit()',
])->label(false) ?>
<?php ActiveForm::end(); ?>
</div>
<div class="col-md-6">
<?= Html::a(Yii::t('menu', 'Create Menu'), ['menu/create'], [
'class' => 'btn btn-success',
]) ?>
<?= Html::a(Yii::t('menu', 'Update Menu'), ['menu/update', 'id' => $menu->id], [
'class' => 'btn btn-primary',
]) ?>
<?= Html::a(Yii::t('menu', 'Delete Menu'), ['menu/delete', 'id' => $menu->id], [
'class' => 'btn btn-danger',
'data' => [
'confirm' => Yii::t('buttons', 'Are you sure you want to delete this item?'),
'method' => 'post',
],
]) ?>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-3">
<div class="box box-default">
<div class="box-header with-border"><?= Yii::t('menu', 'Available Blocks') ?></div>
<div class="box-body">
<div class="panel-group" id="accordion">
<?php foreach ($creator as $item): ?>
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapseOne"><?= $item['title'] ?>
<i class="fa fa-angle-down pull-right" aria-hidden="true"></i></a>
</h4>
</div>
<div id="collapseOne" class="panel-collapse collapse">
<div class="panel-body">
<?= $item['content'] ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
</div>
<div class="col-md-9">
<div class="box box-default">
<div class="box-header with-border"><?= Yii::t('menu', 'Menu Items') ?></div>
<div class="box-body">
<?= \backend\components\menu\widgets\MenuEditorWidget::widget([
'menu_id' => $menu->id
]) ?>
</div>
</div>
</div>
</div>
</div>

51
backend/views/menu/select_menu.php

@ -0,0 +1,51 @@
<?php
/**
* Created by Error202
* Date: 10.07.2018
*/
use kartik\form\ActiveForm;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
/**
* @var $this \yii\web\View
* @var $model \core\forms\menu\MenuSelectForm
* @var $menus \core\entities\menu\Menu[]
*/
$this->title = Yii::t('menu', 'Menu');
$this->params['breadcrumbs'][] = $this->title;
?>
<div class="menu">
<div class="box box-default">
<div class="box-header with-border"><?= Yii::t('menu', 'Current Menu') ?></div>
<div class="box-body">
<div class="row">
<div class="col-md-6">
<?php $form = ActiveForm::begin([
'enableClientValidation' => false,
'method' => 'get',
'id' => 'select_menu',
]); ?>
<?= $form->field($model, 'id')->dropDownList(ArrayHelper::map($menus, 'id', 'name'), [
'prompt' => Yii::t('menu', 'Select menu...'),
'onchange' => 'this.form.submit()',
])->label(false) ?>
<?php ActiveForm::end(); ?>
</div>
<div class="col-md-2">
<?= Html::a(Yii::t('menu', 'Create Menu'), ['menu/create'], [
'class' => 'btn btn-success',
]) ?>
</div>
</div>
</div>
</div>
</div>

18
backend/views/menu/update.php

@ -0,0 +1,18 @@
<?php
/* @var $this yii\web\View */
/* @var $menu \core\entities\menu\Menu */
/* @var $model \core\forms\menu\MenuForm */
$this->title = Yii::t('menu', 'Update Menu: {name}', ['name' => $menu->name]);
$this->params['breadcrumbs'][] = ['label' => Yii::t('menu', 'Menu'), 'url' => ['index']];
$this->params['breadcrumbs'][] = ['label' => $menu->name, 'url' => ['index', 'id' => $menu->id]];
$this->params['breadcrumbs'][] = Yii::t('buttons', 'Editing');
?>
<div class="menu-update">
<?= $this->render('_form', [
'model' => $model,
]) ?>
</div>

13
common/modules/blog/BlogModule.php

@ -1,6 +1,7 @@
<?php
namespace common\modules\blog;
use common\modules\blog\widgets\MenuItemCreatorWidget;
use core\components\modules\ModuleInterface;
use yii\helpers\ArrayHelper;
@ -97,4 +98,16 @@ class BlogModule extends \yii\base\Module implements ModuleInterface
];
}
}
public static function getMenuItemCreator($menu_id): array
{
$widgets = [];
$widgets[] = [
'title' => \Yii::t('blog', 'Blog'),
'content' => MenuItemCreatorWidget::widget([
'menu_id' => $menu_id,
]),
];
return $widgets;
}
}

19
common/modules/blog/controllers/manage/PostController.php

@ -186,6 +186,25 @@ class PostController extends Controller
return $out;
}
public function actionPostSearch($q = null, $id = null)
{
\Yii::$app->response->format = Response::FORMAT_JSON;
$out = ['results' => ['id' => '', 'text' => '']];
if (!is_null($q)) {
$data = BlogPost::find()->select('id, title as text')->andWhere(['like', 'title', $q])->limit(20)->asArray()->all();
$out['results'] = array_values($data);
}
elseif ($id > 0) {
$tag_name = BlogPost::findOne($id)->title;
$out['results'] = ['id' => $tag_name, 'text' => $tag_name];
}
else {
$data = BlogPost::find()->select('id, title as text')->orderBy(['created_at' => SORT_DESC])->limit(20)->asArray()->all();
$out['results'] = array_values($data);
}
return $out;
}
/**
* @param $id
*

3
common/modules/blog/messages/ru/blog.php

@ -64,4 +64,7 @@ return [
'Preview on site' => 'Просмотр на сайте',
'Publish Date' => 'Дата публикации',
'The article will be published after the specified date if its status is not a draft' => 'Статья будет опубликована после наступления указанной даты, если ее статус не черновик.',
'Blog Post' => 'Статья',
'Blog Home' => 'Блог',
'Select post...' => 'Укажите статью...',
];

30
common/modules/blog/widgets/MenuItemCreatorWidget.php

@ -0,0 +1,30 @@
<?php
/**
* Created by Error202
* Date: 10.07.2018
*/
namespace common\modules\blog\widgets;
use core\forms\menu\MenuItemForm;
use yii\base\Widget;
class MenuItemCreatorWidget extends Widget
{
public $menu_id;
public function run()
{
$form = new MenuItemForm();
$form->module = \Yii::t('blog', 'Blog');
$form->name = \Yii::t('blog', 'Blog');
$form->title_attr = \Yii::t('blog', 'Blog');
$form->menu_id = $this->menu_id;
$form->url = '/blog/post/index';
return $this->render('menu-item/creator', [
'model' => $form,
]);
}
}

117
common/modules/blog/widgets/views/menu-item/creator.php

@ -0,0 +1,117 @@
<?php
/**
* Created by Error202
* Date: 10.07.2018
*/
/**
* @var $this \yii\web\View
* @var $model \core\forms\menu\MenuItemForm
*/
use yii\widgets\ActiveForm;
use yii\helpers\Html;
use yii\web\JsExpression;
use yii\helpers\Url;
$js = <<<JS
function selectBlogPostOn() {
$("#post_select").removeAttr('disabled');
updateUrl('post')
}
function selectBlogPostOff() {
$("#post_select").attr('disabled', 'disabled');
updateUrl('blog')
}
function updateUrl(type) {
if (type === 'blog') {
$("#blog_menu_item_url").val('/blog/post/index');
$("#blog_menu_item_url_params").val('');
}
else {
var selected_post = $("#post_select").val();
$("#blog_menu_item_url").val('/blog/post/post/');
$("#blog_menu_item_url_params").val('{"id":'+selected_post+'}');
}
}
JS;
$this->registerJs($js, $this::POS_HEAD);
$fetchUrl = Url::to( [ '/blog/manage/post/post-search' ] );
?>
<div class="menu_item_widget">
<div class="form-group">
<div class="radio">
<?= Html::label(Html::radio('blog_item_type', true, [
'value' => 'blog_home',
'onclick' => new JsExpression('selectBlogPostOff()'),
]) . Yii::t('blog', 'Blog Home')) ?>
</div>
<div class="radio">
<?= Html::label(Html::radio('blog_item_type', false, [
'value' => 'blog_post',
'onclick' => new JsExpression('selectBlogPostOn()'),
]) . Yii::t('blog', 'Blog Post')) ?>
</div>
<div>
<?= \kartik\widgets\Select2::widget([
'name' => 'post_select',
'value' => '',
'options' => [
'placeholder' => Yii::t('blog', 'Select post...'),
'id' => 'post_select',
'onchange' => new JsExpression("updateUrl('post')"),
],
'pluginOptions' => [
'disabled' => true,
'ajax' => [
'url' => $fetchUrl,
'dataType' => 'json',
'data' => new JsExpression('function(params) { return {q:params.term}; }')
],
'escapeMarkup' => new JsExpression('function (markup) { return markup; }'),
'templateResult' => new JsExpression('function(tag) { return tag.text; }'),
'templateSelection' => new JsExpression('function (tag) { return tag.text; }'),
],
]) ?>
</div>
</div>
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'name')->hiddenInput([
])->label(false) ?>
<?= $form->field($model, 'title_attr')->hiddenInput([
])->label(false) ?>
<?= $form->field($model, 'module')->hiddenInput([
])->label(false) ?>
<?= $form->field($model, 'menu_id')->hiddenInput([
])->label(false) ?>
<?= $form->field($model, 'url')->hiddenInput([
'id' => 'blog_menu_item_url',
])->label(false) ?>
<?= $form->field($model, 'url_params')->hiddenInput([
'value' => '',
'id' => 'blog_menu_item_url_params',
])->label(false) ?>
<div class="form-group">
<?= Html::submitButton(Yii::t('buttons', 'Add to menu'), [
'class' => 'btn btn-info btn-sm pull-right'
]) ?>
</div>
<?php ActiveForm::end(); ?>
</div>

4
composer.json

@ -36,7 +36,9 @@
"bower-asset/jquery-cookie": "^1.4",
"drewm/mailchimp-api": "^2.4",
"yiisoft/yii2-imagine": "^2.1",
"zertex/yii2-avatar-generator": "*"
"zertex/yii2-avatar-generator": "*",
"paulzi/yii2-sortable": "^1.0",
"paulzi/yii2-adjacency-list" : "^2.1"
},
"require-dev": {
"yiisoft/yii2-debug": "~2.0.0",

33
console/migrations/m180709_202649_create_menu_table.php

@ -0,0 +1,33 @@
<?php
use yii\db\Migration;
/**
* Handles the creation of table `menu`.
*/
class m180709_202649_create_menu_table extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$tableOptions = null;
if ($this->db->driverName === 'mysql') {
$tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=InnoDB';
}
$this->createTable('{{%menu}}', [
'id' => $this->primaryKey(),
'name' => $this->string(255)->notNull(),
], $tableOptions);
}
/**
* {@inheritdoc}
*/
public function safeDown()
{
$this->dropTable('{{%menu}}');
}
}

55
console/migrations/m180709_202659_create_menu_items_table.php

@ -0,0 +1,55 @@
<?php
use yii\db\Migration;
/**
* Handles the creation of table `menu_items`.
*/
class m180709_202659_create_menu_items_table extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$tableOptions = null;
if ($this->db->driverName === 'mysql') {
$tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=InnoDB';
}
$this->createTable('{{%menu_items}}', [
'id' => $this->primaryKey(),
'menu_id' => $this->integer()->notNull(),
'parent_id' => $this->integer(),
'name' => $this->string(255)->notNull(),
'title_attr' => $this->string(255),
'target' => $this->string(20),
'css' => $this->string(255),
'style' => $this->string(255),
'module' => $this->string(255),
'url' => $this->string(255),
'url_params' => $this->text(), // json id=>1, ...
'sort' => $this->integer()->notNull()->defaultValue(1),
], $tableOptions);
$this->createIndex('idx_menu_item_sort', '{{%menu_items}}', ['menu_id', 'sort']);
$this->createIndex('idx_menu_items_menu_id', '{{%menu_items}}', 'menu_id');
$this->createIndex('idx_menu_items_parent_id', '{{%menu_items}}', 'parent_id');
$this->addForeignKey('frg_menu_items_menu_id', '{{%menu_items}}', 'menu_id', '{{%menu}}', 'id', 'CASCADE');
}
/**
* {@inheritdoc}
*/
public function safeDown()
{
$this->dropForeignKey('frg_menu_items_menu_id', '{{%menu_items}}');
$this->dropIndex('idx_menu_items_parent_id', '{{%menu_items}}');
$this->dropIndex('idx_menu_items_menu_id', '{{%menu_items}}');
$this->dropIndex('idx_menu_item_sort', '{{%menu_items}}');
$this->dropTable('{{%menu_items}}');
}
}

7
core/entities/ModuleRecord.php

@ -2,6 +2,8 @@
namespace core\entities;
use core\entities\queries\ModuleRecordQuery;
/**
* This is the model class for table "modules".
@ -47,4 +49,9 @@ class ModuleRecord extends \yii\db\ActiveRecord
'type' => 'Type',
];
}
public static function find(): ModuleRecordQuery
{
return new ModuleRecordQuery(static::class);
}
}

4
core/entities/Page.php

@ -61,8 +61,8 @@ class Page extends ActiveRecord
public function behaviors(): array
{
return [
MetaBehavior::className(),
NestedSetsBehavior::className(),
MetaBehavior::class,
NestedSetsBehavior::class,
];
}

44
core/entities/menu/Menu.php

@ -0,0 +1,44 @@
<?php
/**
* Created by Error202
* Date: 09.07.2018
*/
namespace core\entities\menu;
use yii\db\ActiveRecord;
/**
* Class Menu
* @package core\entities
*
* @property integer $id
* @property string $name
*
* @property MenuItem[] $items
*/
class Menu extends ActiveRecord
{
public static function create($name): self
{
$menu = new static();
$menu->name = $name;
return $menu;
}
public function edit($name): void
{
$this->name = $name;
}
public static function tableName(): string
{
return '{{%menu}}';
}
public function getItems()
{
return $this->hasMany(MenuItem::class, ['menu_id' => 'id'])->orderBy(['sort' => SORT_ASC]);
}
}

103
core/entities/menu/MenuItem.php

@ -0,0 +1,103 @@
<?php
/**
* Created by Error202
* Date: 09.07.2018
*/
namespace core\entities\menu;
use paulzi\sortable\SortableBehavior;
use yii\db\ActiveRecord;
/**
* Class MenuItem
* @package core\entities\menu
*
* @property integer $id
* @property integer $menu_id
* @property integer $parent_id
* @property string $name
* @property string $title_attr
* @property string $target
* @property string $css
* @property string $style
* @property string $module
* @property string $url
* @property string $url_params
* @property integer $sort
*
* @property MenuItem $parent
* @property MenuItem[] $children
*/
class MenuItem extends ActiveRecord
{
public static function create($menu_id, $parent_id, $name, $title_attr, $target, $css, $style, $module, $url, $url_params): self
{
$menu = new static();
$menu->menu_id = $menu_id;
$menu->parent_id = $parent_id;
$menu->name = $name;
$menu->title_attr = $title_attr;
$menu->target = $target;
$menu->css = $css;
$menu->style = $style;
$menu->module = $module;
$menu->url = $url;
$menu->url_params = $url_params;
return $menu;
}
public function edit($menu_id, $parent_id, $name, $title_attr, $target, $css, $style, $module, $url, $url_params): void
{
$this->menu_id = $menu_id;
$this->parent_id = $parent_id;
$this->name = $name;
$this->title_attr = $title_attr;
$this->target = $target;
$this->css = $css;
$this->style = $style;
$this->module = $module;
$this->url = $url;
$this->url_params = $url_params;
}
public static function tableName(): string
{
return '{{%menu_items}}';
}
/*public function behaviors()
{
return [
[
'class' => SortableBehavior::class,
'query' => ['menu_id'],
],
];
}*/
public function beforeSave( $insert ) {
if (parent::beforeSave($insert)) {
if (!isset($this->sort)) {
$count = MenuItem::find()
->andWhere( [ 'menu_id' => $this->menu_id ] )
->andWhere( [ 'parent_id' => $this->parent_id ] )
->count();
$this->sort = $count;
}
return true;
}
return false;
}
public function getChildren()
{
return $this->hasMany(MenuItem::class, ['parent_id' => 'id'])->orderBy(['sort' => SORT_ASC]);
}
public function getParent()
{
return $this->hasOne(MenuItem::class, ['id' => 'parent_id']);
}
}

15
core/entities/queries/ModuleRecordQuery.php

@ -0,0 +1,15 @@
<?php
namespace core\entities\queries;
use yii\db\ActiveQuery;
class ModuleRecordQuery extends ActiveQuery
{
public function active()
{
return $this->andWhere([
'active' => 1,
]);
}
}

37
core/forms/menu/MenuForm.php

@ -0,0 +1,37 @@
<?php
namespace core\forms\menu;
use core\entities\menu\Menu;
use yii\base\Model;
use Yii;
class MenuForm extends Model
{
public $name;
private $_menu;
public function __construct(Menu $menu = null, $config = [])
{
if ($menu) {
$this->name = $menu->name;
$this->_menu = $menu;
}
parent::__construct($config);
}
public function rules(): array
{
return [
[['name'], 'required'],
[['name'], 'string', 'max' => 255],
];
}
public function attributeLabels() {
return [
'name' => Yii::t('main', 'Name'),
];
}
}

68
core/forms/menu/MenuItemForm.php

@ -0,0 +1,68 @@
<?php
namespace core\forms\menu;
use core\entities\menu\MenuItem;
use yii\base\Model;
use Yii;
class MenuItemForm extends Model
{
public $menu_id;
public $parent_id;
public $name;
public $title_attr;
public $target;
public $css;
public $style;
public $module;
public $url;
public $url_params;
private $_menu;
public function __construct(MenuItem $menu = null, $config = [])
{
if ($menu) {
$this->menu_id = $menu->menu_id;
$this->parent_id = $menu->parent_id;
$this->name = $menu->name;
$this->title_attr = $menu->title_attr;
$this->target = $menu->target;
$this->css = $menu->css;
$this->style = $menu->style;
$this->module = $menu->module;
$this->url = $menu->url;
$this->url_params = $menu->url_params;
$this->_menu = $menu;
}
parent::__construct($config);
}
public function rules(): array
{
return [
[['name', 'menu_id', 'url'], 'required'],
[['name', 'title_attr', 'css', 'style', 'module', 'url'], 'string', 'max' => 255],
[['target'], 'string', 'max' => 20],
['url_params', 'string'],
[['parent_id', 'menu_id'], 'integer'],
];
}
public function attributeLabels() {
return [
'menu_id' => Yii::t('main', 'Menu'),
'parent id' => Yii::t('main', 'Parent menu item'),
'name' => Yii::t('main', 'Name'),
'title_attr' => Yii::t('main', 'Title attribute'),
'target' => Yii::t('main', 'Target'),
'css' => Yii::t('main', 'CSS Classes'),
'style' => Yii::t('main', 'CSS Style'),
'module' => Yii::t('main', 'Module'),
'url' => Yii::t('main', 'Url'),
'url_params' => Yii::t('main', 'Url Params'),
];
}
}

25
core/forms/menu/MenuSelectForm.php

@ -0,0 +1,25 @@
<?php
namespace core\forms\menu;
use yii\base\Model;
use Yii;
class MenuSelectForm extends Model
{
public $id;
public function rules(): array
{
return [
[['id'], 'required'],
[['id'], 'integer'],
];
}
public function attributeLabels() {
return [
'id' => Yii::t('menu', 'Menu'),
];
}
}

31
core/repositories/menu/MenuItemRepository.php

@ -0,0 +1,31 @@
<?php
namespace core\repositories\menu;
use core\entities\menu\MenuItem;
use core\repositories\NotFoundException;
class MenuItemRepository
{
public function get($id): MenuItem
{
if (!$item = MenuItem::findOne($id)) {
throw new NotFoundException('Menu is not found.');
}
return $item;
}
public function save(MenuItem $item): void
{
if (!$item->save()) {
throw new \RuntimeException('Saving error.');
}
}
public function remove(MenuItem $item): void
{
if (!$item->delete()) {
throw new \RuntimeException('Removing error.');
}
}
}

31
core/repositories/menu/MenuRepository.php

@ -0,0 +1,31 @@
<?php
namespace core\repositories\menu;
use core\entities\menu\Menu;
use core\repositories\NotFoundException;
class MenuRepository
{
public function get($id): Menu
{
if (!$menu = Menu::findOne($id)) {
throw new NotFoundException('Menu is not found.');
}
return $menu;
}
public function save(Menu $menu): void
{
if (!$menu->save()) {
throw new \RuntimeException('Saving error.');
}
}
public function remove(Menu $menu): void
{
if (!$menu->delete()) {
throw new \RuntimeException('Removing error.');
}
}
}

67
core/services/menu/MenuItemManageService.php

@ -0,0 +1,67 @@
<?php
namespace core\services\menu;
use core\entities\menu\MenuItem;
use core\forms\menu\MenuItemForm;
use core\repositories\menu\MenuItemRepository;
class MenuItemManageService
{
private $repository;
public function __construct(MenuItemRepository $repository)
{
$this->repository = $repository;
}
public function create(MenuItemForm $form): MenuItem
{
$menu = MenuItem::create(
$form->menu_id,
$form->parent_id,
$form->name,
$form->title_attr,
$form->target,
$form->css,
$form->style,
$form->module,
$form->url,
$form->url_params
);
$this->repository->save($menu);
return $menu;
}
public function edit($id, MenuItemForm $form): void
{
$menu = $this->repository->get($id);
$menu->edit(
$form->menu_id,
$form->parent_id,
$form->name,
$form->title_attr,
$form->target,
$form->css,
$form->style,
$form->module,
$form->url,
$form->url_params
);
$this->repository->save($menu);
}
public function remove($id): void
{
$menu = $this->repository->get($id);
$this->repository->remove($menu);
}
public function setPosition(array $position, $sort): void
{
$item = $this->repository->get($position[0]);
$item->parent_id = $position[1] ? $position[1] : null;
$item->sort = $sort;
$this->repository->save($item);
}
}

41
core/services/menu/MenuManageService.php

@ -0,0 +1,41 @@
<?php
namespace core\services\menu;
use core\entities\menu\Menu;
use core\forms\menu\MenuForm;
use core\repositories\menu\MenuRepository;
class MenuManageService
{
private $repository;
public function __construct(MenuRepository $repository)
{
$this->repository = $repository;
}
public function create(MenuForm $form): Menu
{
$menu = Menu::create(
$form->name
);
$this->repository->save($menu);
return $menu;
}
public function edit($id, MenuForm $form): void
{
$menu = $this->repository->get($id);
$menu->edit(
$form->name
);
$this->repository->save($menu);
}
public function remove($id): void
{
$menu = $this->repository->get($id);
$this->repository->remove($menu);
}
}
Loading…
Cancel
Save