Coding and more – Concrete5, Flex, JavaScript

Latest from the blog

concrete5.7 upgrade packages

As you might have heard, a major release of concrete5 has been published. If you haven’t, here’s a list of things that have changed http://www.concrete5.org/documentation/background/version_history/5-7-0/.

In this post, I’m going to look at the process of upgrading an existing package which worked for version 5.6. I’m doing this step by step to show to show you possible error messages which might help you convert you own packages, but please note that I’m aiming at concrete5 developers knowing version 5.6 and will thus skip a few things which haven’t changed.

I’m going to use a simple package I wrote a while ago, it’s the zoom image block which allows you to zoom into a single image. Much like lightbox, but limited to a single picture. The code can be found here https://github.com/Remo/concrete5-zoom-image.

The Directory Structure of concrete5.7

Once you’ve downloaded and installed concrete5.7 you’ll probably see that the directory structure has changed quite a bit:
concrete5.7 directory structure

  • The root directory
    • There are only four directories left
    • application – this is where your overrides for your own site should be placed
    • concrete – this is still the core and should never be touched unless
    • packages – this is obviously the place where you have to add your own packages
    • updates – still the same, here’s where you can find an updated core
  • What new directories are there
    • authentication – there’s a pretty neat authentication system you can extend, by default there’s a built-in concrete5 system and one supporting facebook
    • bootstrap – as the name tells you, here’s where things get started, loaded etc.
    • src – this is where you can find the PSR4 auto-loaded classes, have a look at this directory to get acquainted with the new things in concrete5
    • vendor – version 5.7 partially uses composer.json to manage dependencies, this is where you can find the libraries installed by composer

Installing the zoom image package

Before you install anything, make sure you’ve got a database dump you can easily restore, it might make things easier.

I simply downloaded the latest version from github and placed it in a directory called zoom_image in the packages directory. In concrete5, type add functionality in the search box and select the first item. You’ll already see your first exception at this point:

concrete5.7 exception.

We can’t see any details about the error, now what? You can either look at your database and check the content of the Logs table or go back and type debug in the search box. Navigate to debug settings and check Show errors in page.. If you then to back to install your package, you’ll see a nice output produces by https://github.com/filp/whoops. It tells use, that we can’t redeclare the class ZoomImagePackage. That’s because concrete5.7 uses namespaces for pretty much everything. To get around this problem, we have to update the controller in our package. The first few lines have to look like this:

1
2
3
4
5
6
7
8
9
10
11
<?php
 
namespace Concrete\Package\ZoomImage;
 
defined('C5_EXECUTE') or die('Access Denied.');
 
class Controller extends \Concrete\Core\Package\Package {
 
    protected $pkgHandle = 'zoom_image';
    protected $appVersionRequired = '5.7.0';
    protected $pkgVersion = '2.0.0';
  • A package controller class is always called Controller.
  • It derives from \Concrete\Core\Package\Package
  • The namespace of your package controller has to start with Concrete\Package\ and must then be append with the camelcased name of your package.
  • I also updated the number to be 2.0.0, that’s not necessary, but probably make sense

When you reload the add functionality page again, you’ll finally see our package. Let’s try and install it! Next error:
Class 'Concrete\Package\ZoomImage\BlockType' not found. As I’ve mentioned above, the src directory contains some rather useful classes. We’re looking for something related to blocks and no surprise, there’s a Block directory. If you look at src/Block/BlockType/BlockType.php you can see where the missing class is. Open the file and you’ll know the namespace we have to import.

1
2
3
4
5
<?php
 
namespace Concrete\Package\ZoomImage;
 
use Concrete\Core\Block\BlockType\BlockType;

Unfortunately the package got installed anyway and uninstalling can be tricky if parts of your code haven’t been upgraded for 5.7. Let’s just restore that dump file we’ve created and try to install the package again. Guess what, the next exception. The zoom image block uses an old out-dated method called Loader::block. Remove it, restore the dump file and try again. Next, you’ll get an exception because BlockController isn’t defined. Namespaces again! While we do this, we can also make the class name change like we did with the package controller.

