-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
OFFLINE
committed
May 28, 2023
1 parent
8b55bb6
commit 4004551
Showing
107 changed files
with
7,714 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
Copyright (C) OFFLINE GmbH - All Rights Reserved. | ||
|
||
THE CONTENTS OF THIS PROJECT ARE PROPRIETARY. | ||
|
||
You may use this plugin in October CMS based projects only. You are allowed to distribute this plugin to your clients | ||
as part of your work. You are not allowed to resell this plugin or any part of it on its own. | ||
|
||
The pro version of this plugin requires a valid October CMS plugin license from the October CMS marketplace for every project. | ||
|
||
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 non infringement. | ||
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. | ||
|
||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,349 @@ | ||
<?php | ||
|
||
namespace OFFLINE\Boxes; | ||
|
||
use Backend; | ||
use Cms\Classes\Controller as CmsController; | ||
use Illuminate\Support\Facades\App; | ||
use Illuminate\Support\Facades\Cache; | ||
use Illuminate\Support\Facades\Lang; | ||
use October\Rain\Database\Scopes\MultisiteScope; | ||
use October\Rain\Support\Facades\Block; | ||
use October\Rain\Support\Facades\Config; | ||
use October\Rain\Support\Facades\Event; | ||
use October\Rain\Support\Facades\Site; | ||
use OFFLINE\Boxes\Classes\CMS\CmsPageParams; | ||
use OFFLINE\Boxes\Classes\CMS\Controller; | ||
use OFFLINE\Boxes\Classes\Features; | ||
use OFFLINE\Boxes\Classes\Partial\ExternalPartial; | ||
use OFFLINE\Boxes\Classes\Partial\Partial; | ||
use OFFLINE\Boxes\Classes\PatchedTreeCollection; | ||
use OFFLINE\Boxes\Classes\Search\SiteSearch; | ||
use OFFLINE\Boxes\Components\BoxesPage; | ||
use OFFLINE\Boxes\Components\BoxesPageEditor; | ||
use OFFLINE\Boxes\FormWidgets\BoxesDataForm; | ||
use OFFLINE\Boxes\FormWidgets\BoxesEditor; | ||
use OFFLINE\Boxes\FormWidgets\BoxFinder; | ||
use OFFLINE\Boxes\FormWidgets\HiddenInput; | ||
use OFFLINE\Boxes\Models\Box; | ||
use OFFLINE\Boxes\Models\BoxesSetting; | ||
use OFFLINE\Boxes\Models\Content; | ||
use OFFLINE\Boxes\Models\Page; | ||
use System\Classes\PluginBase; | ||
use Url; | ||
|
||
/** | ||
* Plugin Information File | ||
*/ | ||
class Plugin extends PluginBase | ||
{ | ||
public function __construct($app) | ||
{ | ||
parent::__construct($app); | ||
|
||
// RainLab.Translate is an optional dependency. | ||
// But if it is enabled, we need to require it to make sure | ||
// that all migrations are run in the right order. | ||
if (class_exists(\RainLab\Translate\Plugin::class)) { | ||
$this->require[] = 'RainLab.Translate'; | ||
} | ||
} | ||
|
||
/** | ||
* Returns information about this plugin. | ||
* | ||
* @return array | ||
*/ | ||
public function pluginDetails() | ||
{ | ||
return [ | ||
'name' => 'Boxes Free', | ||
'description' => 'Visual Page Builder for October CMS', | ||
'author' => 'OFFLINE', | ||
'icon' => 'icon-cube', | ||
]; | ||
} | ||
|
||
/** | ||
* Register method, called when the plugin is first registered. | ||
* | ||
* @return void | ||
*/ | ||
public function register() | ||
{ | ||
|
||
} | ||
|
||
/** | ||
* Boot method, called right before the request route. | ||
* | ||
* @return void | ||
*/ | ||
public function boot() | ||
{ | ||
$basePath = App::basePath(); | ||
|
||
Event::listen('cms.page.beforeRenderPartial', function ($controller, $partial) use ($basePath) { | ||
// Allow external partials to be loaded by the CMS. This allows | ||
// third-party plugins to provide custom partials for the plugin. | ||
if (starts_with($partial, Partial::EXTERNAL_PREFIX)) { | ||
$filename = str_replace(Partial::EXTERNAL_PREFIX, '', $partial); | ||
|
||
if (file_exists($filename) && starts_with($filename, $basePath)) { | ||
return ExternalPartial::load('', $filename); | ||
} | ||
} | ||
}); | ||
|
||
// Dynamically create a CMS page that is available via the Controller::PREVIEW_URL. | ||
Event::listen('cms.router.beforeRoute', function ($url) { | ||
// Ignore the route prefix if it is set. | ||
$site = Site::getSiteFromContext(); | ||
|
||
if ($site->is_prefixed) { | ||
$url = str_replace($site->route_prefix, '', $url); | ||
} | ||
|
||
if (str_contains($url, Controller::PREVIEW_URL) && Backend\Facades\BackendAuth::getUser()) { | ||
return Controller::instance()->getPreviewPage($url); | ||
} | ||
|
||
return Controller::instance()->getCmsPageForUrl($url); | ||
}); | ||
|
||
// If the visited page is a boxes page, replace the page content with the rendered boxes. | ||
Event::listen('cms.page.beforeRenderPage', function (CmsController $controller, $page) { | ||
$isBoxesPage = isset($page->apiBag[CmsPageParams::BOXES_PAGE_ID]); | ||
|
||
// If the current page is a boxes page, return the rendered content. | ||
if ($isBoxesPage) { | ||
$isEditor = isset($page->apiBag[CmsPageParams::BOXES_IS_EDITOR]) || $this->isEditModeRequest(); | ||
|
||
return $controller->renderComponent($isEditor ? 'boxesPageEditor' : 'boxesPage'); | ||
} | ||
|
||
return ''; | ||
}); | ||
|
||
// Add the BoxList component to any page that serves a boxes page. | ||
Event::listen('cms.page.init', function (CmsController $controller) { | ||
$page = $controller->getPage(); | ||
|
||
if (isset($page->apiBag[CmsPageParams::BOXES_PAGE_ID])) { | ||
$isEditor = isset($page->apiBag[CmsPageParams::BOXES_IS_EDITOR]) || $this->isEditModeRequest(); | ||
|
||
$controller->addComponent( | ||
$isEditor ? BoxesPageEditor::class : BoxesPage::class, | ||
$isEditor ? 'boxesPageEditor' : 'boxesPage', | ||
[ | ||
'id' => $page->apiBag[CmsPageParams::BOXES_PAGE_ID] ?? 0, | ||
'modelType' => $page->apiBag[CmsPageParams::BOXES_MODEL_TYPE] ?? Page::class, | ||
], | ||
true | ||
); | ||
} | ||
}); | ||
|
||
$this->registerPageFinder(); | ||
|
||
// OFFLINE.SiteSearch | ||
Event::listen('offline.sitesearch.extend', fn () => new SiteSearch()); | ||
|
||
// Multi-site support | ||
Event::listen('cms.sitePicker.overridePattern', function ($page, $pattern, $currentSite, $proposedSite) { | ||
if (!isset($page->apiBag[CmsPageParams::BOXES_PAGE_ID]) || $page->apiBag[CmsPageParams::BOXES_MODEL_TYPE] !== Page::class) { | ||
return; | ||
} | ||
|
||
$boxesPage = Page::withoutGlobalScope(MultisiteScope::class) | ||
->findOrFail( | ||
$page->apiBag[CmsPageParams::BOXES_PAGE_ID] | ||
); | ||
|
||
return Cache::rememberForever( | ||
Page::multisiteCacheKey($boxesPage->id, $proposedSite->id), | ||
fn () => $boxesPage->findForSite($proposedSite->id)?->url, | ||
); | ||
}); | ||
|
||
// Add Seeder behavior to the models if the Seeder plugin is installed. | ||
if (class_exists(\OFFLINE\Seeder\Behaviors\HasSeederFactoryBehavior::class)) { | ||
Box::extend(static function ($model) { | ||
$model->implement[] = \OFFLINE\Seeder\Behaviors\HasSeederFactoryBehavior::class; | ||
}); | ||
|
||
if (class_exists(\OFFLINE\Boxes\Models\Content::class)) { | ||
Content::extend(static function ($model) { | ||
$model->implement[] = \OFFLINE\Seeder\Behaviors\HasSeederFactoryBehavior::class; | ||
}); | ||
} | ||
} | ||
|
||
if (App::runningInBackend()) { | ||
// Inject global CSS styles. | ||
Block::set('head', '<link href="' . Url::to('plugins/offline/boxes/assets/css/offline.boxes.backend.css') . '" rel="stylesheet">'); | ||
} | ||
} | ||
|
||
/** | ||
* Registers any front-end components implemented in this plugin. | ||
* | ||
* @return array | ||
*/ | ||
public function registerComponents() | ||
{ | ||
return [ | ||
BoxesPage::class => 'boxesPage', | ||
BoxesPageEditor::class => 'boxesPageEditor', | ||
]; | ||
} | ||
|
||
public function registerFormWidgets() | ||
{ | ||
return [ | ||
HiddenInput::class => 'hidden', | ||
BoxesDataForm::class => 'boxesdataform', | ||
BoxesEditor::class => 'boxes', | ||
BoxFinder::class => 'boxfinder', | ||
]; | ||
} | ||
|
||
|
||
|
||
/** | ||
* Registers backend navigation items for this plugin. | ||
* | ||
* @return array | ||
*/ | ||
public function registerNavigation() | ||
{ | ||
$label = BoxesSetting::get('main_menu_label'); | ||
|
||
if (!$label) { | ||
$label = Lang::get('offline.boxes::lang.content'); | ||
} | ||
|
||
$counter = 0; | ||
|
||
if (Features::instance()->revisions && method_exists(Page::class, 'getUnpublishedDraftCount')) { | ||
$counter = Page::getUnpublishedDraftCount(); | ||
} | ||
|
||
return [ | ||
'boxes' => [ | ||
'label' => $label, | ||
'url' => Backend::url('offline/boxes/editorcontroller'), | ||
'iconSvg' => '/plugins/offline/boxes/assets/img/cube.svg', | ||
'permissions' => ['offline.boxes.access_editor'], | ||
'order' => Config::get('offline.boxes::config.main_menu_order', 500), | ||
'counter' => $counter, | ||
'counterLabel' => Lang::get('offline.boxes::lang.unpublished_changes'), | ||
], | ||
]; | ||
} | ||
|
||
/** | ||
* Register new Twig variables | ||
* @return array | ||
*/ | ||
public function registerMarkupTags() | ||
{ | ||
return [ | ||
'filters' => [ | ||
'boxesPage' => [Page::class, 'getAttributeWhereSlug'], | ||
], | ||
]; | ||
} | ||
|
||
public function registerPermissions() | ||
{ | ||
return [ | ||
'offline.boxes.manage_settings' => [ | ||
'label' => trans('offline.boxes::lang.permissions.manage_settings'), | ||
'tab' => 'Boxes', | ||
], | ||
'offline.boxes.access_editor' => [ | ||
'label' => trans('offline.boxes::lang.permissions.access_editor'), | ||
'tab' => 'Boxes', | ||
], | ||
]; | ||
} | ||
|
||
public function registerSettings() | ||
{ | ||
return [ | ||
'boxes' => [ | ||
'label' => Lang::get('offline.boxes::lang.settings_label'), | ||
'description' => Lang::get('offline.boxes::lang.settings_description'), | ||
'category' => 'Boxes', | ||
'icon' => 'icon-cube', | ||
'class' => BoxesSetting::class, | ||
'order' => 500, | ||
'keywords' => 'boxes', | ||
'permissions' => ['offline.boxes.manage_settings'], | ||
], | ||
]; | ||
} | ||
|
||
protected function isEditModeRequest() | ||
{ | ||
return get(Controller::PREVIEW_PARAM) && Backend\Facades\BackendAuth::getUser(); | ||
} | ||
|
||
/** | ||
* Add support for RainLab.Pages and the integrated Page Finder. | ||
*/ | ||
protected function registerPageFinder(): void | ||
{ | ||
$listTypes = function () { | ||
return [ | ||
Page::MENU_TYPE_PAGES => 'OFFLINE.Boxes: ' . trans('offline.boxes::lang.pages'), | ||
Page::MENU_TYPE_ALL_PAGES => 'OFFLINE.Boxes: ' . trans('offline.boxes::lang.all_pages'), | ||
]; | ||
}; | ||
|
||
$getTypeInfo = function ($type) { | ||
if ($type === Page::MENU_TYPE_PAGES) { | ||
$refs = Page::query() | ||
->with([ | ||
'children' => fn ($q) => $q->where('url', '<>', ''), | ||
]) | ||
->get() | ||
->pipe(fn ($pages) => new PatchedTreeCollection($pages)) | ||
->listsNested('name', 'slug', ' - '); | ||
|
||
return [ | ||
'references' => $refs, | ||
'nesting' => true, | ||
'dynamicItems' => true, | ||
]; | ||
} | ||
|
||
if ($type === Page::MENU_TYPE_ALL_PAGES) { | ||
return [ | ||
'nesting' => true, | ||
'dynamicItems' => true, | ||
]; | ||
} | ||
|
||
return []; | ||
}; | ||
|
||
$resolveItem = function ($type, $item, $url) { | ||
if ($type === Page::MENU_TYPE_PAGES || $type === Page::MENU_TYPE_ALL_PAGES) { | ||
return Page::resolveMenuItem($item, $url); | ||
} | ||
|
||
return null; | ||
}; | ||
|
||
Event::listen('pages.menuitem.listTypes', $listTypes); | ||
Event::listen('cms.pageLookup.listTypes', $listTypes); | ||
|
||
Event::listen('pages.menuitem.getTypeInfo', $getTypeInfo); | ||
Event::listen('cms.pageLookup.getTypeInfo', $getTypeInfo); | ||
|
||
Event::listen('pages.menuitem.resolveItem', $resolveItem); | ||
Event::listen('cms.pageLookup.resolveItem', $resolveItem); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
|
||
|
||
/** Style the Boxes counter less aggressive */ | ||
.mainmenu-item [data-menu-id="boxes"].counter { | ||
background: rgba(255, 255, 255, .4); | ||
padding: 2px 4px; | ||
font-weight: bold; | ||
color: white; | ||
} | ||
|
||
.navbar-mode-inline .mainmenu-item [data-menu-id="boxes"].counter { | ||
position: absolute; | ||
left: 12px; | ||
top: auto; | ||
bottom: 4px; | ||
backdrop-filter: blur(4px); | ||
} |
Oops, something went wrong.