Разработка кастомного пакета (Package) Concrete CMS

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

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

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

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка кастомного пакета (Package) Concrete CMS
Сложная
~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

Разработка кастомного пакета (Package) Concrete CMS

Пакет (Package) в Concrete CMS — это контейнер, объединяющий темы, блоки, атрибуты, типы страниц, одиночные страницы, Express-объекты и задачи автоматизации. Правильно структурированный пакет позволяет установить всё необходимое одной командой и легко переносить решение между инсталляциями.

Структура пакета

packages/my-package/
  controller.php                    # главный контроллер пакета
  blocks/
    feature-card/                   # кастомный блок
      controller.php
      db.xml
      add.php
      edit.php
      view.php
  themes/
    my-theme/                       # тема
  attributes/
    color_picker/                   # кастомный тип атрибута
      controller.php
  single_pages/
    dashboard/
      my_package/
        settings.php                # страница настроек в Dashboard
  elements/
    my_package/
      settings_form.php
  jobs/
    sync_products.php               # автоматическая задача (Cron Job)
  config/
    generated_overrides/
  mail/
    order_notification.php          # шаблон email
  src/
    Entity/                         # Doctrine-сущности
      Order.php
    Repository/
      OrderRepository.php
    Service/
      OrderService.php
  db.xml                            # общие таблицы пакета

controller.php — сердце пакета

<?php
namespace Concrete\Package\MyPackage;

use Concrete\Core\Package\Package;
use Concrete\Core\Page\Single as SinglePage;
use Concrete\Core\Block\BlockType\BlockType;
use Concrete\Core\Page\Type\Type as PageType;
use Concrete\Core\Attribute\Type as AttributeType;
use Concrete\Core\Job\Job;

defined('C5_EXECUTE') or die('Access Denied.');

class Controller extends Package {

    protected string $pkgHandle          = 'my-package';
    protected string $appVersionRequired = '9.0.0';
    protected string $pkgVersion         = '2.1.0';

    public function getPackageName(): string        { return t('My Package'); }
    public function getPackageDescription(): string { return t('Полный функциональный пакет для корпоративного сайта'); }

    public function on_start(): void {
        // Регистрация сервисов, автолоадер, роуты
        $this->app->make(\Concrete\Package\MyPackage\Routing\RouteRegistrar::class)->register();

        // Регистрация Doctrine-сущностей
        $this->app->make('Concrete\Core\Foundation\Service\ProviderList')
            ->registerProvider(\Concrete\Package\MyPackage\Provider\ServiceProvider::class);
    }

    public function install(): void {
        $pkg = parent::install();
        $this->installOrUpgrade($pkg);
    }

    public function upgrade(): void {
        parent::upgrade();
        $pkg = $this->getPackageEntity();
        $this->installOrUpgrade($pkg);
    }

    private function installOrUpgrade(\Concrete\Core\Entity\Package $pkg): void {
        // Блоки
        $this->installBlock('feature-card', $pkg);
        $this->installBlock('team-member', $pkg);
        $this->installBlock('testimonial', $pkg);

        // Типы страниц
        $this->installPageType('service-detail', 'Service Detail', $pkg);
        $this->installPageType('team-member', 'Team Member', $pkg);

        // Атрибуты страниц
        $this->installPageAttribute('hero_image', 'image', 'Hero Image', $pkg);
        $this->installPageAttribute('intro_text', 'text', 'Intro Text', $pkg);
        $this->installPageAttribute('meta_description', 'textarea', 'Meta Description', $pkg);
        $this->installPageAttribute('show_in_nav', 'boolean', 'Show in Navigation', $pkg);

        // Dashboard-страница настроек
        $sp = SinglePage::add('/dashboard/my_package', $pkg);
        if ($sp) { $sp->update(['cName' => 'My Package', 'cDescription' => 'Настройки']); }

        $sp = SinglePage::add('/dashboard/my_package/settings', $pkg);
        if ($sp) { $sp->update(['cName' => 'Settings']); }

        // Cron Job
        Job::installByPackage('sync_products', $pkg);
    }

    private function installBlock(string $handle, $pkg): void {
        if (!\Concrete\Core\Block\BlockType\BlockType::getByHandle($handle)) {
            BlockType::installBlockTypeFromPackage($handle, $pkg);
        }
    }

    private function installPageType(string $handle, string $name, $pkg): void {
        if (!PageType::getByHandle($handle)) {
            PageType::add([
                'ptHandle'            => $handle,
                'ptName'              => $name,
                'ptIsFrequentlyAdded' => 0,
                'ptLaunchInComposer'  => 1,
            ], $pkg);
        }
    }

    private function installPageAttribute(string $handle, string $type, string $name, $pkg): void {
        $at = AttributeType::getByHandle($type);
        $ak = \Concrete\Core\Attribute\Key\CollectionKey::getByHandle($handle);
        if (!$ak) {
            \Concrete\Core\Attribute\Key\CollectionKey::add($at, [
                'akHandle' => $handle,
                'akName'   => $name,
            ], $pkg);
        }
    }