  • The class is simply called Controller too.
  • You’ll have to derive it from \Concrete\Core\Block\BlockController. If you look at the code below, you can see that I didn’t specify the fully qualifier name when deriving the class but rather added another statement to use.
  • Your namespace has to be Concrete\Package\[camel-case-package-handle]\Block\[camel-case-block-handle]

After making these changes, restore the dump file and install the package again – the installation process works! Now let’s try to add a zoom image block. Go back to the website and hit the plus icon:

concrete5.7 add block

Drag your block into the page and release it. The block interface shows up, but after adding the block, nothing happens. That’s because the AJAX magic hid the error message, reload the page and you’ll get your next exception to work with Class 'Concrete\Package\ZoomImage\Block\ZoomImage\File' not found. As you can see, it tries to load the class File in our own namespace where it can’t be found. We forgot to add another use statement. Let’s add \Concrete\Core\File\File and reload the page.

We now get the message Call to a member function getFileObject() on a non-object.

concrete5.7 exception 2

Accessing the controller from view.php was a bit of a habit when you looked at code back from version 5.0 and a bit later. Before we worry about the message, let’s make sure we properly forward data from the controller to the view and not the other way round.

Let’s add the following method to the block controller:

1
2
3
4
5
6
7
8
9
10
11
public function view() {
    $ih = Loader::helper('image');
 
    $fileObject = $this->getFileObject();
 
    $fileName = $fileObject->getRelativePath();
    $thumbnail = $ih->getThumbnail($fileObject, intval($this->thumbnailWidth), intval($this->thumbnailHeight));
 
    $this->set('fileName', $fileName);
    $this->set('thumbnail', $thumbnail);
}

Use those variables in your view.php and then try to reload the page. No error messages for a change, but when you publish the page changes and try to click on the thumbnail of your block, nothing happens. If we check the error console of the browser, we can see a problem due to undefined jQuery variables. At the time when our javascript gets executed, jQuery isn’t loaded. We don’t think too much about it and simply use addFooterItem instead of addHeaderItem.

Reload and still no luck, msie isn’t defined. But luckily that’s just because of our JavaScript which isn’t happy with the new jQuery version. Update the script to get around that.

That’s it! JavaScript fixed and no more problems, everything working! While this article is a bit long, what we actually did isn’t that much. In short:

  • Namespaced all of our controllers, the package and block controller
  • Made sure we properly forwarded our variables from the controller to the view (something I should have done before)
  • Fixed a problem because of the new jQuery version in concrete5.7

You can find the commit I’ve made on github, a proper diff file might help https://github.com/Remo/concrete5-zoom-image/commit/fa0895081b62fbbb53d8e79c00858619fcec1fd4.

Have fun with 5.7!

Floating Point Arithmetic in MySQL

The fact that computers aren’t perfect at calculation is something most developers know. 1/3 can’t be saved as a floating point number, it would be infinitely long, 0.3333… If you aren’t aware of that, check out this site, it explains the problem quite nicely http://floating-point-gui.de/.

Most people who work with SQL rarely come across this problem, but there’s one pitfall which in my opinion is even more dangerous, especially since SQL handles this problem in most cases quite well.

Let’s start by creating a dummy table and insert a row with two numbers:

DROP TABLE IF EXISTS mathtest;
CREATE TABLE mathtest (num_dec DECIMAL(20, 10), num_float FLOAT);
INSERT INTO mathtest VALUES (2018.446510036496, 2018.446510036496);

Now that we have some data to work with, let’s query our table and add two columns with a static value.

SELECT
	round(num_float, 9) table_float,
	round(num_dec, 9) table_decimal,
	round(2018.446510036496, 9) static_float,
	round(CAST(2018.446510036496 AS DECIMAL(20,10)), 9) static_decimal
FROM mathtest;

What will the result be of this? The first table_float is pretty obvious, it’s very imprecise, but what about the others? Let’s have a look:

Table Float 2018.446533203
Table Decimal 2018.446510037
Static Float 2018.446510036
Static Decimal 2018.446510037

What does this tell us? It’s simple, when you query data from a table, it will use the precision of the column type. But when you do some arithmetic in an SQL query, it will use floats by default and thus be imprecise. If we cast it to a decimal, we can get a precision of 64 digits.

Not a big deal, but make sure you’re aware how you do your calculations in SQL!

concrete5 – Working with custom Permission Keys

When you build an add-on with concrete5, you’ll sometimes want to hide a few things depending on the users or group. You might be tempted to hard-code a check to a specific group, but there’s a much more elegant way to handle this. It also gives you a lot more power to control who’s allowed to do something, just in case your customer changes.

In this tutorial, we’re building a new package called “codeblog_task_permissions” which you have to put in your “packages” directory. Within the new package directory, create a new file called “controller.php” and put the following content in it, we’ll have a closer look at what it does afterwards:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?php
 
class TaskPermissionsPackage extends Package {
 
