This commit is contained in:
Kerim 2023-06-18 19:26:27 +05:00
parent 402951cbe0
commit 0e85d1fc3a
72 changed files with 4551 additions and 106 deletions

View File

@ -5,6 +5,8 @@ APP_DEBUG=true
APP_URL=http://localhost
APP_LOCALE=en
BAGISTO_URL=https://nurgul.com.tm/app/api/
ACTIVE_THEME=demo
BACKEND_URI=/admin
CMS_ROUTE_CACHE=false

View File

@ -0,0 +1,19 @@
# MIT license
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,169 @@
<?php namespace Indikator\DevTools;
use System\Classes\PluginBase;
use System\Classes\SettingsManager;
use Event;
use Backend;
use BackendAuth;
use BackendMenu;
use Indikator\DevTools\Models\Settings as Tools;
use Db;
class Plugin extends PluginBase
{
public $elevated = true;
public function pluginDetails()
{
return [
'name' => 'indikator.devtools::lang.plugin.name',
'description' => 'indikator.devtools::lang.plugin.description',
'author' => 'indikator.devtools::lang.plugin.author',
'icon' => 'icon-wrench',
'homepage' => 'https://github.com/gergo85/oc-devtools'
];
}
public function registerSettings()
{
return [
'devtool' => [
'label' => 'indikator.devtools::lang.help.menu_label',
'description' => 'indikator.devtools::lang.help.menu_description',
'icon' => 'icon-wrench',
'class' => 'Indikator\DevTools\Models\Settings',
'category' => SettingsManager::CATEGORY_SYSTEM,
'permissions' => ['indikator.devtools.settings']
]
];
}
public function registerFormWidgets()
{
return [
'Indikator\DevTools\FormWidgets\Help' => [
'label' => 'Help',
'code' => 'help'
]
];
}
public function registerPermissions()
{
return [
'indikator.devtools.editor' => [
'tab' => 'indikator.devtools::lang.plugin.name',
'label' => 'indikator.devtools::lang.editor.permission',
'order' => 100,
'roles' => ['developer']
],
'indikator.devtools.settings' => [
'tab' => 'indikator.devtools::lang.plugin.name',
'label' => 'indikator.devtools::lang.help.permission',
'order' => 200,
'roles' => ['developer']
]
];
}
public function boot()
{
// Add new menu
BackendMenu::registerCallback(function($manager) {
$manager->registerMenuItems('Indikator.DevTools', [
'editor' => [
'label' => 'indikator.devtools::lang.editor.menu_label',
'url' => Backend::url('indikator/devtools/editor'),
'icon' => 'icon-file-code-o',
'iconSvg' => 'plugins/indikator/devtools/assets/images/devtools-icon.svg',
'permissions' => ['indikator.devtools.editor'],
'order' => 390,
'sideMenu' => [
'assets' => [
'label' => 'indikator.devtools::lang.editor.plugins',
'icon' => 'icon-cubes',
'url' => 'javascript:;',
'attributes' => ['data-menu-item' => 'assets'],
'counterLabel' => 'cms::lang.asset.unsaved_label',
'order' => 100
]
]
]
]);
});
// Add new features
Event::listen('backend.form.extendFields', function($form)
{
// Security check
if (!BackendAuth::check()) {
return;
}
// Help docs
if ($this->tools_enabled('help') && (get_class($form->config->model) == 'Cms\Classes\Page' || get_class($form->config->model) == 'Cms\Classes\Partial' || get_class($form->config->model) == 'Cms\Classes\Layout') || get_class($form->config->model) == 'Indikator\DevTools\Classes\Asset') {
if (get_class($form->config->model) == 'Indikator\DevTools\Classes\Asset') {
$content = 'php';
}
else {
$content = 'cms';
}
$form->addSecondaryTabFields([
'help' => [
'label' => '',
'tab' => 'indikator.devtools::lang.help.tab',
'type' => 'help',
'content' => $content
]
]);
return;
}
// Wysiwyg editor
if ($this->tools_enabled('wysiwyg') && get_class($form->config->model) == 'Cms\Classes\Content') {
foreach ($form->getFields() as $field) {
if (!empty($field->config['type']) && $field->config['type'] == 'codeeditor') {
$field->config['type'] = $field->config['widget'] = 'richeditor';
}
}
}
});
}
public function tools_enabled($name)
{
// Security check
if ($name != 'help' && $name != 'wysiwyg') {
return false;
}
// Is enabled
if (!Tools::get($name.'_enabled', false)) {
return false;
}
// My account
$admin = BackendAuth::getUser();
// Is superuser
if (Tools::get($name.'_superuser', false) && $admin->is_superuser == 1) {
return true;
}
// Is admin group
if (Tools::get($name.'_admingroup', false) > 0 && Db::table('backend_users_groups')->where(['user_id' => $admin->id, 'user_group_id' => Tools::get($name.'_admingroup', false)])->count() == 1) {
return true;
}
// Is current user
if (Tools::get($name.'_adminid', false) > 0 && $admin->id == Tools::get($name.'_adminid', false)) {
return true;
}
// Finish
return false;
}
}

View File

@ -0,0 +1,24 @@
# Developer Tools Plugin
It is a must-have plugin for you, if you use the October's Docs a lot or you want to use the build-in wysiwyg editor on the Content page.
## Main features
* __Edit directly plugins with the online code editor!__
* Add a Help tab to the Pages, Partials or Layouts pages.
* Replace the code editor to wysiwyg editor on the Content page.
* Show the PHP's configuration if you logged as Superuser.
* Set a different permissions for the above features.
## Available languages
* en - English
* hu - Magyar
## Location of plugin
You can find it in the back-end: __Settings > System > Developer Tools__
## PHP's configuration
If you like to get the PHP's configuration of your server, use the following URL: www.yourwebsite.com/phpinfo
## Installation
1. Go to the __Settings > Updates & Plugins__ page in the Backend.
1. Click on the __Install plugins__ button.
1. Type the __Developer Tools__ text in the search field.

View File

