v0.95.82 2023-11-13 10:06 UTC

README

Latest Version on Packagist Total Downloads Build Status

该包是Voyager 管理面板包的基本内容组织的一个简单但有用的子系统实现。使用此包,您可以快速轻松地构建小型和中型网站。

功能

  • 基本页面管理模块,包括系统页面。
  • 基本 SEO 实现 - SEO 标题、Meta 描述和 Meta 关键字参数。
  • 面包屑实现。
  • 灵活的区块/小部件管理模块,用于构建网站页面的一部分。
  • 表单管理系统,包括 AJAX 发送。
  • Liquid 模板子系统用于区块/小部件和表单。
  • 使用灵活的区块/小部件构建页面布局。
  • 短代码集成。
  • 从数据库表进行本地化,您可以在其中轻松保存和管理您的翻译。
  • 高级网站设置模块,包括 SMTP 邮件设置和开箱即用的邮件发送测试。

安装

要求

在之前,您应该完全安装Voyager 扩展包。

通过 Composer

$ composer require monstrex/voyager-site

然后运行安装命令

$ php artisan voyager-site:install

如果需要,发布配置

$ php artisan vendor:publish --provider="MonstreX\VoyagerSite\VoyagerSiteServiceProvider" --tag="config"

用法

配置文件

  • route_home_page - 主页的路由名称(默认为 'home')。
  • default_model_table - 查找记录的默认模型表名称(默认为 'pages')。
  • default_slug_field - 默认 slug 字段名称(默认为 'slug')。
  • use_legacy_error_handler - 如果为 false,将使用 voyager-site 404 错误处理器。
  • template - 存储视图模板文件的根文件夹名称(默认为 'template')。
  • template_master - 主模板名称(默认为 'layouts.master')。
  • template_layout - 主要布局模板名称(默认为 'layouts.main')。
  • template_page - 页面模板名称(默认为 'pages.page')。
  • template_filters - 添加自定义 Liquid 过滤器或 Null 的类。

网站设置

网站设置是一个灵活可扩展且易于使用的设置子系统,集成在包中。

Site settings

您可以使用和修改现有设置,以及添加您自己的组和设置。要检索特定设置,您可以使用辅助函数

$mail = site_setting('mail.to_address');

内部包函数使用的某些网站设置将覆盖 Laravel .env 设置。

设置内置了 SMTP 邮件参数和邮件发送测试功能。 网站设置 - 邮件

您可以通过点击编辑操作,使用类似 JSON 的设置字段配置来轻松修改设置或添加新的设置。

{
    "fields": {
        "to_address": {
            "label": "Default address for site emails",
            "type": "text",
            "value": "destination@example.com",
            "class": "col-md-12"
        },
        "section_smtp": {
            "type": "section",
            "icon": "voyager-mail",
            "label": "E-Mail transport"
        },
        "driver": {
            "label": "Mail driver",
            "type": "dropdown",
            "value": "smtp",
            "options": {
                "smtp": "SMTP",
                "mailgun": "MAILGUN",
                "log": "LOG"
            },
            "class": "col-md-12"
        }
    }
}

内置的字段类型有

  • 文本
  • 数字
  • 多行文本框
  • 富文本框(tinyMCE 编辑器)
  • 代码编辑器(ACE 代码编辑器)
  • 复选框
  • 单选按钮
  • 图像(voyager 类型图像)
  • 媒体(图像文件,使用 laravel medialibrary 包)
  • 路由(使用路由名称返回 URL)
  • 部分(没有值,仅用于视觉上分隔字段组)

本地化

本包为所有需要本地化的字符串提供类似数据库的本地化存储。只需将语言列添加到存储中,它将代替文件中的本地化使用。

Localizations

页面

VPage模块允许您管理网站上的基本页面。您可以轻松找到并准备任何页面及其相关属性,例如SEO数据、面包屑和数据源,然后将这些数据集发送到视图。它可以以两种方式使用:将PageTrait类实现到您的控制器中或使用外观VPage。查看示例

示例 #1

namespace App\Http\Controllers;

use MonstreX\VoyagerSite\Traits\PageTrait;