    protected $pkgHandle = 'task_permissions';
    protected $appVersionRequired = '5.6.3';
    protected $pkgVersion = '1.0';
 
    public function getPackageDescription() {
        return t("Installs the Task Permission demo package.");
    }
 
    public function getPackageName() {
        return t("Task Permissions");
    }
 
    public function install() {
        $pkg = parent::install();
        $this->installTaskPermissions($pkg);
    }
 
    /**
     * This method installs our permission keys
     * 
     * @param Package $pkg
     */
    protected function installTaskPermissions($pkg) {
        // add a new permission key to handle shutdons
        $pkShutdownHandle = 'shutdown_planet';
        if (!is_object(PermissionKey::getByHandle($pkShutdownHandle))) {
            $pkShutdown = PermissionKey::add('admin', $pkShutdownHandle, t('Shutdown the planet'), t('Permission to shutdown the planet'), '', '', $pkg);
 
            // assign administrators the right to handle our planet
            $group = Group::getByID(ADMIN_GROUP_ID);
            $adminGroupEntity = GroupPermissionAccessEntity::getOrCreate($group);
 
            $pa = PermissionAccess::create($pkShutdown);
            $pa->addListItem($adminGroupEntity);
            $pt = $pkShutdown->getPermissionAssignmentObject();
            $pt->assignPermissionAccess($pa);
        }
 
        // install a second permission key to control the weather
        $pkWeatherHandle = 'make_weather_nice';
        if (!is_object(PermissionKey::getByHandle($pkWeatherHandle))) {
            $pkWeather = PermissionKey::add('admin', $pkWeatherHandle, t('Remote Weather Control'), t('Access to the remote weather control system'), '', '', $pkg);
        }
    }
 
}

Let’s have a look at the simpler example. In line 44 we’re setting the handle of the permission key, it’s what we need to work with it when we run our check in the code. In the next line, we check if the permission key is already installed, if it isn’t, we add it. These are the parameters you can use:

public static function add(
        $pkCategoryHandle, 
        $pkHandle, 
        $pkName, 
        $pkDescription, 
        $pkCanTriggerWorkflow, 
        $pkHasCustomClass, 
        $pkg = false
)

That’s all we need to add a custom attribute key. If you install the package and navigate to “/dashboard/system/permissions/tasks/” in your dashboard, you’ll see your custom permission keys at the end of the screen.

permissionkeys

As you can see, our new permission key doesn’t have any groups assigned to it. You can either do that manually, or by code. In the example above, the first permission key shows you how to do that. Just have a look at the lines 33 to 40. We get an instance of our administrators group and then pass it along a permission access object which we can assign to our permission key.

Now that we have created our permission keys, we want to use them. This part is even easier, all you have to do is to create a PermissionKey object and run the “can()” method:

1
2
3
4
5
6
$pk = PermissionKey::getByHandle('shutdown_planet');
if ($pk->can()) {
    echo t('Yes you are allowed to shutdown the planet');
} else {
    echo t('We are sorry but you have no permissions to shutdown the planet');
}

You can find the complete example on github, https://github.com/Remo/codeblog/tree/master/codeblog_task_permissions. If you use the example and want to check the permissions, point your browser to http://1/index.php/tools/packages/task_permissions/check_permissions and you’ll see weather you have the permission to the keys or not.

concrete5 – use LESS files for block templates

If you built a couple of block templates, you’ll probably have had some nasty CSS code. Quite often you’ll have block specific options, a background color for example, which has to change the CSS file. You can easily do that, no doubt, just use addHeaderItem or put a style tag in your view.php. The block controller code might look like this:

1
2
3
public function on_page_view() {
   $this->addHeaderItem('<style type="text/css">#my-block-' . $this->bID . ' { background: ' . $this->backgroundColor . '; }</style>');
}

In most cases you’ll find situations where the developer mixed a code like shown above with an external CSS file.

That certainly works but it’s usually much more elegant to keep all the block related CSS rules in a single file. We can use the great LESS language to achieve a much more elegant solution. Wouldn’t it be great if you could put all the CSS rules in a file like this:

1
2
3
4
5
6
#my-block@{bID} {
   background: @backgroundColor;
   display: block;
   width: 100px;
   height: 100px;
}