@ -0,0 +1 @@
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 512 512" height="512px" id="Слой_1" version="1.1" viewBox="0 0 512 512" width="512px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="wrench"><g><path d="M512,256c0,141.391-114.609,255.992-256,255.992C114.609,511.984,0,397.383,0,256.008 c0-141.391,114.609-255.992,256-256C397.391,0.008,512,114.617,512,256z" fill="#FA6E51"/><path d="M302.195,209.852L112.727,399.32l110.516,110.516c10.734,1.367,21.648,2.156,32.758,2.156 c139.391-0.008,252.656-111.398,255.852-250.016L356.742,106.875l-78.773,78.766L302.195,209.852z" fill="#E8573F"/></g><g><path d="M405.531,156.766l-41.086,41.094c-6.703,6.703-23.188,1.086-36.813-12.523 C314,171.703,308.391,155.227,315.109,148.5l0,0l41.633-41.625c-5.984-1.688-12.297-2.57-18.82-2.578 c-38.555,0.008-69.789,31.234-69.789,69.773c0,8.43,1.5,16.523,4.242,23.984L112.914,357.5c-5.32,5.313-8.594,12.672-8.617,20.797 c0,16.234,13.172,29.398,29.406,29.406c8.133,0,15.484-3.281,20.797-8.617l0,0l159.461-159.438 c7.461,2.727,15.539,4.227,23.961,4.219c38.539,0,69.781-31.258,69.781-69.797C407.703,168.109,406.945,162.297,405.531,156.766z" fill="#F4F6F9" id="wrench_3_"/><path d="M122.078,378.328c0,6.422,5.219,11.625,11.625,11.625c6.43,0,11.633-5.203,11.633-11.625 s-5.203-11.633-11.633-11.633C127.297,366.695,122.078,371.906,122.078,378.328z" fill="#CBD0D8"/></g></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,706 @@
/*
* Scripts for the CMS page.
*/
+function ($) { "use strict";
var Base = $.oc.foundation.base,
BaseProto = Base.prototype
var CmsPage = function() {
Base.call(this)
//
// Initialization
//
this.init()
}
CmsPage.prototype = Object.create(BaseProto)
CmsPage.prototype.constructor = CmsPage
CmsPage.prototype.init = function() {
$(document).ready(this.proxy(this.registerHandlers))
}
CmsPage.prototype.updateTemplateList = function(type) {
var $form = $('#cms-side-panel form[data-template-type='+type+']'),
templateList = type + 'List'
$form.request(templateList + '::onUpdate', {
complete: function() {
$('button[data-control=delete-template]', $form).trigger('oc.triggerOn.update')
}
})
}
CmsPage.prototype.registerHandlers = function() {
var $document = $(document),
$masterTabs = $('#cms-master-tabs')
$masterTabs.on('closed.oc.tab', this.proxy(this.onTabClosed))
$masterTabs.on('beforeClose.oc.tab', this.proxy(this.onBeforeTabClose))
$masterTabs.on('oc.beforeRequest', this.proxy(this.onBeforeRequest))
$masterTabs.on('shown.bs.tab', this.proxy(this.onTabShown))
$masterTabs.on('initTab.oc.tab', this.proxy(this.onInitTab))
$masterTabs.on('afterAllClosed.oc.tab', this.proxy(this.onAfterAllTabsClosed))
$(window).on('ajaxInvalidField', this.proxy(this.ajaxInvalidField))
$document.on('open.oc.list', '#cms-side-panel', this.proxy(this.onOpenDocument))
$document.on('ajaxUpdate', '[data-control=filelist], [data-control=assetlist]', this.proxy(this.onAjaxUpdate))
$document.on('ajaxError', '#cms-master-tabs form', this.proxy(this.onAjaxError))
$document.on('ajaxSuccess', '#cms-master-tabs form', this.proxy(this.onAjaxSuccess))
$document.on('click', '#cms-side-panel form button[data-control=create-template], #cms-side-panel form li a[data-control=create-template]', this.proxy(this.onCreateTemplateClick))
$document.on('click', '#cms-side-panel form button[data-control=delete-template]', this.proxy(this.onDeleteTemplateClick))
$document.on('showing.oc.inspector', '[data-inspectable]', this.proxy(this.onInspectorShowing))
$document.on('hidden.oc.inspector', '[data-inspectable]', this.proxy(this.onInspectorHidden))
$document.on('hiding.oc.inspector', '[data-inspectable]', this.proxy(this.onInspectorHiding))
$document.on('click', '#cms-master-tabs > div.tab-content > .tab-pane.active .control-componentlist a.remove', this.proxy(this.onComponentRemove))
$document.on('click', '#cms-component-list [data-component]', this.proxy(this.onComponentClick))
}
// EVENT HANDLERS
// ============================
CmsPage.prototype.onOpenDocument = function(event) {
/*
* Open a document when it's clicked in the sidebar
*/
var $item = $(event.relatedTarget),
$form = $item.closest('[data-template-type]'),
data = {
type: $form.data('template-type'),
theme: $item.data('item-theme'),
path: $item.data('item-path')
},
tabId = data.type + '-' + data.theme + '-' + data.path
if (data.type == 'asset' && $item.data('editable') === undefined)
return true
if ($form.length == 0)
return false
/*
* Find if the tab is already opened
*/
if ($('#cms-master-tabs').data('oc.tab').goTo(tabId))
return false
/*
* Open a new tab
*/
$.oc.stripeLoadIndicator.show()
$form.request('onOpenTemplate', {
data: data
}).done(function(data) {
$.oc.stripeLoadIndicator.hide()
var fileType = data.tabTitle.split('.').pop()
if (fileType == 'php' || fileType == 'js' || fileType == 'css' || fileType == 'html' || fileType == 'htm') fileType = 'icon-file-code-o'
else fileType = 'icon-file-text-o'
$('#cms-master-tabs').ocTab('addTab', data.tabTitle, data.tab, tabId, fileType)
}).always(function() {
$.oc.stripeLoadIndicator.hide()
}).fail(function(jqXHR, textStatus, errorThrown) {
alert(jqXHR.responseText.length ? jqXHR.responseText : jqXHR.statusText)
$.oc.stripeLoadIndicator.hide()
})
return false
}
CmsPage.prototype.ajaxInvalidField = function(ev, element, name, messages, isFirst) {
/*
* Detect invalid fields, uncollapse the panel
*/
if (!isFirst)
return
ev.preventDefault()
var $el = $(element),
$panel = $el.closest('.form-tabless-fields.collapsed'),
$primaryPanel = $el.closest('.control-tabs.primary-tabs.collapsed')
if ($panel.length > 0)
$panel.removeClass('collapsed')
if ($primaryPanel.length > 0) {
$primaryPanel.removeClass('collapsed')
var pane = $primaryPanel.closest('.tab-pane'),
$secondaryPanel = $('.control-tabs.secondary-tabs', pane)
$secondaryPanel.removeClass('primary-collapsed')
}
$el.focus()
}
CmsPage.prototype.onTabClosed = function(ev) {
this.updateModifiedCounter()
if ($('> div.tab-content > div.tab-pane', '#cms-master-tabs').length == 0)
this.setPageTitle('')
}
CmsPage.prototype.onBeforeTabClose = function(ev) {
if ($.fn.table !== undefined)
$('[data-control=table]', ev.relatedTarget).table('dispose')
$.oc.foundation.controlUtils.disposeControls(ev.relatedTarget.get(0))
}
CmsPage.prototype.onBeforeRequest = function(ev) {
var $form = $(ev.target)
if ($('.components .layout-cell.error-component', $form).length > 0) {
if (!confirm('The form contains unknown components. Their properties will be lost on save. Do you want to save the form?'))
ev.preventDefault()
}
}
CmsPage.prototype.onTabShown = function(ev) {
/*
* Listen for the tabs "shown" event to track the current template in the list
*/
var $target = $(ev.target)
if ($target.closest('[data-control=tab]').attr('id') != 'cms-master-tabs')
return
var dataId = $target.closest('li').attr('data-tab-id'),
title = $target.attr('title'),
$sidePanel = $('#cms-side-panel')
if (title)
this.setPageTitle(title)
$sidePanel.find('[data-control=filelist]').fileList('markActive', dataId)
$sidePanel.find('form').trigger('oc.list.setActiveItem', [dataId])
}
CmsPage.prototype.onInitTab = function(ev, data) {
/*
* Listen for the tabs "initTab" event to inject extra controls to the tab
*/
if ($(ev.target).attr('id') != 'cms-master-tabs')
return
var $collapseIcon = $('<a href="javascript:;" class="tab-collapse-icon tabless"><i class="icon-chevron-up"></i></a>'),
$panel = $('.form-tabless-fields', data.pane)
$panel.append($collapseIcon);
$collapseIcon.click(function() {
$panel.toggleClass('collapsed')
if (typeof(localStorage) !== 'undefined')
localStorage.ocCmsTablessCollapsed = $panel.hasClass('collapsed') ? 1 : 0
window.setTimeout(function() {
$(window).trigger('oc.updateUi')
}, 500)
return false
})
var $primaryCollapseIcon = $('<a href="javascript:;" class="tab-collapse-icon primary"><i class="icon-chevron-down"></i></a>'),
$primaryPanel = $('.control-tabs.primary-tabs', data.pane),
$secondaryPanel = $('.control-tabs.secondary-tabs', data.pane)
if ($primaryPanel.length > 0) {
$secondaryPanel.append($primaryCollapseIcon);
$primaryCollapseIcon.click(function() {
$primaryPanel.toggleClass('collapsed')
$secondaryPanel.toggleClass('primary-collapsed')
$(window).trigger('oc.updateUi')
if (typeof(localStorage) !== 'undefined')
localStorage.ocCmsPrimaryCollapsed = $primaryPanel.hasClass('collapsed') ? 1 : 0
return false
})
}
if (typeof(localStorage) !== 'undefined') {
if (!$('a', data.tab).hasClass('new-template') && localStorage.ocCmsTablessCollapsed == 1)
$panel.addClass('collapsed')
if (localStorage.ocCmsPrimaryCollapsed == 1) {
$primaryPanel.addClass('collapsed')
$secondaryPanel.addClass('primary-collapsed')
}
}
var $componentListFormGroup = $('.control-componentlist', data.pane).closest('.form-group')
if ($primaryPanel.length > 0)
$primaryPanel.before($componentListFormGroup)
else
$secondaryPanel.parent().before($componentListFormGroup)
$componentListFormGroup.removeClass()
$componentListFormGroup.addClass('layout-row min-size')
this.updateComponentListClass(data.pane)
this.updateFormEditorMode(data.pane, true)
var $form = $('form', data.pane),
self = this
$form.on('changed.oc.changeMonitor', function() {
$panel.trigger('modified.oc.tab')
self.updateModifiedCounter()
})
$form.on('unchanged.oc.changeMonitor', function() {
$panel.trigger('unmodified.oc.tab')
self.updateModifiedCounter()
})
this.addTokenExpanderToEditor(data.pane, $form)
}
CmsPage.prototype.onAfterAllTabsClosed = function(ev) {
var $sidePanel = $('#cms-side-panel')
$sidePanel.find('[data-control=filelist]').fileList('markActive', null)
$sidePanel.find('form').trigger('oc.list.setActiveItem', [null])
}
CmsPage.prototype.onAjaxUpdate = function(ev) {
var dataId = $('#cms-master-tabs .nav-tabs li.active').attr('data-tab-id'),
$sidePanel = $('#cms-side-panel')
$sidePanel.find('[data-control=filelist]').fileList('markActive', dataId)
$sidePanel.find('form').trigger('oc.list.setActiveItem', [dataId])
}
CmsPage.prototype.onAjaxSuccess = function(ev, context, data) {
var element = ev.target
if (data.templatePath !== undefined) {
$('input[name=templatePath]', element).val(data.templatePath)
$('input[name=templateMtime]', element).val(data.templateMtime)
$('[data-control=delete-button]', element).removeClass('hide')
$('[data-control=preview-button]', element).removeClass('hide')
if (data.pageUrl !== undefined)
$('[data-control=preview-button]', element).attr('href', data.pageUrl)
}
if (data.tabTitle !== undefined) {
$('#cms-master-tabs').ocTab('updateTitle', $(element).closest('.tab-pane'), data.tabTitle)
this.setPageTitle(data.tabTitle)
}
var tabId = $('input[name=templateType]', element).val() + '-'
+ $('input[name=theme]', element).val() + '-'
+ $('input[name=templatePath]', element).val();
$('#cms-master-tabs').ocTab('updateIdentifier', $(element).closest('.tab-pane'), tabId)
var templateType = $('input[name=templateType]', element).val()
if (templateType.length > 0) {
$.oc.cmsPage.updateTemplateList(templateType)
if (templateType == 'layout')
this.updateLayouts(element)
}
this.updateFormEditorMode($(element).closest('.tab-pane'), false)
if (context.handler == 'onSave' && (!data['X_OCTOBER_ERROR_FIELDS'] && !data['X_OCTOBER_ERROR_MESSAGE'])) {
$(element).trigger('unchange.oc.changeMonitor')
}
}
CmsPage.prototype.onAjaxError = function(ev, context, message, data, jqXHR) {
if (context.handler == 'onSave') {
if (jqXHR.responseText == 'mtime-mismatch') {
ev.preventDefault()
this.handleMtimeMismatch(ev.target)
}
}
}
CmsPage.prototype.onCreateTemplateClick = function(ev) {
var $form = $(ev.target).closest('[data-template-type]'),
type = $form.data('template-type'),
tabId = type + Math.random(),
self = this
$.oc.stripeLoadIndicator.show()
$form.request('onCreateTemplate', {
data: {type: type}
}).done(function(data) {
$('#cms-master-tabs').ocTab('addTab', data.tabTitle, data.tab, tabId, $form.data('type-icon') + ' new-template')
$('#layout-side-panel').trigger('close.oc.sidePanel')
self.setPageTitle(data.tabTitle)
}).always(function() {
$.oc.stripeLoadIndicator.hide()
})
}
CmsPage.prototype.onDeleteTemplateClick = function(ev) {
var $el = $(ev.currentTarget),
$form = $el.closest('form'),
templateType = $form.data('template-type'),
self = this
if (!confirm($el.data('confirmation')))
return
$.oc.stripeLoadIndicator.show()
$form.request('onDeleteTemplates', {
data: {type: templateType}
}).done(function(data) {
var tabs = $('#cms-master-tabs').data('oc.tab');
$.each(data.deleted, function(index, path){
var
tabId = templateType + '-' + data.theme + '-' + path,
tab = tabs.findByIdentifier(tabId)
$('#cms-master-tabs').ocTab('closeTab', tab, true)
})
if (data.error !== undefined && $.type(data.error) === 'string' && data.error.length)
$.oc.flashMsg({text: data.error, 'class': 'error'})
}).always(function() {
self.updateTemplateList(templateType)
$.oc.stripeLoadIndicator.hide()
})
}
CmsPage.prototype.onInspectorShowing = function(ev, data) {
$(ev.currentTarget).closest('[data-control="toolbar"]').data('oc.dragScroll').goToElement(ev.currentTarget, data.callback)
ev.stopPropagation()
}
CmsPage.prototype.onInspectorHidden = function(ev) {
var element = ev.target,
values = $.parseJSON($('[data-inspector-values]', element).val())
$('[name="component_aliases[]"]', element).val(values['oc.alias'])
$('span.alias', element).text(values['oc.alias'])
}
CmsPage.prototype.onInspectorHiding = function(ev, values) {
var element = ev.target,
values = $.parseJSON($('[data-inspector-values]', element).val()),
alias = values['oc.alias'],
$componentList = $('#cms-master-tabs > div.tab-content > .tab-pane.active .control-componentlist .layout'),
$cell = $(ev.target).parent()
$('div.layout-cell', $componentList).each(function() {
if ($cell.get(0) == this)
return true
var $input = $('input[name="component_aliases[]"]', this)
if ($input.val() == alias) {
ev.preventDefault()
alert('The component alias "'+alias+'" is already used.')
return false
}
})
}
CmsPage.prototype.onComponentRemove = function(ev) {
var element = ev.currentTarget
$(element).trigger('change')
var pane = $(element).closest('.tab-pane'),
component = $(element).closest('div.layout-cell')
/*
* Remove any {% component %} tags in the editor for this component
*/
var editor = $('[data-control=codeeditor]', pane)
if (editor.length) {
var alias = $('input[name="component_aliases[]"]', component).val(),
codeEditor = editor.codeEditor('getEditorObject')
codeEditor.replace('', {
needle: "{% component '" + alias + "' %}"
})
}
component.remove()
$(window).trigger('oc.updateUi')
this.updateComponentListClass(pane)
return false
}
CmsPage.prototype.onComponentClick = function(ev) {
/*
* Determine if a page or layout is open in the master tabs
*/
var $componentList = $('#cms-master-tabs > div.tab-content > .tab-pane.active .control-componentlist .layout')
if ($componentList.length == 0) {
alert('Components can be added only to pages, partials and layouts.')
return;
}
var $component = $(ev.currentTarget).clone(),
$iconInput = $component.find('[data-component-icon]'),
$componentContainer = $('.layout-relative', $component),
$configInput = $component.find('[data-inspector-config]'),
$aliasInput = $component.find('[data-component-default-alias]'),
$valuesInput = $component.find('[data-inspector-values]'),
$nameInput = $component.find('[data-component-name]'),
$classInput = $component.find('[data-inspector-class]'),
alias = $aliasInput.val(),
originalAlias = alias,
counter = 2,
existingAliases = []
$('div.layout-cell input[name="component_aliases[]"]', $componentList).each(function() {
existingAliases.push($(this).val())
})
while($.inArray(alias, existingAliases) !== -1) {
alias = originalAlias + counter
counter++
}
// Set the last alias used so dragComponents can use it
$('input[name="component_aliases[]"]', $(ev.currentTarget)).val(alias)
$component.attr('data-component-attached', true)
$componentContainer.addClass($iconInput.val())
$iconInput.remove()
$componentContainer.attr({
'data-inspectable': '',
'data-inspector-title': $component.find('span.name').text(),
'data-inspector-description': $component.find('span.description').text(),
'data-inspector-config': $configInput.val(),
'data-inspector-class': $classInput.val()
})
$configInput.remove()
$('input[name="component_names[]"]', $component).val($nameInput.val())
$nameInput.remove()
$('input[name="component_aliases[]"]', $component).val(alias)
$component.find('span.alias').text(alias)
$valuesInput.val($valuesInput.val().replace('--alias--', alias))
$aliasInput.remove()
$component.addClass('adding')
$componentList.append($component)
$componentList.closest('[data-control="toolbar"]').data('oc.dragScroll').goToElement($component)
$component.removeClass('adding')
$component.trigger('change')
this.updateComponentListClass($component.closest('.tab-pane'))
$(window).trigger('oc.updateUi')
}
// INTERNAL METHODS
// ============================
CmsPage.prototype.updateComponentListClass = function(pane) {
var $componentList = $('.control-componentlist', pane),
$primaryPanel = $('.control-tabs.primary-tabs', pane),
$primaryTabContainer = $('.nav-tabs', $primaryPanel),
hasComponents = $('.layout', $componentList).children(':not(.hidden)').length > 0
$primaryTabContainer.toggleClass('component-area', hasComponents)
$componentList.toggleClass('has-components', hasComponents)
}
CmsPage.prototype.updateFormEditorMode = function(pane, initialization) {
var $contentTypeElement = $('[data-toolbar-type]', pane)
if ($contentTypeElement.length == 0)
return
if ($contentTypeElement.data('toolbar-type') != 'content')
return
var fileName = $('input[name=fileName]', pane).val(),
parts = fileName.split('.'),
extension = 'txt',
mode = 'plain_text',
modes = { css: "css", htm: "html", html: "html", js: "javascript", less: "less", md: "markdown", sass: "sass", scss: "scss", txt: "plain_text", yaml: "yaml", php: "php" },
editor = $('[data-control=codeeditor]', pane)
if (parts.length >= 2)
extension = parts.pop().toLowerCase()
if (modes[extension] !== undefined)
mode = modes[extension];
var setEditorMode = function() {
window.setTimeout(function() {
editor.data('oc.codeEditor').editor.getSession().setMode({path: 'ace/mode/'+mode})
}, 200)
}
if (initialization)
editor.on('oc.codeEditorReady', setEditorMode)
else
setEditorMode()
}
CmsPage.prototype.updateModifiedCounter = function() {
var counters = {
page: { menu: 'pages', count: 0 },
partial: { menu: 'partials', count: 0 },
layout: { menu: 'layouts', count: 0 },
content: { menu: 'content', count: 0 },
asset: { menu: 'assets', count: 0}
}
$('> div.tab-content > div.tab-pane[data-modified]', '#cms-master-tabs').each(function() {
var inputType = $('> form > input[name=templateType]', this).val()
counters[inputType].count++
})
$.each(counters, function(type, data){
$.oc.sideNav.setCounter('cms/' + data.menu, data.count);
})
}
CmsPage.prototype.addTokenExpanderToEditor = function(pane, $form) {
var group = $('[data-field-name=markup]', pane),
editor = $('[data-control=codeeditor]', group),
canExpand = false,
self = this
if (!editor.length || editor.data('oc.tokenexpander'))
return
var toolbar = editor.codeEditor('getToolbar')
editor.tokenExpander()
var breakButton = $('<li />').prop({ 'class': 'tokenexpander-button' }).append(
$('<a />').prop({ 'href': 'javascript:; '}).append(
$('<i />').prop({ 'class': 'icon-code-fork' })
)
)
breakButton.hide().on('click', function() {
self.handleExpandToken(editor, $form)
return false
})
$('ul:first', toolbar).prepend(breakButton)
editor
.on('show.oc.tokenexpander', function() {
canExpand = true
breakButton.show()
})
.on('hide.oc.tokenexpander', function() {
canExpand = false
breakButton.hide()
})
.on('dblclick', function(ev){
if ((ev.metaKey || ev.ctrlKey) && canExpand) {
self.handleExpandToken(editor, $form)
}
})
}
CmsPage.prototype.handleExpandToken = function(editor, $form) {
editor.tokenExpander('expandToken', function(token, value){
return $form.request('onExpandMarkupToken', {
data: { tokenType: token, tokenName: value }
})
})
}
CmsPage.prototype.handleMtimeMismatch = function(form) {
var $form = $(form)
$form.popup({ handler: 'onOpenConcurrencyResolveForm' })
var popup = $form.data('oc.popup'),
self = this
$(popup.$content).on('click', 'button[data-action=reload]', function() {
popup.hide()
self.reloadForm(form)
})
$(popup.$content).on('click', 'button[data-action=save]', function() {
popup.hide()
$('input[name=templateForceSave]', $form).val(1)
$('a[data-request=onSave]', $form).trigger('click')
$('input[name=templateForceSave]', $form).val(0)
})
}
CmsPage.prototype.reloadForm = function(form) {
var
$form = $(form),
data = {
type: $('[name=templateType]', $form).val(),
theme: $('[name=theme]', $form).val(),
path: $('[name=templatePath]', $form).val(),
},
tabId = data.type + '-' + data.theme + '-' + data.path,
tabs = $('#cms-master-tabs').data('oc.tab'),
tab = tabs.findByIdentifier(tabId),
self = this
/*
* Update tab
*/
$.oc.stripeLoadIndicator.show()
$form.request('onOpenTemplate', {
data: data
}).done(function(data) {
$('#cms-master-tabs').ocTab('updateTab', tab, data.tabTitle, data.tab)
$('#cms-master-tabs').ocTab('unmodifyTab', tab)
self.updateModifiedCounter()
}).always(function() {
$.oc.stripeLoadIndicator.hide()
}).fail(function(jqXHR, textStatus, errorThrown) {
alert(jqXHR.responseText.length ? jqXHR.responseText : jqXHR.statusText)
})
}
CmsPage.prototype.setPageTitle = function(title) {
if (title.length)
$.oc.layout.setPageTitle(title + ' | ')
else
$.oc.layout.setPageTitle(title)
}
CmsPage.prototype.updateLayouts = function(form) {
$(form).request('onGetTemplateList', {
success: function(data) {
$('#cms-master-tabs > .tab-content select[name="settings[layout]"]').each(function() {
var
$select = $(this),
value = $select.val()
$select.find('option').remove()
$.each(data.layouts, function(layoutFile, layoutName){
$select.append($('<option>').attr('value', layoutFile).text(layoutName))
})
$select.trigger('pause.oc.changeMonitor')
$select.val(value)
$select.trigger('change')
$select.trigger('resume.oc.changeMonitor')
})
}
})
}
$.oc.cmsPage = new CmsPage();
}(window.jQuery);

View File