class PagesController extends Controller
{
    use PageTrait;

    public function home()
    {

        $this->create('home');

        return $this->view();

    }
    
    public function show($alias)
    {

        $this->create($alias);

        return $this->view();

    }
    
    public function showArticle($alias)
    {

        $this->create($alias,'articles');

        return $this->view('layout.article', ['vars' => $some_vars]);

    }
}

示例 #2

namespace App\Http\Controllers;

use MonstreX\VoyagerSite\Traits\PageTrait;

use VSite, VPage, VData;

class PagesController extends Controller
{
    public function home()
    {

        VPage::create(VData::find('home'), VSite::getSettings());

        return VPage::view(); 

    }
}

示例执行的操作

  • 在默认(或指定)模型中查找特定记录(默认使用'slug'字段)。如果您使用(int)值,则将通过ID字段查找。如果未找到记录或记录有空的(null)状态字段,则将抛出异常404。
  • 创建一个VPage实例,并用页面数据初始化该实例。
    • 找到的模型对象。
    • 网站设置。
    • 初始化了面包屑。
    • 初始化SEO参数(SEO标题、元描述、元关键词)。
    • 附加数据源,如果details字段有数据源描述。
  • 使用默认(或指定)视图渲染页面。

视图中可用的变量

$template            // Root template folder 
$template_master     // Master template name (to extend View)
$template_page       // Internal page template name
$breadcrumbs         // Breadcrumbs array
$title               // Page title
$banner              // Page banner image (if present)
$page                // Page instance
$parents             // Chain of parents of current page  
$children            // Children pages if present for current page
$data_sources        // Extra Data sources attached to the Page
$seo['title']        // Seo Title
$seo['description']  // Meta Description
$seo['keywords']     // Meta Keywords
$data                // Additional given data

页面数据集

这是一个子系统,用于检索在特定页面上使用所需的附加数据。数据是从不同模型的记录集合中不同的记录。
要使用它,您需要在您的模型中具有details字段,该记录填充了某种JSON数据结构

注意:在调用create方法时,将在JSON结构中定义的附加数据自动加载。

{
    "data_sources": {
        "articles": {
            "model": "Article",
            "where": {
              "status": 1
            }
        },
        "services": {
            "model": "Service",
            "where": {
                "status": 1,
                "featured": 0,
                "type": "main"
            },
            "with": [
              "category",
              "country"
            ],
            "order" : {
                "field" : "order",
                "direction" : "asc"
            }                 
        }
    }
}

在您的视图模板中,您可以轻松地像这样使用它

@foreach($data_sources['articles'] as $article)
    <h2>{{ $article['title'] }}</h2>
@endforeach

注意:此类JSON数据结构也用于块/小部件子系统。

覆盖页面上的默认模板

您可能想覆盖特定页面上的默认模板名称。为此,请在details字段中定义新的模板名称

{
  "template": "my-theme",
  "template_master": "layout.master",
  "template_layout": "layout.main",
  "template_page": "pages.contacts"
}

在控制器中覆盖(使用页面特性)

public function service($alias)
{
    $pageContent = $this->create($alias,'services')->getContent();
    $this->setPageTemplate('pages.service');
    $this->buildBreadcrumbs();
    $this->setChildren('parent_id');
    return $this->view();
}

页面SEO参数

SEO参数是在create页面方法内部生成的。默认情况下

SEO标题 - 从页面SEO字段组,如果适当的字段不为空,否则检查标题字段,如果该字段不存在,则检查网站设置seo.seo_title,如果该设置为空,则从网站设置general.site_title中获取值。最后,给定值通过SEO标题模板网站设置seo.seo_title_template传递。

您可以使用方法VPage::setSeoTitle('new title')覆盖SEO标题值。

元描述 - 从页面SEO字段组,如果适当的字段不为空,否则检查网站设置seo.meta_description,如果该设置为空,则从网站设置general.site_description中获取值。

您可以使用方法VPage::setMetaDescription('new description')覆盖元描述值。

元关键词 - 从页面SEO字段组,如果适当的字段不为空,否则检查网站设置seo.meta_keywords

您可以使用方法VPage::setMetaKeywords('new keywords')覆盖元关键词值。

