feat: VivesPOS landing on Winter CMS 1.2 — theme + plugin + Dockerfile
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run
- Base: wintercms/winter branch 1.2 (full framework) - Theme vivespos: Canvas 7 + Bootstrap 5 CDN, custom CSS - Layout: deferred GTM/GA4 tracking, JSON-LD SoftwareApplication - Partials: hero (offline-first), features, modes (offline/nube toggle), screenshots, pricing (3 planes), comparison, FAQ, CTA - Plugin VivesPOS.Site with ContactForm - Dockerfile: PHP 8.2 Apache, port 80, healthcheck - Added winter/wn-pages, blog, sitemap, seo plugins - Active theme set to vivespos
This commit is contained in:
415
modules/cms/widgets/TemplateList.php
Normal file
415
modules/cms/widgets/TemplateList.php
Normal file
@@ -0,0 +1,415 @@
|
||||
<?php namespace Cms\Widgets;
|
||||
|
||||
use Str;
|
||||
use File;
|
||||
use Input;
|
||||
use Request;
|
||||
use Cms\Classes\Theme;
|
||||
use Backend\Classes\WidgetBase;
|
||||
|
||||
/**
|
||||
* Template list widget.
|
||||
* This widget displays templates of different types.
|
||||
*
|
||||
* @package winter\wn-cms-module
|
||||
* @author Alexey Bobkov, Samuel Georges
|
||||
*/
|
||||
class TemplateList extends WidgetBase
|
||||
{
|
||||
const SORTING_FILENAME = 'fileName';
|
||||
|
||||
use \Backend\Traits\SelectableWidget;
|
||||
use \Backend\Traits\CollapsableWidget;
|
||||
|
||||
protected $searchTerm = false;
|
||||
|
||||
protected $dataSource;
|
||||
|
||||
protected $theme;
|
||||
|
||||
/**
|
||||
* @var string object property to use as a title.
|
||||
*/
|
||||
public $titleProperty;
|
||||
|
||||
/**
|
||||
* @var array a list of object properties to use in the description area.
|
||||
* The array should include the property names and corresponding titles:
|
||||
* ['url'=>'URL']
|
||||
*/
|
||||
public $descriptionProperties = [];
|
||||
|
||||
/**
|
||||
* @var string object property to use as a description.
|
||||
*/
|
||||
public $descriptionProperty;
|
||||
|
||||
/**
|
||||
* @var string Message to display when there are no records in the list.
|
||||
*/
|
||||
public $noRecordsMessage = 'cms::lang.template.no_list_records';
|
||||
|
||||
/**
|
||||
* @var string Message to display when the Delete button is clicked.
|
||||
*/
|
||||
public $deleteConfirmation = 'cms::lang.template.delete_confirm';
|
||||
|
||||
/**
|
||||
* @var string Specifies the item type.
|
||||
*/
|
||||
public $itemType;
|
||||
|
||||
/**
|
||||
* @var string Extra CSS class name to apply to the control.
|
||||
*/
|
||||
public $controlClass;
|
||||
|
||||
/**
|
||||
* @var string A list of file name patterns to suppress / hide.
|
||||
*/
|
||||
public $ignoreDirectories = [];
|
||||
|
||||
/**
|
||||
* @var boolean Defines sorting properties.
|
||||
* The sorting feature is disabled if there are no sorting properties defined.
|
||||
*/
|
||||
public $sortingProperties = [];
|
||||
|
||||
/*
|
||||
* Public methods
|
||||
*/
|
||||
|
||||
public function __construct($controller, $alias, callable $dataSource)
|
||||
{
|
||||
$this->alias = $alias;
|
||||
$this->dataSource = $dataSource;
|
||||
$this->theme = Theme::getEditTheme();
|
||||
$this->selectionInputName = 'template';
|
||||
$this->collapseSessionKey = $this->getThemeSessionKey('groups');
|
||||
|
||||
parent::__construct($controller, []);
|
||||
|
||||
if (!Request::isXmlHttpRequest()) {
|
||||
$this->resetSelection();
|
||||
}
|
||||
|
||||
$configFile = 'config_' . snake_case($alias) .'.yaml';
|
||||
$config = $this->makeConfig($configFile);
|
||||
|
||||
foreach ($config as $field => $value) {
|
||||
if (property_exists($this, $field)) {
|
||||
$this->$field = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$this->bindToController();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the widget.
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$toolbarClass = Str::contains($this->controlClass, 'hero') ? 'separator' : null;
|
||||
|
||||
$this->vars['toolbarClass'] = $toolbarClass;
|
||||
|
||||
return $this->makePartial('body', [
|
||||
'data' => $this->getData()
|
||||
]);
|
||||
}
|
||||
|
||||
/*
|
||||
* Event handlers
|
||||
*/
|
||||
|
||||
public function onSearch()
|
||||
{
|
||||
$this->setSearchTerm(Input::get('search'));
|
||||
$this->extendSelection();
|
||||
|
||||
return $this->updateList();
|
||||
}
|
||||
|
||||
public function onUpdate()
|
||||
{
|
||||
$this->extendSelection();
|
||||
|
||||
return $this->updateList();
|
||||
}
|
||||
|
||||
public function onApplySorting()
|
||||
{
|
||||
$this->setSortingProperty(Input::get('sortProperty'));
|
||||
|
||||
$result = $this->updateList();
|
||||
$result['#'.$this->getId('sorting-options')] = $this->makePartial('sorting-options');
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
//
|
||||
// Methods for the internal use
|
||||
//
|
||||
|
||||
protected function getData()
|
||||
{
|
||||
/*
|
||||
* Load the data
|
||||
*/
|
||||
$items = call_user_func($this->dataSource);
|
||||
|
||||
if ($items instanceof \Winter\Storm\Support\Collection) {
|
||||
$items = $items->all();
|
||||
}
|
||||
|
||||
$items = $this->removeIgnoredDirectories($items);
|
||||
|
||||
$items = array_map([$this, 'normalizeItem'], $items);
|
||||
|
||||
$this->sortItems($items);
|
||||
|
||||
/*
|
||||
* Apply the search
|
||||
*/
|
||||
$filteredItems = [];
|
||||
$searchTerm = Str::lower($this->getSearchTerm());
|
||||
|
||||
if (strlen($searchTerm)) {
|
||||
/*
|
||||
* Exact
|
||||
*/
|
||||
foreach ($items as $index => $item) {
|
||||
if ($this->itemContainsWord($searchTerm, $item, true)) {
|
||||
$filteredItems[] = $item;
|
||||
unset($items[$index]);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Fuzzy
|
||||
*/
|
||||
$words = explode(' ', $searchTerm);
|
||||
foreach ($items as $item) {
|
||||
if ($this->itemMatchesSearch($words, $item)) {
|
||||
$filteredItems[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$filteredItems = $items;
|
||||
}
|
||||
|
||||
/*
|
||||
* Group the items
|
||||
*/
|
||||
$result = [];
|
||||
$foundGroups = [];
|
||||
foreach ($filteredItems as $itemData) {
|
||||
$pos = strpos($itemData->fileName, '/');
|
||||
|
||||
if ($pos !== false) {
|
||||
$group = substr($itemData->fileName, 0, $pos);
|
||||
if (!array_key_exists($group, $foundGroups)) {
|
||||
$newGroup = (object)[
|
||||
'title' => $group,
|
||||
'items' => []
|
||||
];
|
||||
|
||||
$foundGroups[$group] = $newGroup;
|
||||
}
|
||||
|
||||
$foundGroups[$group]->items[] = $itemData;
|
||||
}
|
||||
else {
|
||||
$result[] = $itemData;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort folders by name regardless of the
|
||||
// selected sorting options.
|
||||
ksort($foundGroups);
|
||||
|
||||
foreach ($foundGroups as $group) {
|
||||
$result[] = $group;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function sortItems(&$items)
|
||||
{
|
||||
$sortingProperty = $this->getSortingProperty();
|
||||
|
||||
usort($items, function ($a, $b) use ($sortingProperty) {
|
||||
return strcmp($a->$sortingProperty, $b->$sortingProperty);
|
||||
});
|
||||
}
|
||||
|
||||
protected function removeIgnoredDirectories($items)
|
||||
{
|
||||
if (!$this->ignoreDirectories) {
|
||||
return $items;
|
||||
}
|
||||
|
||||
$ignoreCache = [];
|
||||
|
||||
$items = array_filter($items, function ($item) use (&$ignoreCache) {
|
||||
$fileName = $item->getBaseFileName();
|
||||
$dirName = dirname($fileName);
|
||||
|
||||
if (isset($ignoreCache[$dirName])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->ignoreDirectories as $ignoreDir) {
|
||||
if (File::fileNameMatch($dirName, $ignoreDir)) {
|
||||
$ignoreCache[$dirName] = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
protected function normalizeItem($item)
|
||||
{
|
||||
$description = null;
|
||||
if ($descriptionProperty = $this->descriptionProperty) {
|
||||
$description = $item->$descriptionProperty;
|
||||
}
|
||||
|
||||
$descriptions = [];
|
||||
foreach ($this->descriptionProperties as $property => $title) {
|
||||
if ($item->$property) {
|
||||
$descriptions[$title] = $item->$property;
|
||||
}
|
||||
}
|
||||
|
||||
$result = [
|
||||
'title' => $this->getItemTitle($item),
|
||||
'fileName' => $item->getFileName(),
|
||||
'description' => $description,
|
||||
'descriptions' => $descriptions,
|
||||
'dragValue' => $this->getItemDragValue($item)
|
||||
];
|
||||
|
||||
foreach ($this->sortingProperties as $property => $name) {
|
||||
$result[$property] = $item->$property;
|
||||
}
|
||||
|
||||
return (object) $result;
|
||||
}
|
||||
|
||||
protected function getItemDragValue($item)
|
||||
{
|
||||
if ($item instanceof \Cms\Classes\Partial) {
|
||||
return "{% partial '".$item->getBaseFileName()."' %}";
|
||||
}
|
||||
|
||||
if ($item instanceof \Cms\Classes\Content) {
|
||||
return "{% content '".$item->getBaseFileName()."' %}";
|
||||
}
|
||||
|
||||
if ($item instanceof \Cms\Classes\Page) {
|
||||
return "{{ '".$item->getBaseFileName()."'|page }}";
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
protected function getItemTitle($item)
|
||||
{
|
||||
$titleProperty = $this->titleProperty;
|
||||
|
||||
if ($titleProperty) {
|
||||
return $item->$titleProperty ?: basename($item->getFileName());
|
||||
}
|
||||
|
||||
return basename($item->getFileName());
|
||||
}
|
||||
|
||||
protected function setSearchTerm($term)
|
||||
{
|
||||
$this->searchTerm = trim($term);
|
||||
$this->putSession('search', $this->searchTerm);
|
||||
}
|
||||
|
||||
protected function getSearchTerm()
|
||||
{
|
||||
return $this->searchTerm !== false ? $this->searchTerm : $this->getSession('search');
|
||||
}
|
||||
|
||||
protected function updateList()
|
||||
{
|
||||
return [
|
||||
'#'.$this->getId('template-list') => $this->makePartial('items', ['items' => $this->getData()])
|
||||
];
|
||||
}
|
||||
|
||||
protected function itemMatchesSearch($words, $item)
|
||||
{
|
||||
foreach ($words as $word) {
|
||||
$word = trim($word);
|
||||
if (!strlen($word)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$this->itemContainsWord($word, $item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function itemContainsWord($word, $item, $exact = false)
|
||||
{
|
||||
$operator = $exact ? 'is' : 'contains';
|
||||
|
||||
if (strlen($item->title) && Str::$operator(Str::lower($item->title), $word)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Str::$operator(Str::lower($item->fileName), $word)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Str::$operator(Str::lower($item->description), $word) && strlen($item->description)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($item->descriptions as $value) {
|
||||
if (Str::$operator(Str::lower($value), $word) && strlen($value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getThemeSessionKey($prefix)
|
||||
{
|
||||
return $prefix.$this->theme->getDirName();
|
||||
}
|
||||
|
||||
protected function getSortingProperty()
|
||||
{
|
||||
$property = $this->getSession($this->getThemeSessionKey('sorting_property'), self::SORTING_FILENAME);
|
||||
|
||||
if (!array_key_exists($property, $this->sortingProperties)) {
|
||||
return self::SORTING_FILENAME;
|
||||
}
|
||||
|
||||
return $property;
|
||||
}
|
||||
|
||||
protected function setSortingProperty($property)
|
||||
{
|
||||
$this->putSession($this->getThemeSessionKey('sorting_property'), $property);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user