    public function uninstall(): void {
        parent::uninstall();
        // Опционально: удалить таблицы пакета
        $db = $this->app->make('database')->connection();
        $db->executeStatement('DROP TABLE IF EXISTS MyPackageOrders');
    }
}

Doctrine-сущности

Concrete CMS 9+ использует Doctrine ORM. Сущности пакета:

<?php
// src/Entity/Order.php
namespace Concrete\Package\MyPackage\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="Concrete\Package\MyPackage\Repository\OrderRepository")
 * @ORM\Table(name="MyPackageOrders")
 */
class Order {
    /** @ORM\Id @ORM\Column(type="integer") @ORM\GeneratedValue */
    private int $id;

    /** @ORM\Column(type="string", length=255) */
    private string $customerEmail;

    /** @ORM\Column(type="decimal", precision=10, scale=2) */
    private float $total;

    /** @ORM\Column(type="string", length=50) */
    private string $status = 'pending';

    /** @ORM\Column(type="datetime") */
    private \DateTime $createdAt;

    // getters/setters...
}
<?php
// Provider/ServiceProvider.php — регистрация сущностей
namespace Concrete\Package\MyPackage\Provider;

use Concrete\Core\Foundation\Service\Provider;

class ServiceProvider extends Provider {
    public function register(): void {
        $this->app->make('Concrete\Core\Database\DatabaseStructureManager')
            ->installDatabase($this->app->make(\Concrete\Package\MyPackage\Controller::class)->getPackageEntity());
    }
}

Cron Job

<?php
// jobs/sync_products.php
namespace Concrete\Package\MyPackage\Job;

use Concrete\Core\Job\Job;

class SyncProducts extends Job {

    public function getJobName(): string        { return t('Sync Products'); }
    public function getJobDescription(): string { return t('Синхронизация товаров с внешним API'); }

    public function run(): string {
        $service = $this->app->make(\Concrete\Package\MyPackage\Service\ProductSyncService::class);
        $count   = $service->sync();
        return t('Синхронизировано: %d товаров', $count);
    }
}

Job запускается через Dashboard → System → Automated Jobs или по cron:

*/30 * * * * /usr/bin/php /var/www/mysite/concrete/bin/concrete5 c5:job:run sync_products

Dashboard-страница настроек

<?php
// single_pages/dashboard/my_package/settings.php
namespace Concrete\Package\MyPackage\Controller\SinglePage\Dashboard\MyPackage;

use Concrete\Core\Page\Controller\DashboardPageController;
use Concrete\Core\Http\Request;

class Settings extends DashboardPageController {

    public function view(): void {
        $config = $this->app->make('config');
        $this->set('api_key', $config->get('my_package.api_key', ''));
        $this->set('sync_interval', $config->get('my_package.sync_interval', 30));
    }

    public function save(): void {
        $token = $this->app->make('token');
        if (!$token->validate('my_package_settings')) {
            $this->error->add(t('Недействительный токен'));
            return $this->view();
        }

        $config = $this->app->make('config');
        $config->save('my_package.api_key', $this->request->get('api_key'));
        $config->save('my_package.sync_interval', (int)$this->request->get('sync_interval'));

        $this->flash('success', t('Настройки сохранены'));
        $this->redirect('/dashboard/my_package/settings');
    }
}

REST API для пакета

Concrete CMS 9 поддерживает регистрацию кастомных API-эндпоинтов:

// Routing/RouteRegistrar.php
namespace Concrete\Package\MyPackage\Routing;

use Concrete\Core\Routing\RouteListInterface;
use Concrete\Core\Routing\Router;

class RouteRegistrar implements RouteListInterface {
    public function loadRoutes(Router $router): void {
        $router->buildGroup()
            ->setPrefix('/api/v1/my-package')
            ->setNamespace('Concrete\Package\MyPackage\Controller\Api')
            ->routes(function($groupRouter) {
                $groupRouter->get('/products', 'Products::index');
                $groupRouter->post('/products', 'Products::store');
                $groupRouter->get('/products/{id}', 'Products::show');
            });
    }
}

Миграции структуры БД

Для изменений схемы между версиями используют db.xml с инкрементальными изменениями. Concrete CMS сравнивает текущую схему с db.xml при upgrade:

<!-- db.xml v2 — добавлено поле notes -->
<table name="MyPackageOrders">
  <!-- ... существующие поля ... -->
  <field name="notes" type="X2"/>
  <field name="updated_at" type="T"/>
</table>

Упаковка и распространение

# Создать архив пакета для Concrete Marketplace
cd /var/www/mysite
zip -r my-package-2.1.0.zip packages/my-package \
  --exclude "*.git*" \
  --exclude "node_modules/*" \
  --exclude "*.map"

Сроки разработки пакета

Компонент Оценка
Контроллер пакета + установка 4–8 ч
3–5 кастомных блоков 3–6 дней
Тема с 8–12 типами страниц 2–4 недели
Doctrine-сущности + CRUD 2–4 дня
Dashboard-страница + настройки 1–2 дня
REST API (3–5 эндпоинтов) 2–3 дня
Cron Jobs (1–3 задачи) 4–8 ч
Полный корпоративный пакет 8–16 недель