页面面包屑

当您调用页面create方法时,它将第一个元素添加到Breadcrumbs数组中。

如何设置第一个面包屑元素

addBreadcrumbs(__('site.breadcrumb_home'), route(config('voyager-site.route_home_page')));

要向Breadcrumbs数组添加其他元素,只需使用方法VPage::addBreadcrumbs($title, $path)

您还可以使用方法buildBreadcrumbs()来构建一个包含在Parents属性中的嵌套页面链。当您使用方法setParents($page, $parent_field_name)加载和创建页面实例时,该属性会自动设置,其中$页是当前页面实例,$parent_field_name是包含当前页面父ID的字段名称。

注意:为模型进行的额外设置 - 构建父链面包屑和获取横幅

// Example (Additional Model properties)
public $masterPageRouteName = 'page';
public $masterPageSlug = 'pages';
public $masterPageId = 2;
public $pageRouteName = 'service';
public $bannerField = 'banner_image';

块 / 小部件

块 / 小部件是一个灵活的子系统,它为您提供了实现和整理额外的内容部分,并将它们包含到页面中的方法。
每个块都具有以下结构

状态 - 如果禁用,则块将被忽略。

标题 - 仅在管理员面板中可见。

块键 - 用于查找和渲染此块的关键字(短名)。

区域位置 - 用于分组块,然后按特定顺序将它们作为组渲染。可用的位置可以在“区域”菜单中编辑。

块内容 - 主要块内容。您可以使用纯HTML代码或Liquid模板

可用变量

  • this.images - 包含以下列出的图像项。
  • this.field_name - 包含额外的字段值,其中field_name是块表中额外的字段名称。
  • data - 如果在“选项”字段中定义了数据源。

示例 #1(使用图像列表)

<!-- Block liquid template -->
<h3>My images</h3>
<div class="images-list">
{% for image in this.images %}
  <img src="{{ image.url | crop: 300,200 | url }}" alt=""/>
{% endfor %}
</div>

其中 image.url 表示图像的相对URL。 裁剪 过滤器 - 裁剪并存储(如果尚未存储)具有给定尺寸的图像。 -url 过滤器 - 从相对URL创建完整URL。 image.full_url - 图像的完整URL。 image.props.* - 自定义图像属性。

示例 #2(使用数据源)

<!-- Block liquid template -->
<h3>Articles Block</h3>
<ul>
{% for article in data.articles %}
  <li>{{ article.title }}</li>
{% endfor %}
</ul>

对于此类模板,您需要在“选项”字段中定义类似于JSON的 数据源 结构

{
    "data_sources": {
        "articles": {
            "model": "Article",
            "where": {
                "status": 1,
                "news": 1
            },
            "order" : {
                "field" : "order",
                "direction" : "asc"
            }                 
        }
    }
}

其中 "model" 是等于在Voyager面包系统注册的模型名称的模型名称。

示例 #3(使用内部块的字段)

<!-- Block liquid template -->
{{ this.title }} <!-- Get TITLE block field -->
{{ this.images[0].url }} <!-- Get Image URL with index 0 -->
{{ this.images[0].props.title }} <!-- Get Image custom property -->
{{ this.additional_field }} <!-- Get any additional block field value -->
{{ options.data_section }} <!-- Get JSON parameter stored in the parameters field -->

您还可以使用辅助程序获取特定块的任何字段

<h1>{{ get_block_field('top-line', 'title') }}</h1>

块渲染

只需使用helper render_block('key')。您可以使用块标题或ID(数字)代替键。

{!! render_block('top-line') !!}
{!! render_block('Our services') !!}
{!! render_block(3) !!}

区域(组)渲染和URL路径规则。

它可以用于在需要时在模板中组织并渲染具有相同区域(组)名称的一组块。例如

{!! render_region('content-before') !!}

将渲染所有设置了此区域(组)'content-before'的块。并且该包将检查所有块是否可以渲染 - 这取决于设置“URL路径规则”

URL path rules

这是一个类似Drupal的块系统渲染。这可以依赖于当前URL,也可以不依赖。

表单

