Building a simple Blog package - Part 1

Building a simple Blog package - Part 1

Time to build your first package: a very simple blog application for concrete5


Article by Ollie / / Comments / Difficulty 
Building a simple Blog package - Part 1

If you install concrete5 with the default content, you'll get a blog on your website. If you delete it, or install a blank site you won't have the blog, so if you want this functionality you'll need to create it or buy from the concrete5 marketplace (in which there can be found a couple of excellent solutions).

In this tutorial I want to look at how we'd build a package, and, just for the sake of it, we'll make the package application a very simple blog, similar in almost all respects to the blog that get's installed with the default content.

It's a bit involved so I'm going to break it into a number of parts. In this part (Part 1) we'll deal with the package setup and package controller.

What is a package?

Packages are self contained pieces of functionality which can be installed into concrete5 websites. Packages contain all the php code and other scripts needed to set up an application. Everything in the marketplace is a package. All packages have a controller which looks after installation, upgrade and uninstallation, and you can also utilise it to load assets such as CSS and JavaScript required by the packaged application at start up.

Everything else is optional or will depend on what the package application is intended to achieve, but typically it may contain blocks, single pages, controllers, page types, libraries, and of course themes. It's good practice to include an icon.png file and a CHANGELOG with your package, although nothing breaks if you don't.

For more general information on packages check out concrete5.org developer documentation.

Our package is going to contain a blog, with a single dashboard page through which we can enable or disable comments on a sitewide basis, and through which we can add our Disqus shortname, since we're going to use the Disqus comments system instead of the guestbook block which the default installed blog uses.

Package structure

Our first step is to create our package folder structure. In your /packages folder create a folder for your package. This can be named as anything, but you might want to choose something you can rely on to be unique. In concrete5 5.7 PHP namespacing will be employed, but in 5.6.3 and below you generally have to namespace your code by naming in ways unlikely to cause conflicts.

Let's name our package folder 'first_blog'.

In this folder we'll add our controller.php file, our CHANGELOG and our icon.png which is a 97px by 97px image, usually with a 4px border radius. In our CHANGELOG we keep version information. These details are presented when upgrading packages, so it's wise to be descriptive about what has changed in each version of your package.

Whilst we're about it, we'll create the other subfolders and files we'll need as we progress through building our blog package. All of these will be explained as we go, for now create the following folders and files

first_blog/single_pages/dashboard/system/environment/blog/view.php
first_blog/controllers/dashboard/system/environment/blog/controller.php
first_blog/page_types/first_blog_post.php

The first two paths provide a home for a single page (view.php) and a related controller (controller.php) that we'll need to install a custom dashboard page. You can see from the path where the dashboard page will go, once installed we'll be able to find it under Environment in 'System & Settings'.

The second path provides a home for a custom page type that we're going to install (first_blog_post.php). This page type will be used to create the default page layout that each and every blog post we make will inherit.

We could discuss page types at length here, but it's suffice to say that as well as governing presentation, page types also represent a data store, and are very useful on the context of defining information about a thing that you need to store, without going to the lengths of creating a new concrete5 object.

What's in the Controller?

If you're still with me then good, because we're coding from here on! We're going to look at the package controller first, and I'm going to walk you, line by line, through the code, explaining each statement as we go.

<?php defined('C5_EXECUTE') or die(_("Access Denied."));

You should find this line at the top of every concrete5 file. It simply prevents execution of any PHP file if it is not running as part of the concrete5 environment. We add it to all our files too.