@ -0,0 +1,306 @@
<?php namespace Indikator\DevTools\Classes;
use File;
use Lang;
use Config;
use Request;
use Cms\Classes\Theme;
use Cms\Helpers\File as FileHelper;
use October\Rain\Extension\Extendable;
use ApplicationException;
use ValidationException;
/**
* The CMS asset file class.
*
* @package october\cms
* @author Alexey Bobkov, Samuel Georges
*/
class Asset extends Extendable
{
/**
* @var \Cms\Classes\Theme A reference to the CMS theme containing the object.
*/
protected $theme;
/**
* @var string The plugin folder name.
*/
protected $dirName = 'plugins';
/**
* @var string Specifies the file name corresponding the CMS object.
*/
public $fileName;
/**
* @var string Specifies the file name, the CMS object was loaded from.
*/
protected $originalFileName = null;
/**
* @var string Last modified time.
*/
public $mtime;
/**
* @var string The entire file content.
*/
public $content;
/**
* @var array The attributes that are mass assignable.
*/
protected $fillable = [
'fileName',
'content'
];
/**
* @var array Allowable file extensions.
*/
protected $allowedExtensions = [];
/**
* @var bool Indicates if the model exists.
*/
public $exists = false;
/**
* Creates an instance of the object and associates it with a CMS theme.
* @param \Cms\Classes\Theme $theme Specifies the theme the object belongs to.
*/
public function __construct()
{
$this->allowedExtensions = self::getEditableExtensions();
parent::__construct();
}
/**
* Loads the object from a file.
* This method is used in the CMS back-end. It doesn't use any caching.
* @param \Cms\Classes\Theme $theme Specifies the theme the object belongs to.
* @param string $fileName Specifies the file name, with the extension.
* The file name can contain only alphanumeric symbols, dashes and dots.
* @return mixed Returns a CMS object instance or null if the object wasn't found.
*/
public static function load($theme, $fileName)
{
return (new static($theme))->find($fileName);
}
/**
* Prepares the theme datasource for the model.
* @param \Cms\Classes\Theme $theme Specifies a parent theme.
* @return $this
*/
public static function inTheme($theme)
{
if (is_string($theme)) {
$theme = Theme::load($theme);
}
return new static($theme);
}
/**
* Find a single template by its file name.
*
* @param string $fileName
* @return mixed|static
*/
public function find($fileName)
{
$filePath = $this->getFilePath($fileName);
if (!File::isFile($filePath)) {
return null;
}
if (($content = @File::get($filePath)) === false) {
return null;
}
$this->fileName = $fileName;
$this->originalFileName = $fileName;
$this->mtime = File::lastModified($filePath);
$this->content = $content;
$this->exists = true;
return $this;
}
/**
* Sets the object attributes.
* @param array $attributes A list of attributes to set.
*/
public function fill(array $attributes)
{
foreach ($attributes as $key => $value) {
if (!in_array($key, $this->fillable)) {
throw new ApplicationException(Lang::get(
'cms::lang.cms_object.invalid_property',
['name' => $key]
));
}
$this->$key = $value;
}
}
/**
* Saves the object to the disk.
*/
public function save()
{
$this->validateFileName();
$fullPath = $this->getFilePath();
if (File::isFile($fullPath) && $this->originalFileName !== $this->fileName) {
throw new ApplicationException(Lang::get(
'cms::lang.cms_object.file_already_exists',
['name' => $this->fileName]
));
}
$dirPath = base_path().'/'.$this->dirName;
if (!file_exists($dirPath) || !is_dir($dirPath)) {
if (!File::makeDirectory($dirPath, 0777, true, true)) {
throw new ApplicationException(Lang::get(
'cms::lang.cms_object.error_creating_directory',
['name' => $dirPath]
));
}
}
if (($pos = strpos($this->fileName, '/')) !== false) {
$dirPath = dirname($fullPath);
if (!is_dir($dirPath) && !File::makeDirectory($dirPath, 0777, true, true)) {
throw new ApplicationException(Lang::get(
'cms::lang.cms_object.error_creating_directory',
['name' => $dirPath]
));
}
}
$newFullPath = $fullPath;
if (@File::put($fullPath, $this->content) === false) {
throw new ApplicationException(Lang::get(
'cms::lang.cms_object.error_saving',
['name' => $this->fileName]
));
}
if (strlen($this->originalFileName) && $this->originalFileName !== $this->fileName) {
$fullPath = $this->getFilePath($this->originalFileName);
if (File::isFile($fullPath)) {
@unlink($fullPath);
}
}
clearstatcache();
$this->mtime = @File::lastModified($newFullPath);
$this->originalFileName = $this->fileName;
$this->exists = true;
}
public function delete()
{
$fileName = Request::input('fileName');
$fullPath = $this->getFilePath($fileName);
$this->validateFileName($fileName);
if (File::exists($fullPath)) {
if (!@File::delete($fullPath)) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.error_deleting_file',
['name' => $fileName]
));
}
}
}
/**
* Validate the supplied filename, extension and path.
* @param string $fileName
*/
protected function validateFileName($fileName = null)
{
if ($fileName === null) {
$fileName = $this->fileName;
}
$fileName = trim($fileName);
if (!strlen($fileName)) {
throw new ValidationException(['fileName' =>
Lang::get('cms::lang.cms_object.file_name_required', [
'allowed' => implode(', ', $this->allowedExtensions),
'invalid' => pathinfo($fileName, PATHINFO_EXTENSION)
])
]);
}
if (!FileHelper::validateExtension($fileName, $this->allowedExtensions, false)) {
throw new ValidationException(['fileName' =>
Lang::get('cms::lang.cms_object.invalid_file_extension', [
'allowed' => implode(', ', $this->allowedExtensions),
'invalid' => pathinfo($fileName, PATHINFO_EXTENSION)
])
]);
}
if (!FileHelper::validatePath($fileName, null)) {
throw new ValidationException(['fileName' =>
Lang::get('cms::lang.cms_object.invalid_file', [
'name' => $fileName
])
]);
}
}
/**
* Returns the file name.
* @return string
*/
public function getFileName()
{
return $this->fileName;
}
/**
* Returns the absolute file path.
* @param string $fileName Specifies the file name to return the path to.
* @return string
*/
public function getFilePath($fileName = null)
{
if ($fileName === null) {
$fileName = $this->fileName;
}
return base_path().'/'.$this->dirName.'/'.$fileName;
}
/**
* Returns a list of editable asset extensions.
* The list can be overridden with the cms.editableAssetTypes configuration option.
* @return array
*/
public static function getEditableExtensions()
{
$defaultTypes = ['js', 'jsx', 'css', 'sass', 'scss', 'less', 'php', 'htm', 'html', 'yaml', 'md', 'txt'];
$configTypes = Config::get('cms.editableAssetTypes');
if (!$configTypes) {
return $defaultTypes;
}
return $configTypes;
}
}

View File

@ -0,0 +1,29 @@
# ===================================
# Form Field Definitions
# ===================================
fields:
fileName:
label: cms::lang.editor.filename
attributes:
default-focus: 1
toolbar:
type: partial
path: content_toolbar
cssClass: collapse-visible
tabs:
cssClass: master-area
secondaryTabs:
stretch: true
fields:
content:
tab: cms::lang.editor.content
stretch: true
type: codeeditor

View File

@ -0,0 +1,11 @@
{
"name": "indikator/devtools-plugin",
"type": "october-plugin",
"description": "It is a must-have plugin for you, if you use the October's Docs a lot or you want to use the build-in wysiwyg editor on the Content page.",
"homepage": "https://octobercms.com/plugin/indikator-devtools",
"keywords": ["october", "octobercms"],
"license": "MIT",
"require": {
"composer/installers": "~1.0"
}
}

View File

@ -0,0 +1,380 @@
<?php namespace Indikator\DevTools\Controllers;
use Url;
use Lang;
use Flash;
use Config;
use Request;
use Response;
use Exception;
use BackendMenu;
use Indikator\DevTools\Classes\Asset;
use Indikator\DevTools\Widgets\AssetList;
use Cms\Classes\Router;
use Backend\Classes\Controller;
use Backend\Classes\WidgetManager;
use System\Helpers\DateTime;
use October\Rain\Router\Router as RainRouter;
use ApplicationException;
/**
* CMS index
*
* @package october\cms
* @author Alexey Bobkov, Samuel Georges
*/
class Editor extends Controller
{
use \Backend\Traits\InspectableContainer;
public $requiredPermissions = ['indikator.devtools.editor'];
/**
* Constructor.
*/
public function __construct()
{
parent::__construct();
BackendMenu::setContext('Indikator.DevTools', 'editor', 'assets');
try {
new AssetList($this, 'assetList');
}
catch (Exception $ex) {
$this->handleError($ex);
}
}
//
// Pages
//
public function index()
{
$this->addJs('/plugins/indikator/devtools/assets/october.cmspage.js');
$this->addJs('/modules/cms/assets/js/october.dragcomponents.js', 'core');
$this->addJs('/modules/cms/assets/js/october.tokenexpander.js', 'core');
$this->addCss('/modules/cms/assets/css/october.components.css', 'core');
$this->bodyClass = 'compact-container';
$this->pageTitle = 'cms::lang.cms.menu_label';
$this->pageTitleTemplate = '%s '.trans($this->pageTitle);
if (Request::ajax() && Request::input('formWidgetAlias')) {
$this->bindFormWidgetToController();
}
}
public function index_onOpenTemplate()
{
$type = Request::input('type');
$template = $this->loadTemplate($type, Request::input('path'));
$widget = $this->makeTemplateFormWidget($type, $template);
$this->vars['templatePath'] = Request::input('path');
$this->vars['lastModified'] = DateTime::makeCarbon($template->mtime);
if ($type === 'page') {
$router = new RainRouter;
$this->vars['pageUrl'] = $router->urlFromPattern($template->url);
}
return [
'tabTitle' => $this->getTabTitle($type, $template),
'tab' => $this->makePartial('form_page', [
'form' => $widget,
'templateType' => $type,
'templateMtime' => $template->mtime
])
];
}
public function onSave()
{
$type = Request::input('templateType');
$templatePath = trim(Request::input('templatePath'));
$template = $templatePath ? $this->loadTemplate($type, $templatePath) : $this->createTemplate($type);
$formWidget = $this->makeTemplateFormWidget($type, $template);
$saveData = $formWidget->getSaveData();
$postData = post();
$templateData = [];
$settings = array_get($saveData, 'settings', []) + Request::input('settings', []);
$settings = $this->upgradeSettings($settings);
if ($settings) {
$templateData['settings'] = $settings;
}
$fields = ['markup', 'code', 'fileName', 'content'];
foreach ($fields as $field) {
if (array_key_exists($field, $saveData)) {
$templateData[$field] = $saveData[$field];
}
elseif (array_key_exists($field, $postData)) {
$templateData[$field] = $postData[$field];
}
}
if (!empty($templateData['markup']) && Config::get('cms.convertLineEndings', false) === true) {
$templateData['markup'] = $this->convertLineEndings($templateData['markup']);
}
if (!empty($templateData['code']) && Config::get('cms.convertLineEndings', false) === true) {
$templateData['code'] = $this->convertLineEndings($templateData['code']);
}
if (!Request::input('templateForceSave') && $template->mtime) {
if (Request::input('templateMtime') != $template->mtime) {
throw new ApplicationException('mtime-mismatch');
}
}
$template->attributes = [];
$template->fill($templateData);
$template->save();
/*
* Extensibility
*/
$this->fireSystemEvent('cms.template.save', [$template, $type]);
Flash::success(Lang::get('cms::lang.template.saved'));
$result = [
'templatePath' => $template->fileName,
'templateMtime' => $template->mtime,
'tabTitle' => $this->getTabTitle($type, $template)
];
return $result;
}
public function onOpenConcurrencyResolveForm()
{
return $this->makePartial('concurrency_resolve_form');
}
public function onCreateTemplate()
{
$type = Request::input('type');
$template = $this->createTemplate($type);
if ($type === 'asset') {
$template->fileName = $this->widget->assetList->getCurrentRelativePath();
}
$widget = $this->makeTemplateFormWidget($type, $template);
$this->vars['templatePath'] = '';
return [
'tabTitle' => $this->getTabTitle($type, $template),
'tab' => $this->makePartial('form_page', [
'form' => $widget,
'templateType' => $type,
'templateMtime' => null
])
];
}
public function onDeleteTemplates()
{
$type = Request::input('type');
$templates = Request::input('template');
$error = null;
$deleted = [];
try {
foreach ($templates as $path => $selected) {
if ($selected) {
$this->loadTemplate($type, $path)->delete();
$deleted[] = $path;
}
}
}
catch (Exception $ex) {
$error = $ex->getMessage();
}
/*
* Extensibility
*/
$this->fireSystemEvent('cms.template.delete', [$type]);
return [
'deleted' => $deleted,
'error' => $error,
'theme' => Request::input('theme')
];
}
public function onDelete()
{
$type = Request::input('templateType');
$this->loadTemplate($type, trim(Request::input('templatePath')))->delete();
/*
* Extensibility
*/
$this->fireSystemEvent('cms.template.delete', [$type]);
}
//
// Methods for the internal use
//
protected function resolveTypeClassName($type)
{
$types = [
'asset' => Asset::class
];
if (!array_key_exists($type, $types)) {
throw new ApplicationException(trans('cms::lang.template.invalid_type'));
}
return $types[$type];
}
protected function loadTemplate($type, $path)
{
$class = $this->resolveTypeClassName($type);
if (!($template = call_user_func([$class, 'load'], $this->theme, $path))) {
throw new ApplicationException(trans('cms::lang.template.not_found'));
}
/*
* Extensibility
*/
$this->fireSystemEvent('cms.template.processSettingsAfterLoad', [$template]);
return $template;
}
protected function createTemplate($type)
{
$class = $this->resolveTypeClassName($type);
if (!($template = $class::inTheme($this->theme))) {
throw new ApplicationException(trans('cms::lang.template.not_found'));
}
return $template;
}
protected function getTabTitle($type, $template)
{
if ($type === 'asset') {
$result = in_array($type, ['asset', 'content']) ? $template->getFileName() : $template->getBaseFileName();
if (!$result) {
$result = trans('cms::lang.'.$type.'.new');
}
return $result;
}
return $template->getFileName();
}
protected function makeTemplateFormWidget($type, $template, $alias = null)
{
$formConfigs = [
'asset' => '~/plugins/indikator/devtools/classes/asset/fields.yaml'
];
if (!array_key_exists($type, $formConfigs)) {
throw new ApplicationException(trans('cms::lang.template.not_found'));
}
$widgetConfig = $this->makeConfig($formConfigs[$type]);
$widgetConfig->model = $template;
$widgetConfig->alias = $alias ?: 'form'.studly_case($type).md5($template->getFileName()).uniqid();
$widget = $this->makeWidget('Backend\Widgets\Form', $widgetConfig);
return $widget;
}
protected function upgradeSettings($settings)
{
/*
* Handle component usage
*/
$componentProperties = post('component_properties');
$componentNames = post('component_names');
$componentAliases = post('component_aliases');
if ($componentProperties !== null) {
if ($componentNames === null || $componentAliases === null) {
throw new ApplicationException(trans('cms::lang.component.invalid_request'));
}
$count = count($componentProperties);
if (count($componentNames) != $count || count($componentAliases) != $count) {
throw new ApplicationException(trans('cms::lang.component.invalid_request'));
}
for ($index = 0; $index < $count; $index++) {
$componentName = $componentNames[$index];
$componentAlias = $componentAliases[$index];
$section = $componentName;
if ($componentAlias != $componentName) {
$section .= ' '.$componentAlias;
}
$properties = json_decode($componentProperties[$index], true);
unset($properties['oc.alias']);
unset($properties['inspectorProperty']);
unset($properties['inspectorClassName']);
$settings[$section] = $properties;
}
}
/*
* Handle view bag
*/
$viewBag = post('viewBag');
if ($viewBag !== null) {
$settings['viewBag'] = $viewBag;
}
/*
* Extensibility
*/
$dataHolder = (object) ['settings' => $settings];
$this->fireSystemEvent('cms.template.processSettingsBeforeSave', [$dataHolder]);
return $dataHolder->settings;
}
protected function bindFormWidgetToController()
{
$alias = Request::input('formWidgetAlias');
$type = Request::input('templateType');
$object = $this->loadTemplate($type, Request::input('templatePath'));
$widget = $this->makeTemplateFormWidget($type, $object, $alias);
$widget->bindToController();
}
/**
* Replaces Windows style (/r/n) line endings with unix style (/n)
* line endings.
* @param string $markup The markup to convert to unix style endings
* @return string
*/
protected function convertLineEndings($markup)
{
$markup = str_replace(["\r\n", "\r"], "\n", $markup);
return $markup;
}
}

View File

@ -0,0 +1,29 @@
<?= Form::open(['onsubmit'=>'return false']) ?>
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(trans('backend::lang.form.concurrency_file_changed_title')) ?></h4>
</div>
<div class="modal-body">
<p><?= e(trans('backend::lang.form.concurrency_file_changed_description')) ?></p>
</div>
<div class="modal-footer">
<button
type="submit"
data-action="reload"
class="btn btn-primary">
<?= e(trans('backend::lang.form.reload')) ?>
</button>
<button
type="submit"
data-action="save"
class="btn btn-primary">
<?= e(trans('backend::lang.form.save')) ?>
</button>
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
<?= Form::close() ?>

View File

@ -0,0 +1,27 @@
<div class="form-buttons loading-indicator-container" data-toolbar-type="content">
<a
href="javascript:;"
class="btn btn-primary oc-icon-check save"
data-request="onSave"
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
data-hotkey="ctrl+s, cmd+s">
<?= e(trans('backend::lang.form.save')) ?>
</a>
<button
type="button"
class="btn btn-danger empty oc-icon-trash-o <?php if (!$templatePath): ?>hide<?php endif ?>"
data-request="onDelete"
data-request-confirm="<?= e(trans('cms::lang.content.delete_confirm_single')) ?>"
data-request-success="$(this).trigger('close.oc.tab', [{force: true}])"
data-control="delete-button"></button>
<?php if (isset($lastModified)): ?>
<span
class="btn empty oc-icon-calendar"
title="<?= e(trans('backend::lang.media.last_modified')) ?>: <?= $lastModified ?>"
data-toggle="tooltip"
data-placement="right">
</span>
<?php endif; ?>
</div>

View File

@ -0,0 +1,15 @@
<?= Form::open([
'class' => 'layout',
'data-change-monitor' => 'true',
'data-window-close-confirm' => e(trans('backend::lang.form.confirm_tab_close')),
'data-inspector-external-parameters' => true
]) ?>
<?= $form->render() ?>
<input type="hidden" value="<?= e($form->alias) ?>" name="formWidgetAlias">
<input type="hidden" value="<?= ($templateType) ?>" name="templateType">
<input type="hidden" value="<?= ($templatePath) ?>" name="templatePath">
<input type="hidden" value="<?= ($templateMtime) ?>" name="templateMtime">
<input type="hidden" value="0" name="templateForceSave">
<?= Form::close() ?>

View File

@ -0,0 +1,14 @@
<div class="layout control-scrollpanel" id="cms-side-panel">
<div class="layout-cell">
<div class="layout-relative fix-button-container">
<form
class="layout"
data-content-id="assets"
data-template-type="asset"
data-type-icon="oc-icon-cubes"
onsubmit="return false">
<?= $this->widget->assetList->render() ?>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,30 @@
<?= Block::put('sidepanel') ?>
<?php if (!$this->fatalError): ?>
<?= $this->makePartial('sidepanel') ?>
<?php endif ?>
<?= Block::endPut() ?>
<?= Block::put('body') ?>
<?php if (!$this->fatalError): ?>
<div
data-control="tab"
data-closable
data-close-confirmation="<?= e(trans('backend::lang.form.confirm_tab_close')) ?>"
data-pane-classes="layout-cell"
data-max-title-symbols="15"
data-title-as-file-names="true"
class="layout control-tabs master-tabs fancy-layout oc-logo-transparent"
id="cms-master-tabs">
<div class="layout-row min-size">
<div class="tabs-container">
<ul class="nav nav-tabs"></ul>
</div>
</div>
<div class="tab-content layout-row"></div>
</div>
<?php else: ?>
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
<?php endif ?>
<?= Block::endPut() ?>