That’s possible, it just takes a bit more code. You’ll need lessphp available here http://leafo.net/lessphp/. In your block controller, you can use a method like this to compile a LESS file like the one shown above into a static CSS with all variables replaced on the fly if required. Here’s the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public function on_page_view() {
    // get current block template
    $template = $this->getCurrentTemplate();
 
    $bv = new BlockView();
    $bv->setController($this);
    $bv->setBlockObject($this->getBlockObject());
 
    // build path to less file
    $blockPath = $bv->getBlockPath();
    if ($template == '') {
        $blockTemplateLessPath = $blockPath . DIRECTORY_SEPARATOR . 'view.less';
    } else {
        $blockTemplateLessPath = $blockPath . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . $template . DIRECTORY_SEPARATOR . 'view.less';
    }
 
    // there's a less file, check if we have to rebuild it
    if (file_exists($blockTemplateLessPath)) {
        $lessFileHash = md5($blockTemplateLessPath . $this->bID . filemtime($blockTemplateLessPath));
        $cacheFile = DIR_FILES_CACHE . '/my-block-' . $lessFileHash . '.css';
 
        // cache file doesn't exist, rebuild it
        if (!file_exists($cacheFile)) {
            $lessc = Loader::library('3rdparty/lessc.inc', 'my_block');
            $lessc = new lessc();
            $lessc->setVariables(
                    array(
                        'backgroundColor' => $this->backgroundColor,
                        'bID' => $this->bID
                    )
            );
            $lessc->compileFile($blockTemplateLessPath, $cacheFile);
        }
 
        // include generated css file
        $this->addHeaderItem('<link rel="stylesheet type="text/css" href="' . REL_DIR_FILES_CACHE . '/cover-picture-' . $lessFileHash . '.css' . '"/>');
    }

If these instructions were a bit short, you can find a working example in one of my github repositories, check this: https://github.com/Remo/concrete5-cover-picture/blob/master/blocks/cover_picture/controller.php#L43-L81.

I hope this helps you to clean up your code!

Book Review – Learning FuelPHP for Effective PHP Development

I’ve been playing around with FuelPHP for a while and despite the fact that I haven’t used it in production, it has been on my watch list ever since I first saw it. When I saw the new book about FuelPHP by Ross Tweedie, I was eager to read it – here’s my feedback about. If you just want to buy the book, you can get it at Amazon or directly from Packt Publishing.

About the book

The book has about 80 pages of actual content and covers these topics:

  • Chapter 1 – A few words about the history, some ideas behind FuelPHP and a good bit of theory.
  • Chapter 2 – Installation using the console script and a few words about alternative methods like GIT.
  • Chapter 3 – You’ll find information about the architecture in this chapter, (H)MVC, Configurations etc.
  • Chapter 4 – Shows you how to create models, controllers to build a simple blog application.
  • Chapter 5 – A list of packages you can find in the internet as well as a quick introduction showing you how to create your own package.
  • Chapter 6 – A few advanced topics like unit testing, routing concepts, custom oil (the FuelPHP console tool) tasks and profiling.
  • Chapter 7 – briefly covers the FuelPHP community and where to find more information about FuelPHP if you need it.

