Packages Basics

Improvements?

Let us know by posting here.

Package Format

Concrete CMS packages bundle functionality for easy distribution, installation, and uninstallation, usable with or without connecting to concretecms.org.

Package Structure

Packages include:

  • An outer directory
  • Optional icon.png
  • controller.php file defining:
    • Package version
    • Installation and uninstallation routines
    • Custom startup code for Concrete

Filesystem structure in a package mirrors the concrete/ directory for core-installed components. For example, a package with a theme, two custom block types, and a dashboard page might be organized as shown:

Concrete CMS Package File Structure

Development and Deployment

Initially, develop and install functionality (like block types or themes) in the application/ folder using the Concrete dashboard. Then, move it to a package folder and add code to controller.php for package-based installation and uninstallation. This approach ensures automatic installation and uninstallation of components with the package, and enables running custom code when the package is installed on a Concrete site. This makes packages suitable for delivering both simple elements and complex event-based functionality.

Package Creation

Directory Name

For package creation in Concrete CMS:

  • Use lowercase, underscore-separated names for the outer directory.
  • Example: "Hello World" becomes "hello_world".

Icon

  • Include an icon.png at the root of the package.
  • Any square size works, but typically 97px by 97px. Larger than 200px is unnecessary.

Controller

Every package must have a controller.php in its base directory, handling package identification, installation, and uninstallation. Place additional logic elsewhere. Example structure:

<?php
namespace Concrete\Package\DocumentLibrary;

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

use Concrete\Core\Package\Package;
use Concrete\Core\Block\BlockType\BlockType;

class Controller extends Package
{
    protected $pkgHandle = 'document_library';
    protected $appVersionRequired = '5.7.4.3b1';
    protected $pkgVersion = '1.0';

    public function getPackageDescription()
    {
        return t('Adds a searchable file library to a page.');
    }

    public function getPackageName()
    {
        return t('Document Library');
    }

    public function install()
    {
        $pkg = parent::install();
        BlockType::installBlockType('document_library', $pkg);
    }
}
  • Namespace should match the package directory name, camelCased (e.g., document_libraryConcrete\Package\DocumentLibrary).
  • Add defined('C5_EXECUTE') or die('Access Denied.'); for security.
  • Import classes outside the package namespace as needed.
  • Controller class named Controller, extending Concrete\Core\Package\Package.
  • Include $pkgHandle, $appVersionRequired, and $pkgVersion.
  • Implement getPackageDescription() and getPackageName() for Dashboard display.
  • Define install() method for installation actions.

Important: Namespace and directory name must align; mismatches result in 'Unknown Package' and 'Broken Package' errors.

Broken Package Error

Modifying Concrete CMS Startup with Packages

Packages in Concrete CMS can add custom code to the startup routine. For example, to track page views, you can use the on_page_view event:

\Events::addListener(
    'on_page_view',
    function ($e) {
        $c = $e->getPageObject();
        // Actions with the current page object
    });

To integrate this into the startup, add it to the on_start() method in controller.php:

public function on_start()
{
    \Events::addListener(
        'on_page_view',
        function ($e) {
            $c = $e->getPageObject();
            // Actions with the current page object
        }
   );
}

This code will execute whenever Concrete runs, provided the package is installed.

Handling Package Upgrades

Concrete CMS packages can handle ongoing code updates using version numbers. For example, consider a package installing a theme in version 1.0:

namespace Concrete\Package\CustomTheme;

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

use \Concrete\Core\Package\Package;
use \Concrete\Core\Page\Theme\Theme;

class Controller extends Package
{
    protected $pkgHandle = 'custom_theme';
    protected $appVersionRequired = '5.7.4';
    protected $pkgVersion = '1.0';

    // ... Additional methods

    public function install()
    {
        $pkg = parent::install();
        Theme::add('custom_theme', $pkg);
    }
}

To add a block type in a later version:

  1. Import BlockType:

    use \Concrete\Core\Block\BlockType\BlockType;
    
  2. Modify install() to include the block type:

    public function install()
    {
       $pkg = parent::install();
       Theme::add('custom_theme', $pkg);
       BlockType::installBlockTypeFromPackage('custom_block_type', $pkg);
    }
    
  3. Update $pkgVersion for the new version (e.g., 1.2).

  4. Implement an upgrade() method:

    public function upgrade() {
       parent::upgrade();
       $pkg = Package::getByHandle('custom_theme');
       $bt = BlockType::getByHandle('custom_block_type');
       if (!is_object($bt)) {
           BlockType::installBlockTypeFromPackage('custom_block_type', $pkg);
       }
    }
    

This approach allows for seamless upgrades without data loss. Concrete CMS will notify administrators of available updates, and the upgrade() method handles adding new features while checking for existing components to avoid duplicates.

Custom Code in Concrete CMS Packages

Packages in Concrete CMS can include custom PHP code. For instance, instead of directly adding code to the on_start() method, it's more efficient to use a separate script. Here's an example with a Bootstrapper class in the FooCorp\WidgetManager namespace:

public function on_start()
{
    $startup = new \FooCorp\WidgetManager\Bootstrapper();
    $startup->boot();
}

To ensure Concrete finds this class, use PHP autoloading in the package controller:

protected $pkgAutoloaderRegistries = array(
    'src/FooCorp/WidgetManager' => '\FooCorp\WidgetManager'
);

This registers the FooCorp\WidgetManager namespace, directing it to the src/FooCorp/WidgetManager directory in your package. Classes in this directory will be autoloaded based on their namespace and class name matching the filesystem structure.

Legacy Namespace Support

Before Concrete version 5.7.5.10, a namespace like Concrete\Package\WidgetManager\Src was automatically created. This legacy support still exists in version 8 but is disabled if you use $pkgAutoloaderRegistries or your package requires Concrete 8.0 or higher.

Extending Core Classes

Core Concrete objects can be extended in packages. For example, a package with the handle 'spam_stopper' that installs an 'akismet' anti-spam plugin would have a class:

Concrete\Package\SpamStopper\Antispam\AkismetController

This class would automatically be sourced from:

packages/spam_stopper/src/Concrete/Antispam/AkismetController

To know which items can be extended this way, refer to the Developer Documentation or look for overrideable_core_class() in the Concrete source code.