Разработка кастомного шаблона Sulu

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка кастомного шаблона Sulu
Средняя
~5 рабочих дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Разработка кастомного шаблона Sulu

Шаблон в Sulu состоит из двух частей: XML-описания (что редактирует менеджер) и Twig-шаблона (как это отображается). XML регистрирует тип страницы в системе, Twig рендерит контент. Контроллер можно не писать — Sulu использует DefaultController, или написать кастомный для добавления данных.

Полный XML-шаблон

<!-- config/templates/service.xml -->
<?xml version="1.0" encoding="utf-8"?>
<template xmlns="http://schemas.sulu.io/template/template"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://schemas.sulu.io/template/template
          http://schemas.sulu.io/template/template-1.0.xsd">

    <key>service</key>
    <view>pages/service</view>
    <controller>App\Controller\Website\ServiceController::indexAction</controller>
    <cacheLifetime type="seconds">1800</cacheLifetime>

    <properties>
        <property name="title" type="text_line" mandatory="true">
            <meta><title lang="ru">Заголовок</title></meta>
            <tag name="sulu.rlp.part"/>
        </property>

        <property name="intro" type="text_area" colspan="12">
            <meta><title lang="ru">Вступительный текст</title></meta>
            <params>
                <param name="rows" value="3"/>
            </params>
        </property>

        <block name="content_blocks" default-type="text" colspan="12">
            <meta><title lang="ru">Блоки контента</title></meta>
            <types>
                <type name="text">
                    <meta><title lang="ru">Текст</title></meta>
                    <properties>
                        <property name="text" type="text_editor">
                            <meta><title lang="ru">Текст</title></meta>
                        </property>
                    </properties>
                </type>
                <type name="image_text">
                    <meta><title lang="ru">Изображение + текст</title></meta>
                    <properties>
                        <property name="image" type="single_media_selection">
                            <meta><title lang="ru">Изображение</title></meta>
                        </property>
                        <property name="text" type="text_editor">
                            <meta><title lang="ru">Текст</title></meta>
                        </property>
                        <property name="image_position" type="select">
                            <meta><title lang="ru">Позиция изображения</title></meta>
                            <params>
                                <param name="values" type="collection">
                                    <param name="left" title="Слева"/>
                                    <param name="right" title="Справа"/>
                                </param>
                            </params>
                        </property>
                    </properties>
                </type>
                <type name="cta">
                    <meta><title lang="ru">Призыв к действию</title></meta>
                    <properties>
                        <property name="heading" type="text_line">
                            <meta><title lang="ru">Заголовок</title></meta>
                        </property>
                        <property name="button_text" type="text_line">
                            <meta><title lang="ru">Текст кнопки</title></meta>
                        </property>
                        <property name="button_link" type="text_line">
                            <meta><title lang="ru">Ссылка</title></meta>
                        </property>
                    </properties>
                </type>
            </types>
        </block>

        <section name="sidebar">
            <meta><title lang="ru">Боковая панель</title></meta>
            <properties>
                <property name="show_form" type="checkbox">
                    <meta><title lang="ru">Показать форму обратной связи</title></meta>
                    <params>
                        <param name="defaultValue" value="true"/>
                    </params>
                </property>
                <property name="related_services" type="smart_content">
                    <meta><title lang="ru">Похожие услуги</title></meta>
                    <params>
                        <param name="provider" value="pages"/>
                        <param name="types" value="service"/>
                        <param name="max_per_page" value="4"/>
                    </params>
                </property>
            </properties>
        </section>
    </properties>
</template>

Twig-шаблон