类似于块。但您只能使用有限的内部变量集。'send.form'是在表单子系统内部通过sendForm($request)方法实现的。它发送任何表单数据,包括附件。您还可以使用Google ReCaptcha 2表单保护。只需在您需要的表单内添加一行

<div class="g-recaptcha" data-sitekey="{{ 'general.site_captcha_site_key' | site_setting }}"></div>

并将recaptcha验证规则添加到表单选项中

{
    "validator": {
        "g-recaptcha-response": "required|recaptcha"
    }
}

模板部分

<form class="form{{ form_alias }}" action="{{ 'send.form' | route }}" method="post" enctype="multipart/form-data">    

    <input type="hidden" name="_token" value="{{ csrf_token }}">
    <input type="hidden" name="_form_alias" value="{{ form_alias }}">
    <input type="hidden" name="subject" value="Send us a message!">
    
    <input class="form-control" type="text" name="name" value="" required>
    <input class="form-control" type="email" name="email" value="" required>
    <input class="form-control phone" type="text" name="phone" value="" required>
    <textarea class="form-control" name="message" required=""></textarea>
    
    <div class="g-recaptcha" data-sitekey="{{ 'site_setting' | func: 'general.site_captcha_site_key'   }}"></div>

    <button type="submit" class="btn-send-form ">Send</button>
    
</form>

参数部分

{
    "to_address": "info@site.com,support@site.com",    
    "validator": {
        "name":"required",
        "email":"required",
        "phone":"required",
        "g-recaptcha-response": "required|recaptcha"
    },
    "messages": {
        "name.required": "The field NAME shouldn't be empty.",
        "email.required": "The field EMAIL shouldn't be empty.",
        "phone.required": "The field PHONE shouldn't be empty."
    }
}

如果不存在参数'to_address',将使用配置设置:mail.to_address。

在表单模板内部,您可以使用内部变量

{{ old }} <!-- Previous values saved in session = session()->getOldInput() -->
{{ errors_messages}} <!-- = $errors->all() -->
{{ errors }} <!-- = $errors->toArray() -->
{{ form_alias }} <!-- Form Key -->
{{ form_suffix }} <!-- Additional form suffix if you need -->
{{ form_subject }}} <!-- Form Subject -->
{{ csrf_token }} <!-- = csrf_token() -->

要从视图中渲染表单,请使用辅助程序renderForm($key, $subject = null, $suffix = null)

{!! render_form('callback-form', 'Send us a message!', '-second-form') !!}

要在块内部渲染

{{ 'callback-form' | form: 'Send us a message!','-second-form' }}

更多示例

<div id="{{ form_alias }}-{{ form_suffix }}" class="default-form contact-form">
    <form data-holder-id="{{ form_alias }}-{{ form_suffix }}" class="{{ form_alias }}" action="{{ "send.form" | route }}" method="post" enctype="multipart/form-data">
        <input type="hidden" name="_token" value="{{ csrf_token }}">
        <input type="hidden" name="_form_alias" value="{{ form_alias }}">
        <input type="hidden" name="subject" value="Callback form">
        <div class="form-group">
            <label for="name">Name</label>
            <input type="text" class="form-control" id="name" name="name" placeholder="Your name" value="{{ old.name }}">
            {% if errors.name %}
              <span class="help-block">
                {% for error in errors.name %}
                  <div>{{ error }}</div>
                {% endfor %}
              </span>
            {% endif %}            
        </div>
        <div class="form-group">
            <label for="phone">Phone</label>
            <input type="text" class="form-control" id="phone" name="phone" placeholder="Your phone number" value="{{ old.phone }}">
            {% if errors.phone %}
              <span class="help-block">
                {% for error in errors.phone %}
                  <div>{{ error }}</div>
                {% endfor %}
              </span>
            {% endif %}                        
        </div>
        <div class="form-group">
            <label for="email">E-Mail address</label>
            <input type="email" class="form-control" id="email" name="email" placeholder="Your E-Mail address" value="{{ old.email }}">
            {% if errors.email %}
              <span class="help-block">
                {% for error in errors.email %}
                  <div>{{ error }}</div>
                {% endfor %}
              </span>
            {% endif %}                        
        </div>
        <div class="form-group">
            <label for="message">Message</label>
            <textarea class="form-control" id="message" name="message" placeholder="Your message">{{ old.message }}</textarea>
            {% if errors.message %}
              <span class="help-block">
                {% for error in errors.message %}
                  <div>{{ error }}</div>
                {% endfor %}
              </span>
            {% endif %}                        
        </div>
        <div class="form-group form-group-default">
            <label>Attachment #1</label>
            <input type="file" name="images[]" accept="file_extension|image/*|media_type" multiple>
        </div>
        <div class="g-recaptcha" data-sitekey="{{ 'site_setting' | func: 'general.site_captcha_site_key'   }}"></div>
        <button type="submit" class="btn btn-default">Submit</button>
    </form>