View File

@ -0,0 +1,30 @@
<?php namespace Indikator\DevTools\FormWidgets;
use Backend\Classes\FormField;
use Backend\Classes\FormWidgetBase;
class Help extends FormWidgetBase
{
protected $defaultAlias = 'help';
public $content = 'cms';
public function init()
{
$this->fillFromConfig([
'content'
]);
}
public function render()
{
$this->prepareVars();
return $this->makePartial('help');
}
protected function prepareVars()
{
$this->vars['content'] = $this->content;
}
}

View File

@ -0,0 +1,88 @@
<?php if ($content == 'cms'): ?>
<div class="row" style="padding:24px;">
<div class="col-md-4 col-sm-4 col-xs-6">
<strong><?= e(trans('indikator.devtools::lang.help.cms')) ?></strong><br>
<a href="https://octobercms.com/docs/cms/pages#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.pages')) ?></a><br>
<a href="https://octobercms.com/docs/cms/partials#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.partials')) ?></a><br>
<a href="https://octobercms.com/docs/cms/layouts#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.layouts')) ?></a><br>
<a href="https://octobercms.com/docs/cms/content#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.content')) ?></a><br>
<a href="https://octobercms.com/docs/cms/ajax#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.ajax')) ?></a><br>
<br>
<strong><?= e(trans('indikator.devtools::lang.help.functions')) ?></strong><br>
<a href="https://octobercms.com/docs/markup/function-str#search-input" target="_blank">str()</a><br>
<a href="https://octobercms.com/docs/markup/function-form#search-input" target="_blank">form()</a><br>
<a href="https://octobercms.com/docs/markup/function-html#search-input" target="_blank">html()</a><br>
<a href="https://octobercms.com/docs/markup/function-dump#search-input" target="_blank">dump()</a>
</div>
<div class="col-md-4 col-sm-4 col-xs-6">
<strong><?= e(trans('indikator.devtools::lang.help.tags')) ?></strong><br>
<a href="https://octobercms.com/docs/markup/tag-partial#search-input" target="_blank">{% partial %}</a><br>
<a href="https://octobercms.com/docs/markup/tag-content#search-input" target="_blank">{% content %}</a><br>
<a href="https://octobercms.com/docs/markup/tag-component#search-input" target="_blank">{% component %}</a><br>
<a href="https://octobercms.com/docs/markup/tag-placeholder#search-input" target="_blank">{% placeholder %}</a><br>
<a href="https://octobercms.com/docs/markup/tag-scripts#search-input" target="_blank">{% scripts %}</a><br>
<a href="https://octobercms.com/docs/markup/tag-styles#search-input" target="_blank">{% styles %}</a><br>
<a href="https://octobercms.com/docs/markup/tag-flash#search-input" target="_blank">{% flash %}</a><br>
<a href="https://octobercms.com/docs/markup/tag-verbatim#search-input" target="_blank">{% verbatim %}</a><br>
<a href="https://octobercms.com/docs/markup/tag-for#search-input" target="_blank">{% for %}</a><br>
<a href="https://octobercms.com/docs/markup/tag-if#search-input" target="_blank">{% if %}</a>
</div>
<div class="col-md-4 col-sm-4 col-xs-6">
<strong><?= e(trans('indikator.devtools::lang.help.filters')) ?></strong><br>
<a href="https://octobercms.com/docs/markup/filter-app#search-input" target="_blank">|app</a><br>
<a href="https://octobercms.com/docs/markup/filter-page#search-input" target="_blank">|page</a><br>
<a href="https://octobercms.com/docs/markup/filter-theme#search-input" target="_blank">|theme</a><br>
<a href="https://octobercms.com/docs/markup/filter-media#search-input" target="_blank">|media</a><br>
<a href="https://octobercms.com/docs/markup/filter-md#search-input" target="_blank">|md</a><br>
<a href="https://octobercms.com/docs/markup/filter-raw#search-input" target="_blank">|raw</a><br>
<a href="https://octobercms.com/docs/markup/filter-default#search-input" target="_blank">|default</a><br>
<br>
<strong><?= e(trans('indikator.devtools::lang.help.database')) ?></strong><br>
<a href="https://octobercms.com/docs/database/basics#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.basic')) ?></a><br>
<a href="https://octobercms.com/docs/database/query#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.queries')) ?></a>
</div>
</div>
<?php else: ?>
<div class="row" style="padding:24px;">
<div class="col-md-4 col-sm-4 col-xs-6">
<strong><?= e(trans('indikator.devtools::lang.help.plugins')) ?></strong><br>
<a href="https://octobercms.com/docs/plugin/registration#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.registration')) ?></a><br>
<a href="https://octobercms.com/docs/plugin/updates#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.version_history')) ?></a><br>
<a href="https://octobercms.com/docs/plugin/components#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.building_components')) ?></a><br>
<a href="https://octobercms.com/docs/plugin/settings#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.settings_config')) ?></a><br>
<a href="https://octobercms.com/docs/plugin/localization#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.localization')) ?></a><br>
<a href="https://octobercms.com/docs/plugin/scheduling#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.task_scheduling')) ?></a><br>
<a href="https://octobercms.com/docs/plugin/extending#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.extending_plugins')) ?></a>
</div>
<div class="col-md-4 col-sm-4 col-xs-6">
<strong><?= e(trans('indikator.devtools::lang.help.backend')) ?></strong><br>
<a href="https://octobercms.com/docs/backend/controllers-ajax#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.controllers_ajax')) ?></a><br>
<a href="https://octobercms.com/docs/backend/views-partials#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.views_partials')) ?></a><br>
<a href="https://octobercms.com/docs/backend/widgets#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.widgets')) ?></a><br>
<a href="https://octobercms.com/docs/backend/forms#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.forms')) ?></a><br>
<a href="https://octobercms.com/docs/backend/lists#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.lists')) ?></a><br>
<a href="https://octobercms.com/docs/backend/relations#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.relations')) ?></a><br>
<a href="https://octobercms.com/docs/backend/reorder#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.sorting_records')) ?></a><br>
<a href="https://octobercms.com/docs/backend/import-export#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.importing_exporting')) ?></a><br>
<a href="https://octobercms.com/docs/backend/users#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.users_permissions')) ?></a><br>
<a href="https://octobercms.com/docs/ui#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.user_interface_guide')) ?></a>
</div>
<div class="col-md-4 col-sm-4 col-xs-6">
<strong><?= e(trans('indikator.devtools::lang.help.services')) ?></strong><br>
<a href="https://octobercms.com/docs/services/application#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.application')) ?></a><br>
<a href="https://octobercms.com/docs/services/behaviors#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.behaviors')) ?></a><br>
<a href="https://octobercms.com/docs/services/events#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.events')) ?></a><br>
<a href="https://octobercms.com/docs/services/html#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.forms_html')) ?></a><br>
<a href="https://octobercms.com/docs/services/mail#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.mail')) ?></a><br>
<a href="https://octobercms.com/docs/services/request-input#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.request_input')) ?></a><br>
<a href="https://octobercms.com/docs/services/response-view#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.response_view')) ?></a><br>
<a href="https://octobercms.com/docs/services/router#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.router')) ?></a><br>
<a href="https://octobercms.com/docs/services/session#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.session')) ?></a><br>
<a href="https://octobercms.com/docs/services/validation#search-input" target="_blank"><?= e(trans('indikator.devtools::lang.help.validation')) ?></a>
</div>
</div>
<?php endif; ?>

View File

@ -0,0 +1,72 @@
<?php
return [
'plugin' => [
'name' => 'Developer Tools',
'description' => 'Useful features for developers.',
'author' => 'Gergő Szabó'
],
'editor' => [
'menu_label' => 'Code editor',
'plugins' => 'Plugins',
'permission' => 'Use the code editor'
],
'help' => [
'menu_label' => 'Developer Tools',
'menu_description' => 'Settings of developer features.',
'tab' => 'Help',
'cms' => 'CMS',
'pages' => 'Pages',
'partials' => 'Partials',
'layouts' => 'Layouts',
'content' => 'Content',
'ajax' => 'AJAX',
'functions' => 'Functions',
'tags' => 'Tags',
'filters' => 'Filters',
'database' => 'Database',
'basic' => 'Basic',
'queries' => 'Queries',
'plugins' => 'Plugins',
'registration' => 'Registration',
'version_history' => 'Version history',
'building_components' => 'Building components',
'settings_config' => 'Settings & Config',
'localization' => 'Localization',
'task_scheduling' => 'Task scheduling',
'extending_plugins' => 'Extending plugins',
'backend' => 'Backend',
'controllers_ajax' => 'Controllers & AJAX',
'views_partials' => 'Views & Partials',
'widgets' => 'Widgets',
'forms' => 'Forms',
'lists' => 'Lists',
'relations' => 'Relations',
'sorting_records' => 'Sorting records',
'importing_exporting' => 'Importing & Exporting',
'users_permissions' => 'Users & Permissions',
'user_interface_guide' => 'User interface guide',
'services' => 'Services',
'application' => 'Application',
'behaviors' => 'Behaviors',
'events' => 'Events',
'forms_html' => 'Forms & Html',
'mail' => 'Mail',
'request_input' => 'Request & Input',
'response_view' => 'Response & View',
'router' => 'Router',
'session' => 'Session',
'validation' => 'Validation',
'permission' => 'Manage settings'
],
'form' => [
'wysiwyg_label' => 'Wysiwyg Editor',
'wysiwyg_enabled' => 'Enabled on the Content page',
'help_label' => 'Help',
'help_enabled' => 'Enable the Help tab on the CMS pages',
'select_none' => '-- None --',
'select_superuser' => 'Show it for the Superusers',
'select_admingroup' => 'Show it for the following admin group',
'select_adminid' => 'Show it for the following administrator',
]
];

View File

@ -0,0 +1,72 @@
<?php
return [
'plugin' => [
'name' => 'Fejlesztői eszközök',
'description' => 'Hasznos szolgáltatások fejlesztőknek.',
'author' => 'Szabó Gergő'
],
'editor' => [
'menu_label' => 'Kódszerkesztő',
'plugins' => 'Bővítmények',
'permission' => 'Szerkesztő használata'
],
'help' => [
'menu_label' => 'Fejlesztőknek',
'menu_description' => 'Szolgáltatások és lehetőségek beállítása.',
'tab' => 'Súgó',
'cms' => 'Testreszabás',
'pages' => 'Lapok',
'partials' => 'Részlapok',
'layouts' => 'Elrendezések',
'content' => 'Tartalom',
'ajax' => 'AJAX',
'functions' => 'Függvények',
'tags' => 'Tag-ek',
'filters' => 'Filterek',
'database' => 'Adatbázis',
'basic' => 'Alapok',
'queries' => 'Lekérdezések',
'plugins' => 'Bővítmények',
'registration' => 'Regisztrálás',
'version_history' => 'Verzió előzmények',
'building_components' => 'Komponensek',
'settings_config' => 'Beállítások',
'localization' => 'Többnyelvűsítés',
'task_scheduling' => 'Időzítés',
'extending_plugins' => 'Kiegészítés',
'backend' => 'Admin',
'controllers_ajax' => 'Kontroller és AJAX',
'views_partials' => 'Részlapok',
'widgets' => 'Widgetek',
'forms' => 'Űrlapok',
'lists' => 'Listák',
'relations' => 'Kapcsolatok',
'sorting_records' => 'Elemek rendezése',
'importing_exporting' => 'Import és export',
'users_permissions' => 'Jogosultságok',
'user_interface_guide' => 'Felhasználói felület',
'services' => 'Szolgáltatások',
'application' => 'Alkalmazás',
'behaviors' => 'Viselkedések',
'events' => 'Események',
'forms_html' => 'Űrlapok és HTML',
'mail' => 'Levelezés',
'request_input' => 'Űrlap értékek',
'response_view' => 'Válasz megjelenítés',
'router' => 'Router kezelés',
'session' => 'Munkamenet',
'validation' => 'Ellenőrzés',
'permission' => 'Beállítások kezelése'
],
'form' => [
'wysiwyg_label' => 'Szövegszerkesztő',
'wysiwyg_enabled' => 'Engedélyezés a Tartalom oldalon',
'help_label' => 'Súgó',
'help_enabled' => 'Engedélyezés a Testreszabás aloldalakon',
'select_none' => '-- nincs --',
'select_superuser' => 'Szuperadminok láthatják',
'select_admingroup' => 'A következő admin csoport tagjai láthatják',
'select_adminid' => 'A következő admin felhasználó láthatja'
]
];

View File

@ -0,0 +1,63 @@
<?php namespace Indikator\DevTools\Models;
use Model;
use Db;
class Settings extends Model
{
public $implement = ['System.Behaviors.SettingsModel'];
public $settingsCode = 'indikator_devtools_settings';
public $settingsFields = 'fields.yaml';
public $selectList = [0 => 'indikator.devtools::lang.form.select_none'];
public function getHelpAdmingroupOptions()
{
$result = $this->selectList;
$sql = Db::table('backend_user_groups')->orderBy('name', 'asc')->get()->all();
foreach ($sql as $item) {
$result[$item->id] = $item->name.' ('.Db::table('backend_users_groups')->where('user_group_id', $item->id)->count().')';
}
return $result;
}
public function getHelpAdminidOptions()
{
$result = $this->selectList;
$sql = Db::table('backend_users')->orderBy('login', 'asc')->get()->all();
foreach ($sql as $item) {
$result[$item->id] = $item->login.' ('.$item->email.')';
}
return $result;
}
public function getWysiwygAdmingroupOptions()
{
$result = $this->selectList;
$sql = Db::table('backend_user_groups')->orderBy('name', 'asc')->get()->all();
foreach ($sql as $item) {
$result[$item->id] = $item->name.' ('.Db::table('backend_users_groups')->where('user_group_id', $item->id)->count().')';
}
return $result;
}
public function getWysiwygAdminidOptions()
{
$result = $this->selectList;
$sql = Db::table('backend_users')->orderBy('login', 'asc')->get()->all();
foreach ($sql as $item) {
$result[$item->id] = $item->login.' ('.$item->email.')';
}
return $result;
}
}

View File

@ -0,0 +1,85 @@
# ===================================
# Field Definitions
# ===================================
fields:
help_title:
label: indikator.devtools::lang.form.help_label
type: section
comment: ''
span: left
wysiwyg_title:
label: indikator.devtools::lang.form.wysiwyg_label
type: section
comment: ''
span: right
help_enabled:
label: indikator.devtools::lang.form.help_enabled
type: switch
default: false
span: left
wysiwyg_enabled:
label: indikator.devtools::lang.form.wysiwyg_enabled
type: switch
default: false
span: right
help_superuser:
label: indikator.devtools::lang.form.select_superuser
type: switch
default: false
trigger:
action: show
field: help_enabled
condition: checked
span: left
wysiwyg_superuser:
label: indikator.devtools::lang.form.select_superuser
type: switch
default: false
trigger:
action: show
field: wysiwyg_enabled
condition: checked
span: right
help_admingroup:
label: indikator.devtools::lang.form.select_admingroup
type: dropdown
trigger:
action: show
field: help_enabled
condition: checked
span: left
wysiwyg_admingroup:
label: indikator.devtools::lang.form.select_admingroup
type: dropdown
trigger:
action: show
field: wysiwyg_enabled
condition: checked
span: right
help_adminid:
label: indikator.devtools::lang.form.select_adminid
type: dropdown
trigger:
action: show
field: help_enabled
condition: checked
span: left
wysiwyg_adminid:
label: indikator.devtools::lang.form.select_adminid
type: dropdown
trigger:
action: show
field: wysiwyg_enabled
condition: checked
span: right

View File

@ -0,0 +1,10 @@
<?php
Route::get('/phpinfo', function()
{
$admin = BackendAuth::getUser();
if (isset($admin) && $admin->is_superuser == 1) {
echo phpinfo();
}
});

View File

@ -0,0 +1,17 @@
1.0.0: First version of Developer Tools.
1.1.0: Edit plugins with the code editor.
1.1.1: Translate some English texts.
1.1.2: Fixed the Create file issue.
1.1.3: Added new icon for main navigation.
1.1.4: Show the PHP's configuration.
1.1.5: Minor code improvements and bugfix.
1.1.6: The top menu icon shows again.
1.1.7: Fixed the Create folder issue.
1.1.8: "!!! Updated for October 420+."
1.1.9:
- Updated the main navigation icon.
- Added last modified date.
1.2.0: The syntax highlighting works again!
1.2.1: Help links open in a new window.
1.2.2: Fixed the dependency bug in asset list.
1.2.3: The file delete operation works again.

View File