{# templates/pages/service.html.twig #}
{% extends 'base.html.twig' %}

{% block title %}{{ content.title }} | {{ app.request.host }}{% endblock %}

{% block body %}
<div class="page-service">
    <div class="container">
        <header class="page-header">
            <h1>{{ content.title }}</h1>
            {% if content.intro %}
                <p class="lead">{{ content.intro }}</p>
            {% endif %}
        </header>

        <div class="service-layout {% if content.show_form %}service-layout--with-sidebar{% endif %}">
            <main class="service-content">
                {% for block in content.content_blocks %}
                    {% if block.type == 'text' %}
                        <div class="block block--text">
                            {{ block.text|raw }}
                        </div>

                    {% elseif block.type == 'image_text' %}
                        {% set img = sulu_resolve_media(block.image, locale) %}
                        <div class="block block--image-text block--image-{{ block.image_position }}">
                            {% if img %}
                                <figure>
                                    <img
                                        src="{{ img.thumbnails['service-block'] }}"
                                        alt="{{ img.title }}"
                                        loading="lazy"
                                    >
                                </figure>
                            {% endif %}
                            <div class="block__text">{{ block.text|raw }}</div>
                        </div>

                    {% elseif block.type == 'cta' %}
                        <div class="block block--cta">
                            {% if block.heading %}
                                <h2>{{ block.heading }}</h2>
                            {% endif %}
                            <a href="{{ block.button_link }}" class="btn btn--primary">
                                {{ block.button_text }}
                            </a>
                        </div>
                    {% endif %}
                {% endfor %}
            </main>

            {% if content.show_form %}
                <aside class="service-sidebar">
                    {% include 'snippets/contact-form.html.twig' %}

                    {% if content.related_services %}
                        <div class="related-services">
                            <h3>Похожие услуги</h3>
                            {% for service in content.related_services %}
                                <a href="{{ service.url }}" class="related-link">
                                    {{ service.title }}
                                </a>
                            {% endfor %}
                        </div>
                    {% endif %}
                </aside>
            {% endif %}
        </div>
    </div>
</div>
{% endblock %}

Кастомный контроллер

// src/Controller/Website/ServiceController.php
namespace App\Controller\Website;

use Sulu\Bundle\WebsiteBundle\Resolver\TemplateAttributeResolverInterface;
use Sulu\Component\Content\Compat\StructureInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;

class ServiceController extends AbstractController
{
    public function __construct(
        private readonly TemplateAttributeResolverInterface $resolver,
        private readonly TestimonialRepository $testimonials
    ) {}

    public function indexAction(
        StructureInterface $structure,
        bool $preview = false,
        bool $partial = false
    ): Response {
        $attributes = $this->resolver->resolve(
            [
                'content'      => $structure->getContent(),
                'testimonials' => $this->testimonials->findByService(
                    $structure->getUuid(),
                    limit: 3
                ),
            ],
            $structure,
            !$partial,
            $preview
        );

        $view = $partial ? 'pages/service_partial.html.twig' : 'pages/service.html.twig';

        return $this->render($view, $attributes);
    }
}

Базовый шаблон и блоки Twig

{# templates/base.html.twig #}
<!DOCTYPE html>
<html lang="{{ app.request.locale }}">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{% block title %}{{ sulu_content_path('/', webspace, locale) }}{% endblock %}</title>
    {% block stylesheets %}
        <link rel="stylesheet" href="{{ asset('build/app.css') }}">
    {% endblock %}
</head>
<body>
    {% block header %}
        {% include 'snippets/header.html.twig' %}
    {% endblock %}

    {% block body %}{% endblock %}

    {% block footer %}
        {% include 'snippets/footer.html.twig' %}
    {% endblock %}

    {% block javascripts %}
        <script src="{{ asset('build/app.js') }}" defer></script>
    {% endblock %}
</body>
</html>

Sulu Twig Extension

{# навигация #}
{% set navigation = sulu_navigation_root_flat('main', 3) %}
{% for item in navigation %}
    <a href="{{ item.url }}"
       class="{{ item.uuid == content.uuid ? 'active' : '' }}">
        {{ item.title }}
    </a>
{% endfor %}

{# breadcrumbs #}
{% set breadcrumb = sulu_breadcrumb() %}
{% for crumb in breadcrumb %}
    <a href="{{ crumb.url }}">{{ crumb.title }}</a>
{% endfor %}

{# snippet из системы сниппетов #}
{{ sulu_snippet('footer_info', 'default', locale)|raw }}

{# URL страницы по UUID #}
<a href="{{ sulu_content_path(content.link, webspace, locale) }}">Ссылка</a>

Регистрация шаблона в Webspace

<!-- config/packages/webspaces/example.xml -->
<templates>
    <template type="page">service</template>
</templates>

После добавления шаблона — сбросить кэш и переиндексировать:

php bin/console cache:clear
php bin/console sulu:document:initialize

Сроки

Один шаблон с блочным редактором, кастомным контроллером и Twig: 2–3 дня. Полный комплект шаблонов (5–8 типов) для корпоративного сайта: 1,5–2 недели.