</div>

<h2>Form Messages</h2>
{% for message in errors_messages %}
  <div>{{ message }}</div>
{% endfor %}

您还可以使用AJAX表单发送。该包实现了AJAX发送的JS部分,只需包含jQuery库并在此视图中添加此行(或者您可以使用自己的JS函数)

@include('voyager-site::mail.send_form_ajax_js')

AJAX示例

<div id="{{ form_alias }}-{{ form_suffix }}" class="default-form contact-form">
    <form data-holder-id="{{ form_alias }}-{{ form_suffix }}" class="{{ form_alias }}" action="{{ "send.form" | route }}" method="post" enctype="multipart/form-data" onsubmit="formSendAJAX(event, this)">
        <input type="hidden" name="_token" value="{{ csrf_token }}">
        <input type="hidden" name="_form_alias" value="{{ form_alias }}">
        <input type="hidden" name="subject" value="Callback form AJAX">
        <div class="row clearfix">
            <div class="form-group">
                <input type="text" name="name" placeholder="Ваше имя" value="">
            </div>
            <div class="form-group">
                <input type="email" name="email" placeholder="Ваш Email адрес"  value="">
            </div>
            <div class="form-group">
                <input class="phone" type="text" name="phone" placeholder="Ваш телефон"  value="">
            </div>
            <div class="form-group">
                <textarea rows="7" name="message" placeholder="Ваше сообщение"></textarea>
            </div>
            <div class="form-group form-group-default">
                <label>Attachment</label>
                <input type="file" name="images[]" accept="file_extension|image/*|media_type" multiple>
            </div>
            <div class="col-sm-12">
                <div class="g-recaptcha" data-sitekey="{{ 'general.site_captcha_site_key' | site_setting }}"></div>
            </div>                          
            <div class="form-button">
                <button type="submit" class="btn btn-primary btn-send-form">
                    <span class="btn-loader hidden">...loading icon...</span>
                    <span class="btn-title-normal">Send Form</span>
                    <span class="btn-title-sending hidden">Sending...</span>
                </button>
            </div>
        </div>
    </form>
    <div class="form-message"></div>
</div>

页面布局渲染

该特殊系统允许您管理特定页面上的块或表单以及字段。您可以在编辑页面模式下轻松添加/删除块、表单和字段(并对其进行排序),然后使用辅助程序进行渲染

{{ render_layout($page->layout, $page) }}

其中 $page 是页面实例。

或手动

@if ($layoutFields = json_decode($page->layout))
    @if ($layoutFields)
        @foreach($layoutFields as $field)
            @if($field->type === 'Block')
                {!! render_block($field->key) !!}
            @elseif ($field->type === 'Form')
                {!! render_form($field->key) !!}
            @elseif ($field->type === 'Field')
                <div class="page-content">
                    {!! $page->{$field->key} !!}
                </div>
            @endif
        @endforeach
    @endif
@endif

内置短码

block - 渲染一个块。例如:[block name="top-line"]

form - 渲染一个表单。例如:[form name="top-line" subject="表单主题" suffix="-callback-form"]

div - 渲染div包装器。不允许嵌套元素。例如:[div class="wrapper"] 内容 [/div]

image - 使用当前内容实例附加的媒体文件渲染HTML代码中的图片(并裁剪、转换图片)。