Targeted Audience

Without having read who the author actually targets, to me it feels like you’ll have to know quite a bit of PHP and some of the tools you use when writing software. At the beginning, you’ll have a nice overview where you can get a good understanding for FuelPHP and why yet another framework does make sense. The author also starts to use some words like closures, singletons, multitons pretty quickly. There are a few words explaining it, but I don’t feel I’d have actually understood it, if I haven’t worked with these things before. Not every person learns the same way, but I’d have liked to see a few links where I could actually see some example code of a multiton pattern.

Not the fault of the author, but software changes and usually much quicker than paper does. The book seems to have used version 1.6 as well as 1.7 when writing his book. I started straight with the current version which is currently 1.7.1. The good news, it will work just fine, even if the book is focused on 1.6. Not related to the book but more about FuelPHP, version 2.0 has been announced a while ago. The author also covers some changes we’ll see and that’s a bit of a downside to me. I do like everything that will change, but it makes me want to skip the 1.x version.

The bad sides

The author seems to be a skilled developer and likes to write software. I believe that, because I see some similarities with my own behaviour. The book covers quite a few things, things like GIT which are great (imho), but the explanation will hardly be good enough for someone who hasn’t worked with it before. I’d have recommended to keep that part in a blog and simply link to it. As soon as you run into an issue with GIT, you’re on your own. Although, I tried to go through the book without setting up a repository and it worked fine.

There are other situations, for example in the installation where the required PHP modules are described, but not with a name you can use to find the actual PHP extension you’ll need. Luckily that shouldn’t be an issue since FuelPHP doesn’t require much.

While FuelPHP works on different webservers like Apache, Nginx, IIS and probably a few more, most of the book is focused on Apache and *nix. There are a few shell commands that you won’t be able to run on a Windows computer, at least if you don’t want to play around with Cygwin or something similar.

Here’s a list which I think should be improved if there’s going to a be a second edition:

  • What the heck is a “Temporal ORM”? I do know quite a bit about SQL, ORM but was surprised to see a term right at the beginning of a book which is completely new to me. Luckily for me, Google also has only 36 results about this expresison
  • There are some magic commands in the book. While I understand their benefit, I’d recommend to leave them out and focus on the actual FuelPHP part. An information box with a sentence or two and a link would have been sufficient.
  • Probably something the publisher should improve. Just like my books, the code formatting could be better – I’ve seen worse but perfectly indented code is nice! I’d also recommend to highlight the line that actually matters.
  • I was sometimes confused about instructions, they were in the middle of a step by step procedure but sounded quite theoretical.
  • Make sure things are complete, when writing that you simply have to add a package to composer.json to install a new module, that’s incorrect, you’ll have to run composer update too. These things are small, but details matter, especially to beginners

You’ll find a few more situations, but after all I like the book and don’t want to give you an impression that’s worse than I think it should be.

The good parts

I like the started, the introduction about FuelPHP. I often ask myself: Do we really need this? Whether it is a PHP framework, a new Linux desktop. You’ll get a good feeling about what FuelPHP is about.

The demo application is perfect, it shows you how to create models in no time, how to run migration scripts to manage the creation but also the change of the database structure. It quickly covers the controller part, a few words about models and their relationships and bit about the output. You’ll also learn how to create a module and a package, their difference is well explained but will change in v2.0 as far as I know.

Should I read this book?

It depends on your background – I wouldn’t recommend it if you’re a PHP programmer who hasn’t worked with namespaces, databases before. But please keep in mind that this is a short book of only about 80 pages content. FuelPHP might have deserved a bit more but the shortness was nice – at least for someone like me who has worked with all kinds of frameworks. I’d definitely recommend to book if you worked with other frameworks like CodeIgniter, Yii, Zend .. before and now want to have a look at FuelPHP. You’ll get a good impression about its possibilities!