class FirstBlogPackage extends Package {
    protected $pkgHandle = 'first_blog';
    protected $appVersionRequired = '5.5.0';
    protected $pkgVersion = '0.9.0';

Next, we start our controller's class. Note the naming used, it should be the package handle, with underscores removed, and in pascal case (first letters capitalised). Note also that it extends concrete5's Package class - when we extend a class we inherit its functionality without needing to write it, but we can add to the functionality with new methods and change functionality by overriding existing methods.

The next three lines are the packages properties which govern how it will be known in a concrete5 installation, the minimum version of concrete5 required to install the package, and the package version. If you plan to submit to the concrete5 Marketplace, it's good practice to start your version at 0.9.0, whilst your package is being evaluated by the Peer Review Board (PRB).

public function getPackageName() {
    return t("First Blog");
}
public function getPackageDescription() {
    return t("Simple blogging system for concrete5 websites");
}

Next we have a couple of methods that set the name and description for the package. These are used in the installation pages. Note the t() function which wraps the text. This allows a package to be translated to other languages. All strings should be wrapped with this function and it's another thing the PRB will look at should you submit to the Marketplace.

public function install() {
    $pkg = parent::install();

We're starting our package install() method. The first line invokes the parent classes install() method, we'll then do our stuff. It's worth saying that if your package had dependencies, for example say on another package being installed, you might test for that here before invoking the parent install() method.

    ## New page type
    Loader::model('collection_types');
    Loader::model('collection');

We're using the loader class to load a couple of classes - in this case models - that we'll need to install things. When you need to use a loader is a bit hit and miss in concrete5 5.6.3 and below, you generally find out with an error message. In concrete5 5.7 autoloading wil be employed: one of the things I'm most looking forward to.

Let's keep going!

    if(!is_object(CollectionType::getByHandle('first_blog_post'))) {
        $data['ctHandle'] = 'ap_blog_post';
        $data['ctName'] = t('Blog Post');
        $pt = CollectionType::add($data, $pkg);

We're going to install our 'first_blog_post.php' page type. First we test to ensure it is not already installed, then we define the properties we need to install it, in the case ctHandle, and ctName, then install it. We might consider setting some default attributes for this page type, for example to prevent pages created from it being listed in the menu navigation, but I haven't here.

Note the methods refer to 'collections' and not 'pages'. Pages are collections, think back to my comment on page types as data stores. Pages are collections but lots of other things can be collections too. Note also the $pkg variable. We're linking the new page type to the package which installed it. When we uninstall the package, this page type will get removed automatically for us.

        ## Install two blocks to page type
        $pt = CollectionType::getByHandle('first_blog_post');
        $mt = $pt->getMasterTemplate();
        $ctB = BlockType::getByHandle('content');
        $npB = BlockType::getByHandle('next_previous');
        $mt->addBlock($ctB, 'Main', array('btConfig' => '1', 'content' => 'Your blog post goes here!'));
        $pNConfig = array(
            'linkStyle' => 'page_name',
            'showArrows' => '1',
            'loopSequence' => '1',
            'excludeSystemPages' => '1'
        );
        $mt->addBlock($npB, 'Previous Next', $pNConfig);
    }

Whoah, big chunk of code! We're adding two blocks to the page type here, a content block with some default content of 'Your blog post goes here!' and a 'Next Previous' navigation block which will allow navigation between posts from each page, the configuration options for which can be found in the $pNConfig array. In practice the best way to ascertain what key/values are needed to configure a block is to inspect the block options when adding or editing, using your browser's developer tools.

These blocks will be on every page we create using the 'first_blog_post.php' page type.

    Loader::model('page');
    $home = page::getByID(1);

Another model is loading, and I grab the homepage object, which always has an ID of 1, as it's the first page which is installed with concrete5. I'll need this object next.

    $bP = page::getByPath("/blog")->cID;
    if(!$bP) {
        ## Create a blog page
        $bPConfig = array(
            'pkgID' => $pkg->getPackageID(),
            'cName' => 'Blog',
            'cHandle' => 'blog'
        );
        $bP = $home->add(CollectionType::getByHandle('right_sidebar'), $bPConfig);

I'm installing a blog page, which will list all our posts, and have some form of archive by month on it. Note the configuration options in the $bPConfig array. See how the page is installed via the $home object which I'd grabbed above. This means it will be a child of that page. This page does not use a special page type, so we use the default page type, which is included with all themes. Its handle is 'right_sidebar'. This page will be found at yourwebsite/index.php/blog or yourwebsite/blog depending on whether or not your site is using Pretty URLs.

        ## Add Page list block
        $ctID = CollectionType::getByHandle('first_blog_post')->getCollectionTypeID();
        $bP = page::getByPath("/blog");
        $pLConfig = array(
            'num' => '10',
            'rss' => '1',
            'cParentID' => $bP->getCollectionID(),
            'ctID' => $ctID,
            'truncateSummaries' => '0',
            'displayFeaturedOnly' => '0',
            'paginate' => '1'
        );
        $bO = $bP->addBlock('page_list', 'Main', $pLConfig);

In order to list our posts we're going to add a concrete5 Page List block to the blog page we've just created. Note how we get this page object by its path. Also note the $pLConfig array which contains all the options I want to set for our page list block. I want to display a maximum of 10 posts. I want to enable the RSS icon. I want to list pages under a particular parent, in this case the /blog page, and I only want to list pages of a particular page type, 'first_blog_post'. The paginate option will set up paging at the foot of the page list block when available content exceeds the content I have set the block to list, which, in this case, is 10 posts. The block is installed to the 'Main' area, which along with "Sidebar" are two areas you should expect to be available in the 'default' page type. Testing for the existence of these however, would make these routines more robust.

        ## Set custom template
        if(is_object($bO)) {
            $bO->setCustomTemplate('blog_index.php');
        }

Before we leave the page list block we set a custom template that is included with it, which greatly improves the presentation of our blog list. Unusually in concrete5 we actually need the file extension which is '.php'.

        ## Add Content Block.
        $bP->addBlock('content', 'Sidebar', array('btConfig' => '1', 'content' => 'Archive'));

Next we add a content block to the Sidebar area, and we set up the content so it reads 'Archive'.

        ## Add Date Nav block
        $dNConfig =  array(
            'ctID' => $ctID,
            'cParentID' => $bP->getCollectionID(),
            'displayFeaturedOnly' => '0'
        );
        $bP->addBlock('date_nav', 'Sidebar', $dNConfig);
    } 

We're adding a 'Date Nav' block to the Sidebar. Note the options we're passing in the $dNConfig array. This block gives us a handy archive of our blog posts grouped under month and year of publication.

    ## Install Single pages
    Loader::model('single_page');
    ## Dashboard
    $blog = SinglePage::add('/dashboard/system/environment/blog', $pkg);
    $blog->update(array('cName' => 'Blog', 'cDescription' => 'Configure Blog settings'));
}

Once we've loaded another class, so that we can run this functionality, the last operation in our install() method is to add the single page (and controller) to our concrete5 Dashboard. As you can see the path determines where it is installed. You'll also see that we pass the $pkg variable again. When we uninstall the package, our single page will be removed for us.

    public function uninstall() {
        parent::uninstall();
        ## Remove the blog page and children
        Loader::model('page');
        $blog = page::getByPath('/blog');
        if(is_object($blog)){
            $blog->delete();
        }
    }
}

Finally, we have an uninstall() method to complete our class. The parent classes' uninstall() method is invoked, and additionally we remove the /blog page we added in the install() method above.

Now, once we've completed Part 2 of this tutorial, if we install this package in our concrete5 application, when we check out your concrete5 Dashboard. In 'System and Settings' we should have a new menu option under 'Environment' titled 'Blog'.

Under 'Pages and & Themes', in 'Page Types' we should find a new page type called 'Blog Post' and, if we inspect it's defaults, you'll see the two blocks we installed.

In our Sitemap, we should have a new page called 'blog' which is accessible at the URL /blog. Visit that page and we will see the three blocks we added via the package install() method.

If we uninstall the package, everything we just added should be removed.

We're all set, we have everything we need to make blog posts, We need to customise our 'first_blog_post' page type a little, and then populate the controller and view for our new Dashboard page, and we'll do both tasks in part 2 of this tutorial.

Join the conversation

comments powered by Disqus