可用选项
field - 与附加媒体文件相关的媒体文件字段
index - 文件在集合中的索引,从1开始。
url - 图片路径(如果未使用field和index)
class - 图片标签的类。例如:class="lazy responsive"
lightbox - 创建灯箱包装器。例如:lightbox="true"
lightbox_class - 灯箱包装器的类
width - 图片标签的宽度属性。
height - 图片标签的高度属性。
picture - 添加图片包装器。
crop - 裁剪图片。
format - 将图片转换为webp、jpg、png。

示例
[image field="image" index="1"]
[image url="/images/banners/banner-1.jpg"]
[image field="images" index="3" picture="true" format="webp" width="100" height="200" crop="400,400" class="lazy-class" lazy="true" lightbox_class="image-gallery"]

内置的Liquid过滤器

url - 返回给定路径的完整URL。例如:{{ '/images/file.jpg' | url }}
route - 返回laravel路由路径。例如:{{ 'page' | route: 'slug' }}
crop - 裁剪(和/或转换)图片。例如:{{ this.images[0].url | crop: 300,300,'webp',75 }}
webp - 将图片转换为webp格式。例如:{{ this.images[0].url | webp }}
site_setting - 获取网站设置参数。例如:{{ 'mail' | site_setting: 'to_address' }} menu - 渲染菜单。例如:{{ 'main-menu' | menu: 'template.menus.main' }}
block - 渲染块。例如:{{ 'top-line' | block }}
form - 渲染表单。例如:{{ 'callback-form' | form: 'Form subject','-callback' }}
trans - 使用当前区域翻译字符串。例如:{{ '[[en]]English string[[ru]]Русская строка' | trans }}
lang -- 使用语言文件翻译字符串。例如:{{ 'auth.password' | lang }}
view -- 使用数据变量渲染blade视图。例如:{{ data | view: 'template.partials.portfolio-list' }}
service -- 使用服务方法获取数据(并将其传递到视图)。例如:{{ 'getPortfolio' | service: '\App\Services\DataService' | view: 'template.partials.portfolio-list' }}

自定义Liquid过滤器

您可以使用自定义类添加自己的liquid过滤器。您应该将此类添加到配置中
'template_filters' => 'App\Template\TemplateFilters'

<?php

namespace App\Template;

class TemplateFilters
{
    public function handle($instance, $content)
    {
        $instance->registerFilter('div', function ($arg) {
            return '<div>' . $arg . '</div';
        });
    }
}

辅助函数

translit_cyrillic($string) - 将给定的西里尔字符串转换为拉丁符号字符串。
get_file($file_path) - 返回给定file_path的URL,可以解包JSON voyager文件编码格式。
store_post_files(Request $request, $slug, $field, $public = 'public') - 存储请求中的文件给定的$request $field,并返回包含文件链接及其原始名称的Voyager数组。
generate_filename($file, $path) - 如果文件在给定的路径上存在,则返回新的文件名。
site_setting($key, $default = null) - 返回网站设置(或默认值)。
site_settings_group($group_key) - 返回一组网站设置。
get_image_or_create($image_path, $width, $height, $format, $quality) - 返回请求的图片的链接,具有给定的宽度和/或高度、格式和品质。如果不存在,则将其创建。

位置
$image_path - 域名或相对(到域名)的完整URL。
$width - 新图片宽度或空值,
$height - 新图片高度或空值(如果同时使用宽度和高度,则图片将被裁剪),
$format - 'webp', 'png', 'jpg':图片将转换为webp、png、jpg,
$quality - 新图片的品质水平。

get_first_not_empty(array $values) - 返回给定数组中的第一个非空元素。
render_block($key) - 渲染块。
render_region($key, $path = null) - 渲染区域(具有相同区域/组键的块组)。
render_form($key, $subject = null, $suffix = null) - 渲染具有给定键的表单。也接受主题字段和后缀,用于表单中。
render_layout($layout, $page) - 渲染页面布局(页面的块组、表单和页面字段)。
get_block_field($block_key, $field_name) - 返回给定块键的字段值。

安全性

如果您发现任何安全相关的问题,请通过作者邮箱联系,而不是使用问题跟踪器。

鸣谢

许可证

许可证。请参阅许可证文件以获取更多信息。