concrete5 – searching and cloning pages

In this short tutorial I’ll show you two different things. We’ll start with a simple example that explains one function of the PageList class and then another method you can use to duplicate a page.

Searching for pages by an attribute with PageList

The PageList class is very powerful, especially in combination with attributes. In order to be able to search after an attribute, we’ll create a new attribute called “Duplicate this Page”. We’ll use it to mark pages we’d like to clone. First, open the attributes page (/index.php/dashboard/pages/attributes/) and select the checkbox type like shown here:

page_attr

Click on the “Add” button and create the attribute using these values:

page_attr_detail

Now go back to the sitemap and pick a few random pages and assign the new attribute to them and tick the checkbox. Let’s have a quick look at some code. We’ll create a new file called “test.php” in the “tools” directory.

1
2
3
4
5
6
7
8
9
10
// build PageList object to get all pages where the attribute
// with the handle "duplicate_page" is checked
$pl = new PageList();
$pl->filterByDuplicatePage(true);
 
$pages = $pl->get(0);
 
foreach ($pages as $page) {
    echo $page->getCollectionName() . '<br/>';
}

When you now open /index.php/tools/test/ you’ll get the names of all pages where you’ve assigned our new attribute.

The PageList class has a magic method with which you can search for attribute values in an elegant way. Simple append the handle in CamelCase notation after “filterBy”. Your attribute handle is “very_lovely_day”, use $pl->filterByVeryLovelyDay(…); This works not only with checkbox attributes but with almost all attributes you can find.

Cloning pages

Assuming we’d want to copy those pages to a new location, we could easily use the “duplicate” method. But first, create a new page in the sitemap underneath you want the cloned pages to appear. Mine is called “Target Container” and hass the path “/target-container”. We use this in combination with the script we wrote above to get the final result:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
 
// get target container underneath which we'll create the cloned pages
$targetPage = Page::getByPath('/target-container/');
 
// build PageList object to get all pages where the attribute
// with the handle "duplicate_page" is checked
$pl = new PageList();
$pl->filterByDuplicatePage(true);
 
// return _ALL_ pages we've found. If you have hundreds of pages
// you might want to use $pl->getPage(); and run the script several
// times
$pages = $pl->get(0);
 
foreach ($pages as $page) {
    $page->duplicate($targetPage);
}

When you open /index.php/tools/test again, you’ll get some clones in no time! But be aware, if you run the script again, you’ll get more clones.

concrete5 class loader

If if worked with concrete5 before, you’ll probably have seen lots of Loader::model or Loader::helper. A few versions ago these calls were pretty hard to avoid and you had to use them rather often. The core still doesn’t have namespaces due to the fact that the development of concrete5 has started when PHP didn’t know anything about namespaces. The class loader does help a lot though, even if there are no namespaces.

Here’s a simple example showing you how to load classes automatically.

All classes you want to load have to specified in an array in your package controller:

$classes = array(
    'TestClass' => array('model', 'test_class', $this->pkgHandle),
);
Loader::registerAutoload($classes);

Complete file: https://github.com/Remo/codeblog/blob/master/codeblog_classloader/controller.php

If you want to load a helper, replace the first parameter “model” with “helper”. The second parameter is the file name but without the file extension and the last parameter is the handle of the package where our class is located. Once we’ve specified the class names, we can pass it on to Loader::registerAutoload and are almost finished.

When you now want to use our “TestClass”, you can simply use this code:

echo TestClass::saySomething();

Complete code: https://github.com/Remo/codeblog/blob/master/codeblog_classloader/tools/test.php

You can find a working package here https://github.com/Remo/codeblog/tree/master/codeblog_classloader
Once you’ve installed it, you can call the tools file to test everything by opening “/index.php/tools/packages/classloader/test”, e.g. http://localhost/index.php/tools/packages/classloader/test

concrete5 – package installer based on XML file