@ -0,0 +1,709 @@
<?php namespace Indikator\DevTools\Widgets;
use Str;
use Url;
use File;
use Lang;
use Input;
use Config;
use Request;
use Response;
use Validator;
use Cms\Classes\Theme;
use Indikator\DevTools\Classes\Asset;
use Backend\Classes\WidgetBase;
use System\Classes\PluginManager;
use ApplicationException;
use ValidationException;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser;
use October\Rain\Filesystem\Definitions as FileDefinitions;
use RecursiveIteratorIterator;
use RecursiveDirectoryIterator;
use DirectoryIterator;
use Exception;
/**
* CMS asset list widget.
*
* @package october\cms
* @author Alexey Bobkov, Samuel Georges
*/
class AssetList extends WidgetBase
{
use \Backend\Traits\SelectableWidget;
protected $searchTerm = false;
protected $theme;
/**
* @var string Message to display when there are no records in the list.
*/
public $noRecordsMessage = 'cms::lang.asset.no_list_records';
/**
* @var string Message to display when the Delete button is clicked.
*/
public $deleteConfirmation = 'cms::lang.asset.delete_confirm';
/**
* @var array Valid asset file extensions
*/
protected $assetExtensions;
public function __construct($controller, $alias)
{
$this->alias = $alias;
$this->theme = Theme::getEditTheme();
$this->selectionInputName = 'file';
$this->assetExtensions = FileDefinitions::get('assetExtensions');
parent::__construct($controller, []);
$this->bindToController();
$this->checkUploadPostback();
}
/**
* @inheritDoc
*/
protected function loadAssets()
{
$this->addCss('css/assetlist.css');
$this->addJs('js/assetlist.js');
}
/**
* Renders the widget.
* @return string
*/
public function render()
{
return $this->makePartial('body', [
'data' => $this->getData()
]);
}
//
// Event handlers
//
public function onOpenDirectory()
{
$path = Input::get('path');
if (!$this->validatePath($path)) {
throw new ApplicationException(Lang::get('cms::lang.asset.invalid_path'));
}
$delay = Input::get('delay');
if ($delay) {
usleep(1000000 * $delay);
}
$this->putSession('currentPath', $path);
return [
'#'.$this->getId('asset-list') => $this->makePartial('items', ['items' => $this->getData()])
];
}
public function onRefresh()
{
return [
'#'.$this->getId('asset-list') => $this->makePartial('items', ['items' => $this->getData()])
];
}
public function onUpdate()
{
$this->extendSelection();
return $this->onRefresh();
}
public function onDeleteFiles()
{
$fileList = Request::input('file');
$error = null;
$deleted = [];
try {
$assetsPath = $this->getAssetsPath();
foreach ($fileList as $path => $selected) {
if ($selected) {
if (!$this->validatePath($path)) {
throw new ApplicationException(Lang::get('cms::lang.asset.invalid_path'));
}
$fullPath = $assetsPath.'/'.$path;
if (File::exists($fullPath)) {
if (!File::isDirectory($fullPath)) {
if (!@File::delete($fullPath)) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.error_deleting_file',
['name' => $path]
));
}
}
else {
$empty = File::isDirectoryEmpty($fullPath);
if ($empty === false) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.error_deleting_dir_not_empty',
['name' => $path]
));
}
if (!@rmdir($fullPath)) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.error_deleting_dir',
['name' => $path]
));
}
}
$deleted[] = $path;
$this->removeSelection($path);
}
}
}
}
catch (Exception $ex) {
$error = $ex->getMessage();
}
return [
'deleted' => $deleted,
'error' => $error
];
}
public function onLoadRenamePopup()
{
$path = Input::get('renamePath');
if (!$this->validatePath($path)) {
throw new ApplicationException(Lang::get('cms::lang.asset.invalid_path'));
}
$this->vars['originalPath'] = $path;
$this->vars['name'] = basename($path);
return $this->makePartial('rename_form');
}
public function onApplyName()
{
$newName = trim(Input::get('name'));
if (!strlen($newName)) {
throw new ApplicationException(Lang::get('cms::lang.asset.name_cant_be_empty'));
}
if (!$this->validatePath($newName)) {
throw new ApplicationException(Lang::get('cms::lang.asset.invalid_path'));
}
if (!$this->validateName($newName)) {
throw new ApplicationException(Lang::get('cms::lang.asset.invalid_name'));
}
$originalPath = Input::get('originalPath');
if (!$this->validatePath($originalPath)) {
throw new ApplicationException(Lang::get('cms::lang.asset.invalid_path'));
}
$originalFullPath = $this->getFullPath($originalPath);
if (!file_exists($originalFullPath)) {
throw new ApplicationException(Lang::get('cms::lang.asset.original_not_found'));
}
if (!is_dir($originalFullPath) && !$this->validateFileType($newName)) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.type_not_allowed',
['allowed_types' => implode(', ', $this->assetExtensions)]
));
}
$newFullPath = $this->getFullPath(dirname($originalPath).'/'.$newName);
if (file_exists($newFullPath) && $newFullPath !== $originalFullPath) {
throw new ApplicationException(Lang::get('cms::lang.asset.already_exists'));
}
if (!@rename($originalFullPath, $newFullPath)) {
throw new ApplicationException(Lang::get('cms::lang.asset.error_renaming'));
}
return [
'#'.$this->getId('asset-list') => $this->makePartial('items', ['items' => $this->getData()])
];
}
public function onLoadNewDirPopup()
{
return $this->makePartial('new_dir_form');
}
public function onNewDirectory()
{
$newName = trim(Input::get('name'));
if (!strlen($newName)) {
throw new ApplicationException(Lang::get('cms::lang.asset.name_cant_be_empty'));
}
if (!$this->validatePath($newName)) {
throw new ApplicationException(Lang::get('cms::lang.asset.invalid_path'));
}
if (!$this->validateName($newName)) {
throw new ApplicationException(Lang::get('cms::lang.asset.invalid_name'));
}
$newFullPath = $this->getCurrentPath().'/'.$newName;
if (file_exists($newFullPath)) {
throw new ApplicationException(Lang::get('cms::lang.asset.already_exists'));
}
if (!File::makeDirectory($newFullPath)) {
throw new ApplicationException(Lang::get(
'cms::lang.cms_object.error_creating_directory',
['name' => $newName]
));
}
return [
'#'.$this->getId('asset-list') => $this->makePartial('items', ['items' => $this->getData()])
];
}
public function onLoadMovePopup()
{
$fileList = Request::input('file');
$directories = [];
$selectedList = array_filter($fileList, function ($value) {
return $value == 1;
});
$this->listDestinationDirectories($directories, $selectedList);
$this->vars['directories'] = $directories;
$this->vars['selectedList'] = base64_encode(json_encode(array_keys($selectedList)));
return $this->makePartial('move_form');
}
public function onMove()
{
$selectedList = Input::get('selectedList');
if (!strlen($selectedList)) {
throw new ApplicationException(Lang::get('cms::lang.asset.selected_files_not_found'));
}
$destinationDir = Input::get('dest');
if (!strlen($destinationDir)) {
throw new ApplicationException(Lang::get('cms::lang.asset.select_destination_dir'));
}
$destinationFullPath = $this->getFullPath($destinationDir);
if (!file_exists($destinationFullPath) || !is_dir($destinationFullPath)) {
throw new ApplicationException(Lang::get('cms::lang.asset.destination_not_found'));
}
$list = @json_decode(@base64_decode($selectedList));
if ($list === false) {
throw new ApplicationException(Lang::get('cms::lang.asset.selected_files_not_found'));
}
foreach ($list as $path) {
if (!$this->validatePath($path)) {
throw new ApplicationException(Lang::get('cms::lang.asset.invalid_path'));
}
$basename = basename($path);
$originalFullPath = $this->getFullPath($path);
$newFullPath = rtrim($destinationFullPath, '/').'/'.$basename;
$safeDir = $this->getAssetsPath();
if ($originalFullPath == $newFullPath) {
continue;
}
if (is_file($originalFullPath)) {
if (!@File::move($originalFullPath, $newFullPath)) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.error_moving_file',
['file' => $basename]
));
}
}
elseif (is_dir($originalFullPath)) {
if (!@File::copyDirectory($originalFullPath, $newFullPath)) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.error_moving_directory',
['dir' => $basename]
));
}
if (strpos($originalFullPath, '../') !== false) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.error_deleting_directory',
['dir' => $basename]
));
}
if (strpos($originalFullPath, $safeDir) !== 0) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.error_deleting_directory',
['dir' => $basename]
));
}
if (!@File::deleteDirectory($originalFullPath)) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.error_deleting_directory',
['dir' => $basename]
));
}
}
}
return [
'#'.$this->getId('asset-list') => $this->makePartial('items', ['items' => $this->getData()])
];
}
public function onSearch()
{
$this->setSearchTerm(Input::get('search'));
$this->extendSelection();
return $this->onRefresh();
}
/*
* Methods for the internal use
*/
protected function getData()
{
$assetsPath = $this->getAssetsPath();
if (!file_exists($assetsPath) || !is_dir($assetsPath)) {
if (!File::makeDirectory($assetsPath)) {
throw new ApplicationException(Lang::get(
'cms::lang.cms_object.error_creating_directory',
['name' => $assetsPath]
));
}
}
$searchTerm = Str::lower($this->getSearchTerm());
if (!strlen($searchTerm)) {
$currentPath = $this->getCurrentPath();
return $this->getDirectoryContents(
new DirectoryIterator($currentPath)
);
}
return $this->findFiles();
}
protected function getAssetsPath()
{
return base_path().'/plugins';
}
protected function getThemeFileUrl($path)
{
return Url::to('plugins'.$path);
}
public function getCurrentRelativePath()
{
$path = $this->getSession('currentPath', '/');
if (!$this->validatePath($path)) {
return null;
}
if ($path == '.') {
return null;
}
return ltrim($path, '/');
}
protected function getCurrentPath()
{
$assetsPath = $this->getAssetsPath();
$path = $assetsPath.'/'.$this->getCurrentRelativePath();
if (!is_dir($path)) {
return $assetsPath;
}
return $path;
}
protected function getRelativePath($path)
{
$prefix = $this->getAssetsPath();
if (substr($path, 0, strlen($prefix)) == $prefix) {
$path = substr($path, strlen($prefix));
}
return $path;
}
protected function getFullPath($path)
{
return $this->getAssetsPath().'/'.ltrim($path, '/');
}
protected function validatePath($path)
{
if (!preg_match('/^[0-9a-z\.\s_\-\/]+$/i', $path)) {
return false;
}
if (strpos($path, '..') !== false || strpos($path, './') !== false) {
return false;
}
return true;
}
protected function validateName($name)
{
if (!preg_match('/^[0-9a-z\.\s_\-]+$/i', $name)) {
return false;
}
if (strpos($name, '..') !== false) {
return false;
}
return true;
}
protected function getDirectoryContents($dir)
{
$editableAssetTypes = Asset::getEditableExtensions();
$result = $files = [];
foreach ($dir as $node) {
if (substr($node->getFileName(), 0, 1) == '.') {
continue;
}
if ($node->isDir() && !$node->isDot()) {
$result[$node->getFilename()] = (object)[
'type' => 'directory',
'path' => File::normalizePath($this->getRelativePath($node->getPathname())),
'name' => $node->getFilename(),
'editable' => false
];
}
elseif ($node->isFile()) {
$files[] = (object)[
'type' => 'file',
'path' => File::normalizePath($this->getRelativePath($node->getPathname())),
'name' => $node->getFilename(),
'editable' => in_array(strtolower($node->getExtension()), $editableAssetTypes)
];
}
}
foreach ($files as $file) {
$result[] = $file;
}
return $result;
}
protected function listDestinationDirectories(&$result, $excludeList, $startDir = null, $level = 0)
{
if ($startDir === null) {
$startDir = $this->getAssetsPath();
$result['/'] = 'assets';
$level = 1;
}
$dirs = new DirectoryIterator($startDir);
foreach ($dirs as $node) {
if (substr($node->getFileName(), 0, 1) == '.') {
continue;
}
if ($node->isDir() && !$node->isDot()) {
$fullPath = $node->getPathname();
$relativePath = $this->getRelativePath($fullPath);
if (array_key_exists($relativePath, $excludeList)) {
continue;
}
$result[$relativePath] = str_repeat('&nbsp;', $level * 4).$node->getFilename();
$this->listDestinationDirectories($result, $excludeList, $fullPath, $level+1);
}
}
}
protected function getSearchTerm()
{
return $this->searchTerm !== false ? $this->searchTerm : $this->getSession('search');
}
protected function isSearchMode()
{
return strlen($this->getSearchTerm());
}
protected function getUpPath()
{
$path = $this->getCurrentRelativePath();
if (!strlen(rtrim(ltrim($path, '/'), '/'))) {
return null;
}
return dirname($path);
}
/**
* Check for valid asset file extension
* @param string
* @return bool
*/
protected function validateFileType($name)
{
$extension = strtolower(File::extension($name));
if (!in_array($extension, $this->assetExtensions)) {
return false;
}
return true;
}
/**
* Checks the current request to see if it is a postback containing a file upload
* for this particular widget.
*/
protected function checkUploadPostback()
{
$fileName = null;
try {
$uploadedFile = Input::file('file_data');
if (!is_object($uploadedFile)) {
return;
}
$fileName = $uploadedFile->getClientOriginalName();
/*
* Check valid upload
*/
if (!$uploadedFile->isValid()) {
throw new ApplicationException(Lang::get('cms::lang.asset.file_not_valid'));
}
/*
* Check file size
*/
$maxSize = UploadedFile::getMaxFilesize();
if ($uploadedFile->getSize() > $maxSize) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.too_large',
['max_size' => File::sizeToString($maxSize)]
));
}
/*
* Check for valid file extensions
*/
if (!$this->validateFileType($fileName)) {
throw new ApplicationException(Lang::get(
'cms::lang.asset.type_not_allowed',
['allowed_types' => implode(', ', $this->assetExtensions)]
));
}
/*
* Accept the uploaded file
*/
$uploadedFile->move($this->getCurrentPath(), $uploadedFile->getClientOriginalName());
die('success');
}
catch (Exception $ex) {
$message = $fileName !== null
? Lang::get('cms::lang.asset.error_uploading_file', ['name' => $fileName, 'error' => $ex->getMessage()])
: $ex->getMessage();
die($message);
}
}
protected function setSearchTerm($term)
{
$this->searchTerm = trim($term);
$this->putSession('search', $this->searchTerm);
}
protected function findFiles()
{
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($this->getAssetsPath(), RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST,
RecursiveIteratorIterator::CATCH_GET_CHILD
);
$editableAssetTypes = Asset::getEditableExtensions();
$searchTerm = Str::lower($this->getSearchTerm());
$words = explode(' ', $searchTerm);
$result = [];
foreach ($iterator as $item) {
if (!$item->isDir()) {
if (substr($item->getFileName(), 0, 1) == '.') {
continue;
}
$path = $this->getRelativePath($item->getPathname());
if ($this->pathMatchesSearch($words, $path)) {
$result[] = (object)[
'type' => 'file',
'path' => File::normalizePath($path),
'name' => $item->getFilename(),
'editable' => in_array(strtolower($item->getExtension()), $editableAssetTypes)
];
}
}
}
return $result;
}
protected function pathMatchesSearch(&$words, $path)
{
foreach ($words as $word) {
$word = trim($word);
if (!strlen($word)) {
continue;
}
if (!Str::contains(Str::lower($path), $word)) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,244 @@
.control-assetlist p.no-data {
padding: 22px;
margin: 0;
color: #666666;
font-size: 14px;
text-align: center;
font-weight: 400;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
.control-assetlist p.parent,
.control-assetlist ul li {
font-weight: 300;
line-height: 150%;
margin-bottom: 0;
}
.control-assetlist p.parent.active a,
.control-assetlist ul li.active a {
background: #dddddd;
position: relative;
}
.control-assetlist p.parent.active a:after,
.control-assetlist ul li.active a:after {
position: absolute;
height: 100%;
width: 4px;
left: 0;
top: 0;
background: #e67e22;
display: block;
content: ' ';
}
.control-assetlist p.parent a.link,
.control-assetlist ul li a.link {
display: block;
position: relative;
word-wrap: break-word;
padding: 10px 50px 10px 20px;
outline: none;
font-weight: 400;
color: #405261;
font-size: 14px;
}
.control-assetlist p.parent a.link:hover,
.control-assetlist ul li a.link:hover,
.control-assetlist p.parent a.link:focus,
.control-assetlist ul li a.link:focus,
.control-assetlist p.parent a.link:active,
.control-assetlist ul li a.link:active {
text-decoration: none;
}
.control-assetlist p.parent a.link span,
.control-assetlist ul li a.link span {
display: block;
}
.control-assetlist p.parent a.link span.description,
.control-assetlist ul li a.link span.description {
color: #8f8f8f;
font-size: 12px;
font-weight: 400;
word-wrap: break-word;
}
.control-assetlist p.parent a.link span.description strong,
.control-assetlist ul li a.link span.description strong {
color: #405261;
font-weight: 400;
}
.control-assetlist p.parent.directory a.link,
.control-assetlist ul li.directory a.link,
.control-assetlist p.parent.parent a.link,
.control-assetlist ul li.parent a.link {
padding-left: 40px;
}
.control-assetlist p.parent.directory a.link:after,
.control-assetlist ul li.directory a.link:after,
.control-assetlist p.parent.parent a.link:after,
.control-assetlist ul li.parent a.link:after {
display: block;
position: absolute;
width: 10px;
height: 10px;
top: 10px;
left: 20px;
font-family: FontAwesome;
font-weight: normal;
font-style: normal;
text-decoration: inherit;
-webkit-font-smoothing: antialiased;
content: "\f07b";
color: #a1aab1;
font-size: 14px;
}
.control-assetlist p.parent.parent a.link,
.control-assetlist ul li.parent a.link {
padding-left: 41px;
background-color: #ffffff;
word-wrap: break-word;
}
.control-assetlist p.parent.parent a.link:before,
.control-assetlist ul li.parent a.link:before {
content: '';
display: block;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 1px;
background: #ecf0f1;
}
.control-assetlist p.parent.parent a.link:after,
.control-assetlist ul li.parent a.link:after {
font-size: 13px;
color: #34495e;
width: 18px;
height: 18px;
top: 11px;
left: 22px;
opacity: 0.5;
filter: alpha(opacity=50);
font-family: FontAwesome;
font-weight: normal;
font-style: normal;
text-decoration: inherit;
-webkit-font-smoothing: antialiased;
content: "\f053";
}
.control-assetlist p.parent a.link:hover {
background: #dddddd !important;
}
.control-assetlist p.parent a.link:hover:after {
opacity: 1;
filter: alpha(opacity=100);
}
.control-assetlist p.parent a.link:hover:before {
display: none;
}
.control-assetlist ul {
padding: 0;
margin: 0;
}
.control-assetlist ul li {
font-weight: 300;
line-height: 150%;
position: relative;
list-style: none;
}
.control-assetlist ul li.active a.link,
.control-assetlist ul li a.link:hover {
background: #dddddd;
}
.control-assetlist ul li.active a.link {
position: relative;
}
.control-assetlist ul li.active a.link:after {
position: absolute;
height: 100%;
width: 4px;
left: 0;
top: 0;
background: #e67e22;
display: block;
content: ' ';
}
.control-assetlist ul li div.controls {
position: absolute;
right: 45px;
top: 10px;
}
.control-assetlist ul li div.controls .dropdown {
width: 14px;
height: 21px;
}
.control-assetlist ul li div.controls .dropdown.open a.control {
display: block!important;
}
.control-assetlist ul li div.controls .dropdown.open a.control:before {
visibility: visible;
display: block;
}
.control-assetlist ul li div.controls a.control {
color: #405261;
font-size: 14px;
visibility: hidden;
overflow: hidden;
width: 14px;
height: 21px;
display: none;
text-decoration: none;
cursor: pointer;
opacity: 0.5;
filter: alpha(opacity=50);
}
.control-assetlist ul li div.controls a.control:before {
visibility: visible;
display: block;
margin-right: 0;
}
.control-assetlist ul li div.controls a.control:hover {
opacity: 1;
filter: alpha(opacity=100);
}
.control-assetlist ul li:hover {
background: #dddddd;
}
.control-assetlist ul li:hover div.controls,
.control-assetlist ul li:hover a.control {
display: block!important;
}
.control-assetlist ul li:hover div.controls > a.control,
.control-assetlist ul li:hover a.control > a.control {
display: block!important;
}
.control-assetlist ul li .checkbox {
position: absolute;
top: -5px;
right: -5px;
}
.control-assetlist ul li .checkbox label {
margin-right: 0;
}
.control-assetlist ul li .checkbox label:before {
border-color: #cccccc;
}
.control-assetlist div.list-container {
position: relative;
-webkit-transform: translate(0, 0);
-ms-transform: translate(0, 0);
transform: translate(0, 0);
}
.control-assetlist div.list-container.animate ul {
-webkit-transition: all 0.2s ease;
transition: all 0.2s ease;
}
.control-assetlist div.list-container.goForward ul {
-webkit-transform: translate(-350px, 0);
-ms-transform: translate(-350px, 0);
transform: translate(-350px, 0);
}
.control-assetlist div.list-container.goBackward ul {
-webkit-transform: translate(350px, 0);
-ms-transform: translate(350px, 0);
transform: translate(350px, 0);
}

View File

@ -0,0 +1,184 @@
/*
* Asset list
*/
+function ($) { "use strict";
var AssetList = function (form, alias) {
this.$form = $(form)
this.alias = alias
this.$form.on('ajaxSuccess', $.proxy(this.onAjaxSuccess, this))
this.$form.on('click', 'ul.list > li.directory > a', $.proxy(this.onDirectoryClick, this))
this.$form.on('click', 'ul.list > li.file > a', $.proxy(this.onFileClick, this))
this.$form.on('click', 'p.parent > a', $.proxy(this.onDirectoryClick, this))
this.$form.on('click', 'a[data-control=delete-asset]', $.proxy(this.onDeleteClick, this))
this.$form.on('oc.list.setActiveItem', $.proxy(this.onSetActiveItem, this))
this.setupUploader()
}
// Event handlers
// =================
AssetList.prototype.onDirectoryClick = function(e) {
this.gotoDirectory(
$(e.currentTarget).data('path'),
$(e.currentTarget).parent().hasClass('parent')
)
return false;
}
AssetList.prototype.gotoDirectory = function(path, gotoParent) {
var $container = $('div.list-container', this.$form),
self = this
if (gotoParent !== undefined && gotoParent)
$container.addClass('goBackward')
else
$container.addClass('goForward')
$.oc.stripeLoadIndicator.show()
this.$form.request(this.alias+'::onOpenDirectory', {
data: {
path: path,
d: 0.2
},
complete: function() {
self.updateUi()
$container.trigger('oc.scrollbar.gotoStart')
},
error: function(jqXHR, textStatus, errorThrown) {
$container.removeClass('goForward goBackward')
alert(jqXHR.responseText.length ? jqXHR.responseText : jqXHR.statusText)
}
}).always(function(){
$.oc.stripeLoadIndicator.hide()
})
}
AssetList.prototype.onDeleteClick = function(e) {
var $el = $(e.currentTarget),
self = this
if (!confirm($el.data('confirmation')))
return false
this.$form.request(this.alias+'::onDeleteFiles', {
success: function(data) {
if (data.error !== undefined && $.type(data.error) === 'string' && data.error.length)
$.oc.flashMsg({text: data.error, 'class': 'error'})
},
complete: function() {
self.refresh()
}
})
return false
}
AssetList.prototype.onAjaxSuccess = function() {
this.updateUi()
}
AssetList.prototype.onUploadFail = function(file, message) {
if (file.xhr.status === 413) {
message = 'Server rejected the file because it was too large, try increasing post_max_size';
}
if (!message) {
message = 'Error uploading file'
}
$.oc.alert(message)
this.refresh()
}
AssetList.prototype.onUploadSuccess = function(file, data) {
if (data !== 'success') {
$.oc.alert(data)
}
}
AssetList.prototype.onUploadComplete = function(file, data) {
$.oc.stripeLoadIndicator.hide()
this.refresh()
}
AssetList.prototype.onUploadStart = function() {
$.oc.stripeLoadIndicator.show()
}
AssetList.prototype.onFileClick = function(event) {
var $link = $(event.currentTarget),
$li = $link.parent()
var e = $.Event('open.oc.list', {relatedTarget: $li.get(0), clickEvent: event})
this.$form.trigger(e, this)
if (e.isDefaultPrevented())
return false;
}
AssetList.prototype.onSetActiveItem = function(event, dataId) {
$('ul li.file', this.$form).removeClass('active')
if (dataId)
$('ul li.file[data-id="'+dataId+'"]', this.$form).addClass('active')
}
// Service functions
// =================
AssetList.prototype.updateUi = function() {
$('button[data-control=asset-tools]', self.$form).trigger('oc.triggerOn.update')
}
AssetList.prototype.refresh = function() {
var self = this;
this.$form.request(this.alias+'::onRefresh', {
complete: function() {
self.updateUi()
}
})
}
AssetList.prototype.setupUploader = function() {
var self = this,
$link = $('[data-control="upload-assets"]', this.$form),
uploaderOptions = {
method: 'POST',
url: window.location,
paramName: 'file_data',
previewsContainer: $('<div />').get(0),
clickable: $link.get(0),
timeout: 0,
headers: {}
}
/*
* Add CSRF token to headers
*/
var token = $('meta[name="csrf-token"]').attr('content')
if (token) {
uploaderOptions.headers['X-CSRF-TOKEN'] = token
}
var dropzone = new Dropzone($('<div />').get(0), uploaderOptions)
dropzone.on('error', $.proxy(self.onUploadFail, self))
dropzone.on('success', $.proxy(self.onUploadSuccess, self))
dropzone.on('complete', $.proxy(self.onUploadComplete, self))
dropzone.on('sending', function(file, xhr, formData) {
$.each(self.$form.serializeArray(), function (index, field) {
formData.append(field.name, field.value)
})
xhr.setRequestHeader('X-OCTOBER-REQUEST-HANDLER', self.alias + '::onUpload')
self.onUploadStart()
})
}
$(document).ready(function(){
new AssetList($('#asset-list-container').closest('form'), $('#asset-list-container').data('alias'))
})
}(window.jQuery);

View File

@ -0,0 +1,236 @@
@import "../../../../../backend/assets/less/core/boot.less";
.control-assetlist {
p.no-data {
padding: 22px;
margin: 0;
color: @color-filelist-norecords-text;
font-size: 14px;
text-align: center;
font-weight: 400;
.border-radius(@border-radius-base);
}
p.parent, ul li {
font-weight: 300;
line-height: 150%;
margin-bottom: 0;
&.active a {
background: @color-list-active;
position: relative;
&:after {
position: absolute;
height: 100%;
width: 4px;
left: 0;
top: 0;
background: @color-list-active-border;
display: block;
content: ' ';
}
}
a.link {
display: block;
position: relative;
word-wrap: break-word;
padding: 10px 50px 10px 20px;
outline: none;
font-weight: 400;
color: @color-text-title;
font-size: 14px;
&:hover, &:focus, &:active {text-decoration: none;}
span {
display: block;
&.description {
color: @color-text-description;
font-size: 12px;
font-weight: 400;
word-wrap: break-word;
strong {
color: @color-text-title;
font-weight: 400;
}
}
}
}
&.directory, &.parent {
a.link {
padding-left: 40px;
&:after {
display: block;
position: absolute;
width: 10px;
height: 10px;
top: 10px;
left: 20px;
.icon(@folder);
color: @color-list-icon;
font-size: 14px;
}
}
}
&.parent {
a.link {
padding-left: 41px;
background-color: @color-list-parent-bg;
word-wrap: break-word;
&:before {
content: '';
height: 1px;
display: block;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 1px;
background: #ecf0f1;
}
&:after {
font-size: 13px;
color: @color-list-nav-arrow;
width: 18px;
height: 18px;
top: 11px;
left: 22px;
.opacity(0.5);
.icon(@chevron-left);
}
}
}
}
p.parent a.link:hover {
background: @color-list-active!important;
&:after {
.opacity(1);
}
&:before {
display: none;
}
}
ul {
padding: 0;
margin: 0;
li {
font-weight: 300;
line-height: 150%;
position: relative;
list-style: none;
&.active a.link, a.link:hover {background: @color-list-active;}
&.active a.link {
position: relative;
&:after {
position: absolute;
height: 100%;
width: 4px;
left: 0;
top: 0;
background: @color-list-active-border;
display: block;
content: ' ';
}
}
div.controls {
position: absolute;
right: 45px;
top: 10px;
.dropdown {
width: 14px;
height: 21px;
&.open a.control {
display: block!important;
&:before {
visibility: visible;
display: block;
}
}
}
a.control {
color: @color-text-title;
font-size: 14px;
visibility: hidden;
overflow: hidden;
width: 14px;
height: 21px;
display: none;
text-decoration: none;
cursor: pointer;
.opacity(0.5);
&:before {
visibility: visible;
display: block;
margin-right: 0;
}
&:hover {
.opacity(1);
}
}
}
&:hover {
background: @color-list-active;
div.controls, a.control {
display: block!important;
> a.control {
display: block!important;
}
}
}
.checkbox {
position: absolute;
top: -5px;
right: -5px;
label {
margin-right: 0;
&:before {
border-color: @color-filelist-cb-border;
}
}
}
}
}
div.list-container {
position: relative;
.translate(0, 0);
&.animate ul {
.transition(all 0.2s ease);
}
&.goForward ul {
.translate(-350px, 0);
}
&.goBackward ul {
.translate(350px, 0);
}
}
}

View File

@ -0,0 +1,8 @@
<?= $this->makePartial('toolbar') ?>
<div class="layout-row" id="asset-list-container" data-alias="<?= $this->alias ?>">
<div class="layout-cell">
<div class="layout-relative">
<?= $this->makePartial('files', ['data'=>$data]) ?>
</div>
</div>
</div>

View File

@ -0,0 +1,7 @@
<div class="layout-absolute">
<div class="control-scrollbar" data-control="scrollbar">
<div class="control-assetlist" data-control="assetlist" id="<?= $this->getId('asset-list') ?>">
<?= $this->makePartial('items', ['items'=>$data]) ?>
</div>
</div>
</div>

View File

@ -0,0 +1,57 @@
<?php
$searchMode = $this->isSearchMode();
if (($upPath = $this->getUpPath()) !== null && !$searchMode):
?>
<p class="parent">
<a href="<?= $upPath ?>" data-path="<?= $upPath ?>" class="link"><?= $this->getCurrentRelativePath() ?></a>
</p>
<?php endif ?>
<div class="list-container animate">
<?php if ($items): ?>
<ul class="list">
<?php foreach ($items as $item):
$dataId = 'asset-'.ltrim($item->path, '/');
?>
<li class="<?= $item->type ?>" <?php if ($item->editable): ?>data-editable<?php endif ?> data-item-path="<?= e(ltrim($item->path, '/')) ?>" data-item-type="asset" data-id="<?= e($dataId) ?>">
<a class="link" target="_blank" data-path="<?= $item->path ?>" href="<?= $this->getThemeFileUrl($item->path) ?>">
<?= e($item->name) ?>
<?php if ($searchMode): ?>
<span class="description">
<?= e(dirname($item->path)) ?>
</span>
<?php endif ?>
</a>
<div class="controls">
<a
href="javascript:;"
class="control icon btn-primary oc-icon-terminal"
title="<?= e(trans('cms::lang.asset.rename')) ?>"
data-control="popup"
data-request-data="renamePath: '<?= e($item->path) ?>'"
data-handler="<?= $this->getEventHandler('onLoadRenamePopup') ?>"
><?= e(trans('cms::lang.asset.rename')) ?></a>
</div>
<input type="hidden" name="file[<?= e($item->path) ?>]" value="0">
<div class="checkbox custom-checkbox nolabel">
<?php $cbId = 'cb'.md5($item->path) ?>
<input
id="<?= $cbId ?>"
type="checkbox"
name="file[<?= e($item->path) ?>]"
<?= $this->isItemSelected($item->path) ? 'checked' : null ?>
data-request="<?= $this->getEventHandler('onSelect') ?>"
value="1">
<label for="<?= $cbId ?>"><?= e(trans('cms::lang.asset.select')) ?></label>
</div>
</li>
<?php endforeach ?>
</ul>
<?php else: ?>
<p class="no-data"><?= e(trans($this->noRecordsMessage)) ?></p>
<?php endif ?>
</div>

View File

@ -0,0 +1,40 @@
<?= Form::open([
'data-request'=>$this->getEventHandler('onMove'),
'data-request-success'=>"\$(this).trigger('close.oc.popup')",
'data-stripe-load-indicator'=>1,
'id'=>'asset-move-popup-form'
]) ?>
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(trans('cms::lang.asset.move_popup_title')) ?></h4>
</div>
<div class="modal-body">
<div class="form-group">
<label><?= e(trans('cms::lang.asset.move_destination')) ?></label>
<select
class="form-control custom-select"
name="dest"
data-placeholder="<?= e(trans('cms::lang.asset.move_please_select')) ?>">
<option></option>
<?php foreach ($directories as $path=>$directory):?>
<option value="<?= e($path) ?>"><?= e($directory) ?></option>
<?php endforeach ?>
</select>
</div>
<input type="hidden" name="selectedList" value="<?= e($selectedList) ?>">
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary">
<?= e(trans('cms::lang.asset.move_button')) ?>
</button>
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
<?= Form::close() ?>

View File

@ -0,0 +1,45 @@
<?= Form::open([
'data-request'=>$this->getEventHandler('onNewDirectory'),
'data-request-success'=>"\$(this).trigger('close.oc.popup')",
'data-stripe-load-indicator'=>1,
'id'=>'asset-new-dir-popup-form'
]) ?>
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(trans('cms::lang.asset.directory_popup_title')) ?></h4>
</div>
<div class="modal-body">
<div class="form-group">
<label><?= e(trans('cms::lang.asset.directory_name')) ?></label>
<input
type="text"
name="name"
value=""
class="form-control"
autocomplete="off">
</div>
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary">
<?= e(trans('backend::lang.form.create')) ?>
</button>
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
<script>
setTimeout(
function() {
$('#asset-new-dir-popup-form input.form-control').focus()
}, 310
)
</script>
<?= Form::close() ?>

View File

@ -0,0 +1,46 @@
<?= Form::ajax($this->getEventHandler('onApplyName'), [
'success' => "\$el.trigger('close.oc.popup');",
'data-stripe-load-indicator' => 1,
'id' => 'asset-rename-popup-form'
]) ?>
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(trans('cms::lang.asset.rename_popup_title')) ?></h4>
</div>
<div class="modal-body">
<div class="form-group">
<label><?= e(trans('cms::lang.asset.rename_new_name')) ?></label>
<input
type="text"
name="name"
value="<?= e($name) ?>"
class="form-control"
autocomplete="off">
</div>
<input type="hidden" name="originalPath" value="<?= e($originalPath) ?>">
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary">
<?= e(trans('backend::lang.form.apply')) ?>
</button>
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
<script>
setTimeout(
function() {
$('#asset-rename-popup-form input.form-control').focus()
}, 310
)
</script>
<?= Form::close() ?>

View File

@ -0,0 +1,77 @@
<div class="layout-row min-size">
<div class="control-toolbar toolbar-padded">
<!-- Control Panel -->
<div class="toolbar-item" data-calculate-width>
<div class="btn-group">
<div class="dropdown last">
<button type="button" class="btn btn-default oc-icon-plus"
data-control="create-asset"
data-toggle="dropdown"
><?= e(trans('cms::lang.sidebar.add')) ?></button>
<ul class="dropdown-menu offset-left" role="menu" data-dropdown-title="<?= e(trans('cms::lang.asset.drop_down_add_title')) ?>">
<li role="presentation"><a role="menuitem" tabindex="-1" href="javascript:;" data-control="create-template" class="oc-icon-file-text-o"><?= e(trans('cms::lang.asset.create_file')) ?></a></li>
<li role="presentation"><a role="menuitem" tabindex="-1" href="javascript:;" data-control="upload-assets" class="oc-icon-upload"><?= e(trans('cms::lang.asset.upload_files')) ?></a></li>
<li role="presentation" class="divider"></li>
<li role="presentation"><a
role="menuitem"
tabindex="-1"
href="javascript:;"
class="oc-icon-folder-o"
data-control="popup"
data-handler="<?= $this->getEventHandler('onLoadNewDirPopup') ?>"
><?= e(trans('cms::lang.asset.create_directory')) ?></a></li>
</ul>
</div>
<div class="dropdown hide"
id="<?= $this->getId('tools-button') ?>"
data-trigger-action="show"
data-trigger="<?= '#'.$this->getId('asset-list') ?> input[type=checkbox]"
data-trigger-condition="checked">
<button type="button" class="btn btn-default empty oc-icon-wrench last"
data-toggle="dropdown"
data-control="asset-tools"
></button>
<ul class="dropdown-menu" role="menu" data-dropdown-title="<?= e(trans('cms::lang.asset.drop_down_operation_title')) ?>">
<li role="presentation"><a
role="menuitem"
tabindex="-1"
href="javascript:;"
data-control="delete-asset"
data-confirmation="<?= e(trans($this->deleteConfirmation)) ?>"
class="oc-icon-trash-o"
><?= e(trans('cms::lang.asset.delete')) ?></a></li>
<li role="presentation"><a
role="menuitem"
tabindex="-1"
href="javascript:;"
class="oc-icon-angle-double-right"
data-control="popup"
data-handler="<?= $this->getEventHandler('onLoadMovePopup') ?>"
><?= e(trans('cms::lang.asset.move')) ?></a></li>
</ul>
</div>
</div>
</div>
<!-- Asset Search -->
<div class="relative toolbar-item loading-indicator-container size-input-text">
<input
type="text"
name="search"
value="<?= e($this->getSearchTerm()) ?>"
class="form-control icon search" autocomplete="off"
placeholder="<?= e(trans('cms::lang.sidebar.search')) ?>"
data-track-input
data-load-indicator
data-load-indicator-opaque
data-request-success="$('<?= '#'.$this->getId('tools-button') ?>').trigger('oc.triggerOn.update')"
data-request="<?= $this->getEventHandler('onSearch') ?>"
>
</div>
</div>
</div>

View File

@ -26,6 +26,9 @@ class Plugin extends PluginBase
*/
public function registerComponents()
{
return [
'Romanah\Bagisto\Components\Products' => 'products'
];
}
/**

View File

@ -0,0 +1,32 @@
<?php
namespace Romanah\Bagisto\Components;
use Cms\Classes\ComponentBase;
use Illuminate\Support\Facades\Http;
use Redirect;
use Flash;
class Products extends ComponentBase
{
public function componentDetails()
{
return [
'name' => 'Products',
'description' => 'Fetch Products settings'
];
}
public $bagisto_url = 'https://nurgul.com.tm/app/api/';
public function onFetchProduct()
{
$response = Http::get('https://nurgul.com.tm/app/api/products?locale=tm&limit=4');
// $response = getenv('APP_URL');
// dd($response);
return $response;
}
}

View File

@ -0,0 +1,18 @@
<?php namespace Romanah\Bagisto\Controllers;
use Backend\Classes\Controller;
use BackendMenu;
class Brand extends Controller
{
public $implement = [ 'Backend\Behaviors\ListController', 'Backend\Behaviors\FormController' ];
public $listConfig = 'config_list.yaml';
public $formConfig = 'config_form.yaml';
public function __construct()
{
parent::__construct();
BackendMenu::setContext('Romanah.Bagisto', 'main-menu-item', 'side-menu-item');
}
}

View File

@ -0,0 +1,18 @@
<?php namespace Romanah\Bagisto\Controllers;
use Backend\Classes\Controller;
use BackendMenu;
class Slider extends Controller
{
public $implement = [ 'Backend\Behaviors\ListController', 'Backend\Behaviors\FormController' ];
public $listConfig = 'config_list.yaml';
public $formConfig = 'config_form.yaml';
public function __construct()
{
parent::__construct();
BackendMenu::setContext('Romanah.Bagisto', 'main-menu-item');
}
}

View File

@ -0,0 +1,18 @@
<div data-control="toolbar">
<a href="<?= Backend::url('romanah/bagisto/brand/create') ?>" class="btn btn-primary oc-icon-plus"><?= e(trans('backend::lang.form.create')) ?></a>
<button
class="btn btn-default oc-icon-trash-o"
disabled="disabled"
onclick="$(this).data('request-data', {
checked: $('.control-list').listWidget('getChecked')
})"
data-request="onDelete"
data-request-confirm="<?= e(trans('backend::lang.list.delete_selected_confirm')) ?>"
data-trigger-action="enable"
data-trigger=".control-list input[type=checkbox]"
data-trigger-condition="checked"
data-request-success="$(this).prop('disabled', true)"
data-stripe-load-indicator>
<?= e(trans('backend::lang.list.delete_selected')) ?>
</button>
</div>

View File

@ -0,0 +1,10 @@
name: Brand
form: $/romanah/bagisto/models/brand/fields.yaml
modelClass: Romanah\Bagisto\Models\Brand
defaultRedirect: romanah/bagisto/brand
create:
redirect: 'romanah/bagisto/brand/update/:id'
redirectClose: romanah/bagisto/brand
update:
redirect: romanah/bagisto/brand
redirectClose: romanah/bagisto/brand

View File

@ -0,0 +1,12 @@
list: $/romanah/bagisto/models/brand/columns.yaml
modelClass: Romanah\Bagisto\Models\Brand
title: Brand
noRecordsMessage: 'backend::lang.list.no_records'
showSetup: true
showCheckboxes: true
recordsPerPage: 20
toolbar:
buttons: list_toolbar
search:
prompt: 'backend::lang.list.search_prompt'
recordUrl: 'romanah/bagisto/brand/update/:id'

View File

@ -0,0 +1,46 @@
<?php Block::put('breadcrumb') ?>
<ul>
<li><a href="<?= Backend::url('romanah/bagisto/brand') ?>">Brand</a></li>
<li><?= e($this->pageTitle) ?></li>
</ul>
<?php Block::endPut() ?>
<?php if (!$this->fatalError): ?>
<?= Form::open(['class' => 'layout']) ?>
<div class="layout-row">
<?= $this->formRender() ?>
</div>
<div class="form-buttons">
<div class="loading-indicator-container">
<button
type="submit"
data-request="onSave"
data-hotkey="ctrl+s, cmd+s"
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
class="btn btn-primary">
<?= e(trans('backend::lang.form.create')) ?>
</button>
<button
type="button"
data-request="onSave"
data-request-data="close:1"
data-hotkey="ctrl+enter, cmd+enter"
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
class="btn btn-default">
<?= e(trans('backend::lang.form.create_and_close')) ?>
</button>
<span class="btn-text">
<?= e(trans('backend::lang.form.or')) ?> <a href="<?= Backend::url('romanah/bagisto/brand') ?>"><?= e(trans('backend::lang.form.cancel')) ?></a>
</span>
</div>
</div>
<?= Form::close() ?>
<?php else: ?>
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
<p><a href="<?= Backend::url('romanah/bagisto/brand') ?>" class="btn btn-default"><?= e(trans('backend::lang.form.return_to_list')) ?></a></p>
<?php endif ?>

View File

@ -0,0 +1 @@
<?= $this->listRender() ?>

View File

@ -0,0 +1,22 @@
<?php Block::put('breadcrumb') ?>
<ul>
<li><a href="<?= Backend::url('romanah/bagisto/brand') ?>">Brand</a></li>
<li><?= e($this->pageTitle) ?></li>
</ul>
<?php Block::endPut() ?>
<?php if (!$this->fatalError): ?>
<div class="form-preview">
<?= $this->formRenderPreview() ?>
</div>
<?php else: ?>
<p class="flash-message static error"><?= e($this->fatalError) ?></p>
<?php endif ?>
<p>
<a href="<?= Backend::url('romanah/bagisto/brand') ?>" class="btn btn-default oc-icon-chevron-left">
<?= e(trans('backend::lang.form.return_to_list')) ?>
</a>
</p>

View File

@ -0,0 +1,54 @@
<?php Block::put('breadcrumb') ?>
<ul>
<li><a href="<?= Backend::url('romanah/bagisto/brand') ?>">Brand</a></li>
<li><?= e($this->pageTitle) ?></li>
</ul>
<?php Block::endPut() ?>
<?php if (!$this->fatalError): ?>
<?= Form::open(['class' => 'layout']) ?>
<div class="layout-row">
<?= $this->formRender() ?>
</div>
<div class="form-buttons">
<div class="loading-indicator-container">
<button
type="submit"
data-request="onSave"
data-request-data="redirect:0"
data-hotkey="ctrl+s, cmd+s"
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
class="btn btn-primary">
<?= e(trans('backend::lang.form.save')) ?>
</button>
<button
type="button"
data-request="onSave"
data-request-data="close:1"
data-hotkey="ctrl+enter, cmd+enter"
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
class="btn btn-default">
<?= e(trans('backend::lang.form.save_and_close')) ?>
</button>
<button
type="button"
class="oc-icon-trash-o btn-icon danger pull-right"
data-request="onDelete"
data-load-indicator="<?= e(trans('backend::lang.form.deleting')) ?>"
data-request-confirm="<?= e(trans('backend::lang.form.confirm_delete')) ?>">
</button>
<span class="btn-text">
<?= e(trans('backend::lang.form.or')) ?> <a href="<?= Backend::url('romanah/bagisto/brand') ?>"><?= e(trans('backend::lang.form.cancel')) ?></a>
</span>
</div>
</div>
<?= Form::close() ?>
<?php else: ?>
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
<p><a href="<?= Backend::url('romanah/bagisto/brand') ?>" class="btn btn-default"><?= e(trans('backend::lang.form.return_to_list')) ?></a></p>
<?php endif ?>

View File

@ -0,0 +1,18 @@
<div data-control="toolbar">
<a href="<?= Backend::url('romanah/bagisto/slider/create') ?>" class="btn btn-primary oc-icon-plus"><?= e(trans('backend::lang.form.create')) ?></a>
<button
class="btn btn-default oc-icon-trash-o"
disabled="disabled"
onclick="$(this).data('request-data', {
checked: $('.control-list').listWidget('getChecked')
})"
data-request="onDelete"
data-request-confirm="<?= e(trans('backend::lang.list.delete_selected_confirm')) ?>"
data-trigger-action="enable"
data-trigger=".control-list input[type=checkbox]"
data-trigger-condition="checked"
data-request-success="$(this).prop('disabled', true)"
data-stripe-load-indicator>
<?= e(trans('backend::lang.list.delete_selected')) ?>
</button>
</div>

View File

@ -0,0 +1,10 @@
name: Slider
form: $/romanah/bagisto/models/slider/fields.yaml
modelClass: Romanah\Bagisto\Models\Slider
defaultRedirect: romanah/bagisto/slider
create:
redirect: 'romanah/bagisto/slider/update/:id'
redirectClose: romanah/bagisto/slider
update:
redirect: romanah/bagisto/slider
redirectClose: romanah/bagisto/slider

View File

@ -0,0 +1,12 @@
list: $/romanah/bagisto/models/slider/columns.yaml
modelClass: Romanah\Bagisto\Models\Slider
title: Slider
noRecordsMessage: 'backend::lang.list.no_records'
showSetup: true
showCheckboxes: true
recordsPerPage: 20
toolbar:
buttons: list_toolbar
search:
prompt: 'backend::lang.list.search_prompt'
recordUrl: 'romanah/bagisto/slider/update/:id'

View File

@ -0,0 +1,46 @@
<?php Block::put('breadcrumb') ?>
<ul>
<li><a href="<?= Backend::url('romanah/bagisto/slider') ?>">Slider</a></li>
<li><?= e($this->pageTitle) ?></li>
</ul>
<?php Block::endPut() ?>
<?php if (!$this->fatalError): ?>
<?= Form::open(['class' => 'layout']) ?>
<div class="layout-row">
<?= $this->formRender() ?>
</div>
<div class="form-buttons">
<div class="loading-indicator-container">
<button
type="submit"
data-request="onSave"
data-hotkey="ctrl+s, cmd+s"
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
class="btn btn-primary">
<?= e(trans('backend::lang.form.create')) ?>
</button>
<button
type="button"
data-request="onSave"
data-request-data="close:1"
data-hotkey="ctrl+enter, cmd+enter"
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
class="btn btn-default">
<?= e(trans('backend::lang.form.create_and_close')) ?>
</button>
<span class="btn-text">
<?= e(trans('backend::lang.form.or')) ?> <a href="<?= Backend::url('romanah/bagisto/slider') ?>"><?= e(trans('backend::lang.form.cancel')) ?></a>
</span>
</div>
</div>
<?= Form::close() ?>
<?php else: ?>
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
<p><a href="<?= Backend::url('romanah/bagisto/slider') ?>" class="btn btn-default"><?= e(trans('backend::lang.form.return_to_list')) ?></a></p>
<?php endif ?>

View File

@ -0,0 +1 @@
<?= $this->listRender() ?>

View File

@ -0,0 +1,22 @@
<?php Block::put('breadcrumb') ?>
<ul>
<li><a href="<?= Backend::url('romanah/bagisto/slider') ?>">Slider</a></li>
<li><?= e($this->pageTitle) ?></li>
</ul>
<?php Block::endPut() ?>
<?php if (!$this->fatalError): ?>
<div class="form-preview">
<?= $this->formRenderPreview() ?>
</div>
<?php else: ?>
<p class="flash-message static error"><?= e($this->fatalError) ?></p>
<?php endif ?>
<p>
<a href="<?= Backend::url('romanah/bagisto/slider') ?>" class="btn btn-default oc-icon-chevron-left">
<?= e(trans('backend::lang.form.return_to_list')) ?>
</a>
</p>

View File

@ -0,0 +1,54 @@
<?php Block::put('breadcrumb') ?>
<ul>
<li><a href="<?= Backend::url('romanah/bagisto/slider') ?>">Slider</a></li>
<li><?= e($this->pageTitle) ?></li>
</ul>
<?php Block::endPut() ?>
<?php if (!$this->fatalError): ?>
<?= Form::open(['class' => 'layout']) ?>
<div class="layout-row">
<?= $this->formRender() ?>
</div>
<div class="form-buttons">
<div class="loading-indicator-container">
<button
type="submit"
data-request="onSave"
data-request-data="redirect:0"
data-hotkey="ctrl+s, cmd+s"
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
class="btn btn-primary">
<?= e(trans('backend::lang.form.save')) ?>
</button>
<button
type="button"
data-request="onSave"
data-request-data="close:1"
data-hotkey="ctrl+enter, cmd+enter"
data-load-indicator="<?= e(trans('backend::lang.form.saving')) ?>"
class="btn btn-default">
<?= e(trans('backend::lang.form.save_and_close')) ?>
</button>
<button
type="button"
class="oc-icon-trash-o btn-icon danger pull-right"
data-request="onDelete"
data-load-indicator="<?= e(trans('backend::lang.form.deleting')) ?>"
data-request-confirm="<?= e(trans('backend::lang.form.confirm_delete')) ?>">
</button>
<span class="btn-text">
<?= e(trans('backend::lang.form.or')) ?> <a href="<?= Backend::url('romanah/bagisto/slider') ?>"><?= e(trans('backend::lang.form.cancel')) ?></a>
</span>
</div>
</div>
<?= Form::close() ?>
<?php else: ?>
<p class="flash-message static error"><?= e(trans($this->fatalError)) ?></p>
<p><a href="<?= Backend::url('romanah/bagisto/slider') ?>" class="btn btn-default"><?= e(trans('backend::lang.form.return_to_list')) ?></a></p>
<?php endif ?>

View File

@ -0,0 +1,27 @@
<?php namespace Romanah\Bagisto\Models;
use Model;
/**
* Model
*/
class Brand extends Model
{
use \October\Rain\Database\Traits\Validation;
use \October\Rain\Database\Traits\SoftDelete;
protected $dates = ['deleted_at'];
/**
* @var string The database table used by the model.
*/
public $table = 'romanah_bagisto_brand';
/**
* @var array Validation rules
*/
public $rules = [
];
}

View File

@ -0,0 +1,27 @@
<?php namespace Romanah\Bagisto\Models;
use Model;
/**
* Model
*/
class Slider extends Model
{
use \October\Rain\Database\Traits\Validation;
use \October\Rain\Database\Traits\SoftDelete;
protected $dates = ['deleted_at'];
/**
* @var string The database table used by the model.
*/
public $table = 'romanah_bagisto_slider';
/**
* @var array Validation rules
*/
public $rules = [
];
}

View File

@ -0,0 +1,10 @@
columns:
id:
label: id
type: number
img:
label: img
type: text
note:
label: note
type: text

View File

@ -0,0 +1,9 @@
fields:
img:
label: Img
span: auto
type: mediafinder
note:
label: Note
span: auto
type: text

View File

@ -0,0 +1,19 @@
columns:
id:
label: id
type: number
header:
label: header
type: text
header2:
label: header2
type: text
txt:
label: txt
type: text
btn_txt:
label: btn_txt
type: text
url:
label: url
type: text

View File

@ -0,0 +1,25 @@
fields:
img:
label: Img
span: auto
type: mediafinder
header:
label: Header
span: auto
type: text
header2:
label: Header2
span: auto
type: text
txt:
label: Txt
span: auto
type: text
btn_txt:
label: 'Btn txt'
span: auto
type: text
url:
label: Url
span: auto
type: text

View File

@ -4,3 +4,13 @@ plugin:
author: Romanah
icon: oc-icon-cart-plus
homepage: ''
navigation:
main-menu-item:
label: 'Slider (Nurgul)'
url: romanah/bagisto/slider
icon: icon-image
sideMenu:
side-menu-item:
label: Brendlar
url: romanah/bagisto/brand
icon: icon-slack

View File

@ -0,0 +1,26 @@
<?php namespace Romanah\Bagisto\Updates;
use Schema;
use October\Rain\Database\Updates\Migration;
class BuilderTableCreateRomanahBagistoBrand extends Migration
{
public function up()
{
Schema::create('romanah_bagisto_brand', function($table)
{
$table->engine = 'InnoDB';
$table->increments('id')->unsigned();
$table->timestamp('created_at')->nullable();
$table->timestamp('updated_at')->nullable();
$table->timestamp('deleted_at')->nullable();
$table->string('img')->nullable();
$table->string('note')->nullable();
});
}
public function down()
{
Schema::dropIfExists('romanah_bagisto_brand');
}
}

View File

@ -0,0 +1,30 @@
<?php namespace Romanah\Bagisto\Updates;
use Schema;
use October\Rain\Database\Updates\Migration;
class BuilderTableCreateRomanahBagistoSlider extends Migration
{
public function up()
{
Schema::create('romanah_bagisto_slider', function($table)
{
$table->engine = 'InnoDB';
$table->increments('id')->unsigned();
$table->timestamp('created_at')->nullable();
$table->timestamp('updated_at')->nullable();
$table->timestamp('deleted_at')->nullable();
$table->string('img')->nullable();
$table->string('header')->nullable();
$table->string('header2')->nullable();
$table->text('txt')->nullable();
$table->string('btn_txt')->nullable();
$table->string('url')->nullable();
});
}
public function down()
{
Schema::dropIfExists('romanah_bagisto_slider');
}
}

View File

@ -1,2 +1,8 @@
v1.0.1:
1.0.1:
- 'Initialize plugin'
1.0.2:
- 'Created table romanah_bagisto_slider'
- builder_table_create_romanah_bagisto_slider.php
1.0.3:
- 'Created table romanah_bagisto_brand'
- builder_table_create_romanah_bagisto_brand.php

2
themes/nurgul/assets/jquery.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -25,6 +25,8 @@ code = "top-menu"
<link rel="stylesheet" href="{{'assets/css/style.css'|theme}}">
<!-- Responsive css -->
<link rel="stylesheet" href="{{'assets/css/responsive.css'|theme}}">
{% styles %}
</head>
<body>
@ -60,10 +62,15 @@ code = "top-menu"
<!-- preloader area end -->
<!-- All JS Plugins -->
<script src="{{'assets/jquery.js'|theme}}"></script>
<script src="{{'assets/js/plugins.js'|theme}}"></script>
<!-- Main JS -->
<script src="{{'assets/js/main.js'|theme}}"></script>
{% framework %}
{% scripts %} %}
</body>
</html>

View File

@ -1,14 +1,15 @@
url = "/"
layout = "main"
title = "Home"
==
[products]
==
{% partial 'home/slider' %}
{% partial 'home/banner' %}
{% partial 'home/banner2' %}
<button data-request="onFetchProduct">TESTQ</button>
{% partial 'home/new-products' header='New Products' %}
{% partial 'home/banner-mix' %}
@ -23,7 +24,7 @@ title = "Home"
<div class="col-md-12">
<div class="ltn__banner-item">
<div class="ltn__banner-img">
<a href="shop.html"><img src="{{'assets/img/banner/10.jpg'|theme}}" alt="Banner Image"></a>
<a href="/"><img src="{{'banner-bottom/10.jpg'|media}}" alt="Nurgul"></a>
</div>
</div>
</div>

View File

@ -7,7 +7,7 @@
<div class="col-md-6 col-12">
<div class="footer-copyright-left">
<div class="ltn__copyright-design clearfix">
<p>&copy; <span class="current-year"></span> - Just For You</p>
<p>&copy; <span class="current-year"></span> - {{'footer.privacy'|_}}</p>
</div>
</div>
</div>
@ -15,20 +15,20 @@
<div class="footer-copyright-right text-right">
<div class="ltn__copyright-menu d-none">
<ul>
<li><a href="index.html#">Terms & Conditions</a></li>
<li><a href="index.html#">Claim</a></li>
<li><a href="index.html#">Privacy & Policy</a></li>
<li><a href="#">Terms & Conditions</a></li>
<li><a href="#">Claim</a></li>
<li><a href="#">Privacy & Policy</a></li>
</ul>
</div>
<div class="ltn__social-media ">
<ul>
<li><a href="index.html#" title="Facebook"><i
<li><a href="" title="Facebook"><i
class="icon-social-facebook"></i></a></li>
<li><a href="index.html#" title="Twitter"><i
<li><a href="#" title="Twitter"><i
class="icon-social-twitter"></i></a></li>
<li><a href="index.html#" title="Pinterest"><i
<li><a href="#" title="Pinterest"><i
class="icon-social-pinterest"></i></a></li>
<li><a href="index.html#" title="Instagram"><i
<li><a href="#" title="Instagram"><i
class="icon-social-instagram"></i></a></li>
</ul>
</div>

View File

@ -5,14 +5,14 @@
<div class="col-md-6">
<div class="ltn__banner-item">
<div class="ltn__banner-img">
<a href="shop.html"><img src="{{'assets/img/banner/6.jpg'|theme}}" alt="Banner Image"></a>
<a href="/"><img src="{{'banner-center/8.jpg'|media}}" alt="Nurgul"></a>
</div>
</div>
</div>
<div class="col-md-6">
<div class="ltn__banner-item">
<div class="ltn__banner-img">
<a href="shop.html"><img src="{{'assets/img/banner/7.jpg'|theme}}" alt="Banner Image"></a>
<a href="/"><img src="{{'banner-center/9.jpg'|media}}" alt="Nurgul"></a>
</div>
</div>
</div>

View File

@ -10,8 +10,8 @@
<img src="{{'assets/img/icons/svg/8-trolley.svg'|theme}}" alt="#">
</div>
<div class="ltn__feature-info">
<h4>Free shipping</h4>
<p>On all orders over $49.00</p>
<h4>{{'home.free.shipping.title'|_}}</h4>
<p>{{'home.free.shipping.txt'|_}}</p>
</div>
</div>
<div class="ltn__feature-item ltn__feature-item-8">
@ -19,8 +19,8 @@
<img src="{{'assets/img/icons/svg/9-money.svg'|theme}}" alt="#">
</div>
<div class="ltn__feature-info">
<h4>15 days returns</h4>
<p>Moneyback guarantee</p>
<h4>{{'home.title.second'|_}}</h4>
<p>{{'home.desc.second'|_}}</p>
</div>
</div>
<div class="ltn__feature-item ltn__feature-item-8">
@ -28,8 +28,8 @@
<img src="{{'assets/img/icons/svg/10-credit-card.svg'|theme}}" alt="#">
</div>
<div class="ltn__feature-info">
<h4>Secure checkout</h4>
<p>Protected by Paypal</p>
<h4>{{'home.title.third'|_}}</h4>
<p>{{'home.desc.third'|_}}</p>
</div>
</div>
<div class="ltn__feature-item ltn__feature-item-8">
@ -37,8 +37,8 @@
<img src="{{'assets/img/icons/svg/11-gift-card.svg'|theme}}" alt="#">
</div>
<div class="ltn__feature-info">
<h4>Offer & gift here</h4>
<p>On all orders over</p>
<h4>{{'home.title.fourth'|_}}</h4>
<p>{{'home.desc.fourth'|_}}</p>
</div>
</div>
</div>

View File

@ -5,21 +5,21 @@
<div class="col-lg-4 col-md-6">
<div class="ltn__banner-item">
<div class="ltn__banner-img">
<a href="shop.html"><img src="{{'assets/img/banner/1.jpg'|theme}}" alt="Banner Image"></a>
<a href="/"><img src="{{'banner/1.jpg'|media}}" alt="Nurgul"></a>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="ltn__banner-item">
<div class="ltn__banner-img">
<a href="shop.html"><img src="{{'assets/img/banner/2.jpg'|theme}}" alt="Banner Image"></a>
<a href="/"><img src="{{'banner/2.jpg'|media}}" alt="Nurgul"></a>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<div class="ltn__banner-item">
<div class="ltn__banner-img">
<a href="shop.html"><img src="{{'assets/img/banner/3.jpg'|theme}}" alt="Banner Image"></a>
<a href="/"><img src="{{'banner/3.jpg'|media}}" alt="Nurgul"></a>
</div>
</div>
</div>

View File

@ -1,5 +1,5 @@
<div class="col-lg-12">
<div class="ltn__brand-logo-item">
<img src="{{'assets/img/brand-logo/1.png'|theme}}" alt="Brand Logo">
<img src="{{img|media}}" alt="Brand Logo">
</div>
</div>

View File

@ -1,18 +1,29 @@
[builderList]
modelClass = "Romanah\Bagisto\Models\Brand"
scope = "-"
scopeValue = "{{ :scope }}"
displayColumn = "id"
noRecordsMessage = "No records found"
detailsPage = "-"
detailsUrlParameter = "id"
pageNumber = "{{ :page }}"
==
{% set records = builderList.records %}
{% set displayColumn = builderList.displayColumn %}
{% set noRecordsMessage = builderList.noRecordsMessage %}
{% set detailsPage = builderList.detailsPage %}
{% set detailsKeyColumn = builderList.detailsKeyColumn %}
{% set detailsUrlParameter = builderList.detailsUrlParameter %}
<!-- BRAND LOGO AREA START -->
<div class="ltn__brand-logo-area ltn__brand-logo-1 section-bg-1 pt-35 pb-35 plr--5">
<div class="container-fluid">
<div class="row ltn__brand-logo-active">
{% partial "home/brand-item" %}
{% partial "home/brand-item" %}
{% partial "home/brand-item" %}
{% partial "home/brand-item" %}
{% partial "home/brand-item" %}
{% partial "home/brand-item" %}
{% partial "home/brand-item" %}
{% partial "home/brand-item" %}
{% partial "home/brand-item" %}
{% partial "home/brand-item" %}
{% for record in records %}
{% partial "home/brand-item" img=record.img %}
{% endfor %}
</div>
</div>

View File

@ -1,9 +1,28 @@
[builderList]
modelClass = "Romanah\Bagisto\Models\Slider"
scope = "-"
scopeValue = "{{ :scope }}"
displayColumn = "id"
noRecordsMessage = "No records found"
detailsPage = "-"
detailsUrlParameter = "id"
pageNumber = "{{ :page }}"
==
{% set records = builderList.records %}
{% set displayColumn = builderList.displayColumn %}
{% set noRecordsMessage = builderList.noRecordsMessage %}
{% set detailsPage = builderList.detailsPage %}
{% set detailsKeyColumn = builderList.detailsKeyColumn %}
{% set detailsUrlParameter = builderList.detailsUrlParameter %}
<!-- SLIDER AREA START (slider-6) -->
<div class="ltn__slider-area ltn__slider-3 ltn__slider-6 section-bg-1">
<div class="ltn__slide-one-active slick-slide-arrow-1 slick-slide-dots-1 arrow-white---">
<!-- ltn__slide-item -->
{% for record in records %}
<div class="ltn__slide-item ltn__slide-item-8 text-color-white---- bg-image bg-overlay-theme-black-80---"
data-bs-bg="{{'assets/img/slider/1.jpg'|theme}}">
data-bs-bg="{{record.img|media}}">
<div class="ltn__slide-item-inner">
<div class="container">
<div class="row">
@ -12,16 +31,15 @@
<div class="slide-item-info-inner ltn__slide-animation">
<div class="slide-item-info">
<div class="slide-item-info-inner ltn__slide-animation">
<h1 class="slide-title animated ">{{'Fresh Flower'|_}}</h1>
<h1 class="slide-title animated ">{{record.header}}</h1>
<h6 class="slide-sub-title ltn__body-color slide-title-line animated">
Natural & Beautiful Flower Here</h6>
{{record.header2}}</h6>
<div class="slide-brief animated">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed
do eiusmod tempor incididunt ut labore.</p>
<p>{{record.txt}}</p>
</div>
<div class="btn-wrapper animated">
<a href="https://tunatheme.com/tf/html/fiama-preview/fiama/service.html"
class="theme-btn-1 btn btn-round">Shop Now</a>
<a href="{{record.url}}"
class="theme-btn-1 btn btn-round">{{record.btn_txt}}</a>
</div>
</div>
</div>
@ -36,42 +54,10 @@
</div>
</div>
</div>
<!-- ltn__slide-item -->
<div class="ltn__slide-item ltn__slide-item-8 text-color-white---- bg-image bg-overlay-theme-black-80---"
data-bs-bg="{{'assets/img/slider/3.jpg'|theme}}">
<div class="ltn__slide-item-inner">
<div class="container">
<div class="row">
<div class="col-lg-12 align-self-center">
<div class="slide-item-info">
<div class="slide-item-info-inner ltn__slide-animation">
<div class="slide-item-info">
<div class="slide-item-info-inner ltn__slide-animation">
<h1 class="slide-title animated ">Fresh Flower</h1>
<h6 class="slide-sub-title ltn__body-color slide-title-line animated">
Natural & Beautiful Flower Here</h6>
<div class="slide-brief animated">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed
do eiusmod tempor incididunt ut labore.</p>
</div>
<div class="btn-wrapper animated">
<a href="https://tunatheme.com/tf/html/fiama-preview/fiama/service.html"
class="theme-btn-1 btn btn-round">Shop Now</a>
</div>
</div>
</div>
</div>
</div>
<!-- <div class="slide-item-img">
<img src="img/slider/41-1.png" alt="#">
<span class="call-to-circle-1"></span>
</div> -->
</div>
</div>
</div>
</div>
</div>
<!-- -->
{% endfor %}
</div>
</div>
<!-- SLIDER AREA END -->