If you ever built a concrete5 package before, you’ll probably have had to write some code to handle the installation process where you check if an object is already installed and if not, add it. Especially with packages that are in use, this can take a while and the code gets messy very quickly. However, there’s an alternative which is not yet well known.

Creating installation XML

Before we start looking at some code, how do we get our XML content? There are a ton of different objects, but luckily, we can generate an XML from an existing site very easily. To do that, you’ll have to install the Sampel Content Generator you can find here http://www.concrete5.org/marketplace/addons/sample-content-generator/. Please note that you currently have to install it manually as it’s marked as incompatible with version 5.6.

Once you’ve installed the add-on, you can find a new single page in your dashboard. If you open it, you’ll find two buttons, the first one “Archive Files” to create a complete zip file of the site and the second one “Generate content.xml from current website” to get the output we’re looking for. If you click on the second button, you’ll see a screen like this:

installation-data

The output you can see in this screen contains everything you’ve got in your site. That’s way too much for a package but it’s pretty easy to find the elements you need. Most elements are pretty self-explanatory. In my example, I’ve needed two elements, one I had to add using a previous version of my package controller (attributetypes) and one I’ve added in the dashboard (attributetypes). The complete content of my XML looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0"?>
<concrete5-cif version="1.0">    
    <attributetypes>
        <attributetype handle="remo_phasher" package="remo_phasher">
            <categories>
                <category handle="file"/>
            </categories>
        </attributetype>
    </attributetypes>    
    <attributekeys>
        <attributekey handle="image_hash" name="Image Hash" package="" searchable="1" indexed="1" type="remo_phasher" category="file"/>
    </attributekeys>
</concrete5-cif>

I’ll put this content in a file called install.xml right in the root of the package. It looks like this:

file-structure

Install XML content from package controller

In our package controller, all we need is this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
 
defined('C5_EXECUTE') or die('Access Denied.');
 
class RemoPhasherPackage extends Package {
 
    protected $pkgHandle = 'remo_phasher';
    protected $appVersionRequired = '5.6.2.1';
    protected $pkgVersion = '0.9';
 
    public function getPackageDescription() {
        return t("Adds perceptual hash to images.");
    }
 
    public function getPackageName() {
        return t("Image pHasher");
    }
 
    public function install() {
        $pkg = parent::install();
 
        $ci = new ContentImporter();
        $ci->importContentFile($pkg->getPackagePath() . '/install.xml');
    }
 
    public function upgrade() {
        $pkg = Package::getByHandle($this->pkgHandle);
        $ci = new ContentImporter();
        $ci->importContentFile($pkg->getPackagePath() . '/install.xml');
 
        parent::upgrade();
    }
}

We simply get an instance of ContentImporter and import the content of our XML file in both methods, install as well as upgrade. If you need a new item, just add it to install.xml and you’re good to go. You can find a complete example on github: https://github.com/Remo/concrete5-phasher.

concrete5 – programmatically creating a page alias

in concrete5 you can create an alias if you want a page to appear at two different locations. If you look at the following site structure, you can see that we have two categories and two different services we’d like to offer.

alias_1

For whatever reason, we’d like to make sure that our main service shows up beneath both categories. It’s physical location is right under the first category but how do we add an alias underneath the second category? In the UI this is super easy, just drag the service to the second category and release the mouse button – a dialog will show up where you can select that you want to create an alias and that’s it! But how do we the same using code? It’s also pretty simple:

1
2
3
4
<?php
$pageToAlias = Page::getByPath('/category-1/service');
$parentOfAlias = Page::getByPath('/category-2');
$newAlias = $pageToAlias->addCollectionAlias($parentOfAlias);

I’ve used Page::getByPath to get both page objects but you can of course use Page::getByID or anything else that returns a valid page object. As soon as you’ve run this code, you’ll have an alias in your sitemap like that:

alias_2

concrete5 – smart attribute/picture fetching through page hierarchy

In this article I’m going to explain a nice code that works with attribute. It’s something quite a few people are using but it never crossed my mind to write something about it until my friends from 100pro asked for it.

Let’s start with a quick look at the problem we’re going to solve. Our page layout is pretty simple, two columns, some content and in our case the most important part: A picture in the head of every page:

attribute_recursion_header_pic

Assuming we’ve got the following structure in our sitemap:

attribute_recursion_sitemap

As you can see, this company has two slightly different offers on their site. Due to that, the boss wants to have different pictures that match the two topics. Easy, you might say, just add a block in the header of each page – until you notice that there are quite a few pictures & videos and thus lots of pages. Still easy you might say, why not create two page types and use the page type defaults to manage these pictures? Well, you could do that but using a page type for that feels wrong and sometimes it’s not that clear that you need two different pictures.

Silly story short: We want to specify a picture for “Business 2 Business” which is used on that page as well as all its children and we also want to specify a picture for “Funny Things” and its children.

Use an attribute from parent pages

We’re going to use an attribute to assign a picture to these two pages. If you’re logged in, open this page on your site: /dashboard/pages/attributes/. At the bottom, select “Image/File” and hit “Add”. Enter “header_pic” for handle and “Header Picture” for name.

Next, go back to the sitemap /dashboard/sitemap/full/ and click on “Business 2 Business” and then “Properties”. If you switch to “Custom Attributes” and scroll to the bottom, you’ll find our new attribute. Click on it and select a picture of your choice. Save everything and do the same for the second page “Funny Things”. Now that we’ve assigned the two pictures to our pages, it’s time to do the coding.

Fetch attribute from parent pages

This step might look slightly different depending on your theme but should be farily easy to adapt with a basic understand of HTML. The header area you want to replace with the picture selected in the attribute is mostly likely found in elements/header.php of your theme. In my case, the part we have to modify looks like this:

1
2
3
4
5
6
7
8
9
10
<div id="header-area">
   <div class="divider"></div>
   <div id="header-area-inside">
   <?php 			
   $ah = new Area('Header');
   $ah->display($c);			
   ?>	
   </div>				
   <div class="divider"></div>
</div>

Looks have a look at the comment code that replaces the code above:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<div id="header-area">
    <div class="divider"></div>
    <div id="header-area-inside">
        <?php
        $nh = Loader::helper('navigation');
        $ih = Loader::helper('image');
 
        $c = Page::getCurrentPage();
 
        // get list parent pages of current page
        $collectionTrail = $nh->getTrailToCollection($c);
 
        // add current page to array
        array_unshift($collectionTrail, $c);
 
        // loop through all the pages back to the home page
        foreach ($collectionTrail as $page) {
            // get attribute from page
            $picture = $page->getAttribute('header_pic');
 
            // check if attribute on page exists
            if ($picture instanceof File) {
                // make sure the picture isn't bigger than 800x300, cameras
                // these days take huge pictures
                $thumbnail = $ih->getThumbnail($picture, 800, 300, true);
 
                // print thumbnail
                echo "<img src=\"{$thumbnail->src}\" alt=\"\"/>";
 
                // stop searching through more pages
                break;
            }
        }
        ?>	
    </div>				
    <div class="divider"></div>
</div>

This code uses the navigation helper to get a list of all the parent pages and then loops through all of them until a page is found where the attribute with the handle “header_pic” has a valid file. In this case, we replaced the existing area but you can of course also keep it and add the code shown above as an extension.

The method “getTrailToCollection” is usually used to build a breadcrumb like navigation. There’s an autonav template called “breadcrumb” that does that as well but if you wonder how to create a breadcrumb navigation with code, here’s an example:

1
2
3
4
5
6
$nh = Loader::helper('navigation');
$c = Page::getCurrentPage();
$collectionTrail = array_reverse($nh->getTrailToCollection($c));
foreach ($collectionTrail as $page) {
    echo "<a href=\"{$nh->getLinkToCollection($page)}\">{$page->getCollectionName()}</a> ";
}

I hope you were able to get nice pictures with little effort into your site! If not, post a comment and I’ll do my best to get it fixed!