Everything in a Package with concrete5

Exclusive offer: get 50% off this eBook here
concrete5 Beginner's Guide

concrete5 Beginner's Guide — Save 50%

Create and customize your own website with the Concrete5 Beginner's Guide

$26.99    $13.50
by Remo Laubacher | March 2011 | Beginner's Guides Open Source Web Development

While we are able to create and improve a lot of different things in concrete5 without touching the actual core files in the concrete directory, we might have had to manually install several elements to get our functionality into a new site. By using a package, we can wrap all the previously created elements into a single directory, which can be installed by a single click on the dashboard.

Being able to customize and extend almost everything can make things a bit messy. Have a look at this article, by Remo Laubacher, author of concrete5 Beginner's Guide, to see how you can wrap things in a package for an easier handling.

 

concrete5 Beginner's Guide

concrete5 Beginner's Guide

Create and customize your own website with the Concrete5 Beginner's Guide

        Read more about this book      

(For more resources on this subject, see here.)

What's a package?

Before we start creating our package, here are a few words about the functionality and purpose of packages:

  • They can hold a single or several themes together
  • You can include blocks which your theme needs
  • You can check the requirements during the installation process in case your package depends on other blocks, configurations, and so on
  • A package can be used to hook into events raised by concrete5 to execute custom code during different kind of actions
  • You can create jobs, which run periodically to improve or check things in your website

These are the most important things you can do with a package; some of it doesn't depend on packages, but is easier to handle if you use packages. It's up to you, but putting every extension in a package might even be useful if there's just a single element in it—why?

  • You never have to worry where to extract the add-on. It always belongs in the packages directory
  • An add-on wrapped in a package can be submitted to the concrete5 marketplace allowing you to earn money or make some people in the community happy by releasing your add-on for free

Package structure

We've already looked at different structures and you are probably already familiar with most of the directories in concrete5. Before we continue, here are a few words about the package structure, as it's essential that you understand its concept before we continue.

A package is basically a complete concrete5 structure within one directory. All the directories are optional though. No need to create all of them, but you can create and use all of them within a single package. The directory concrete is a lot like a package as well; it's just located in its own directory and not within packages.

Package controller

Like the blocks we've created, the package has a controller as well. First of all, it is used to handle the installation process, but it's not limited to that. We can handle events and a few more things in the package controller; there's more about that later in this article.

For now, we only need the controller to make sure the dashboard knows the package name and description.

Time for action - creating the package controller

Carry out the following steps:

  1. First, create a new directory named c5book in packages.
  2. Within that directory, create a file named controller.php and put the following content in it:

    <?php
    defined('C5_EXECUTE') or die(_("Access Denied."));
    class c5bookPackage extends Package {

    protected $pkgHandle = 'c5book';
    protected $appVersionRequired = '5.4.0';
    protected $pkgVersion = '1.0';
    public function getPackageDescription() {
    return t("Theme, Templates and Blocks from
    concrete5 for Beginner's");
    }
    public function getPackageName() {
    return t("c5book");
    }

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

  3. You can create a file named icon.png 97 x 97 pixels with 4px rounded transparent corners. This is the official specification that you have to follow if you want to upload your add-on to the concrete5 marketplace.
  4. Once you've created the directory and the mandatory controller, you can go to your dashboard and click on Add Functionality. It looks a lot like a block but when you click on Install, the add-on is going to appear in the packages section.

    concrete5 tutorial

What just happened?

The controller we created looks and works a lot like a block controller, which you should have seen and created already. However, let's go through all the elements of the package controller anyway, as it's important that you understand them:

  • pkgHandle: A unique handle for your package. You'll need this when you access your package from code.
  • appVersionRequired: The minimum version required to install the add-on. concrete5 will check that during the installation process.
  • pkgVersion: The current version of the package. Make sure that you change the number when you release an update for a package; concrete5 has to know that it is installing an update and not a new version.
  • getPackageDescription: Returns the description of your package. Use the t-function to keep it translatable.
  • getPackageName: The same as above, just a bit shorter.
  • install: You could remove this method in the controller above, since we're only calling its parent method and don't check anything else. It has no influence, but we'll need this method later when we put blocks in our package. It's just a skeleton for the next steps at the moment.

Moving templates into package

Remember the templates we've created? We placed them in the top level blocks directory. Worked like a charm but imagine what happens when you create a theme which also needs some block templates in order to make sure the blocks look like the theme? You'd have to copy files into the blocks directory as well as themes. This is exactly what we're trying to avoid with packages.

It's rather easy with templates; they work almost anywhere. You just have to copy the folder slideshow from blocks to packages/c5book/blocks, as shown in the following screenshot:

concrete5 tutorial

This step was even easier than most things we did before. We simply moved our templates into a different directory—nothing else.

concrete5 looks for custom templates in different places like:

  • concrete/blocks/<block-name>/templates
  • blocks/<block-name>/templates
  • packages/<package-name>/blocks/<block-name>/templates

It doesn't matter where you put your templates, concrete5 will find them.

Moving themes and blocks into the package

Now that we've got our templates in the package, let's move the new blocks we've created into that package as well. The process is similar, but we have to call a method in the installer which installs our block. concrete5 does not automatically install blocks within packages.

This means that we have to extend the empty install method shown earlier.

Before we move the blocks into the package you should remove all blocks first. To do this, go to your dashboard, click on Add Functionality, click on the Edit button next to the block you want to move, and click on the Remove button in the next screen. We'll start with the jqzoom block.

Please note; removing a block will of course, remove all the blocks you've added to your pages. Content will be lost if you move a block into a package after you've already used it.

Time for action – moving jQZoom block into the package

Carry out the following steps:

  1. As mentioned earlier, remove the jqzoom block from you website by using the Add Functionality section in your dashboard.
  2. Move the directory blocks/jqzoom to packages/c5book/blocks.
  3. Open the package controller we created a few pages earlier; you can find it at packages/c5book/controller.php. The following snippet shows only a part of the controller, the install method. The only thing you have to do is insert the highlighted line:

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

    // install blocks
    BlockType::installBlockTypeFromPackage('jqzoom', $pkg);
    }

  4. Save the file and go to your dashboard again. Select Add Functionality and locate the c5book package; click on Edit and then Uninstall Package and confirm the process on the next screen. Back on the Add Functionality screen, reinstall the package again, which will automatically install the block.

What just happened?

Besides moving files, we only had to add a single line of code to our existing package controller. This is necessary, because blocks within packages aren't automatically installed. When installing a package, only the install method of the controller is called, exactly the place where we hook into and install our block.

The installBlockTypeFromPackage method takes two parameters: The block handle and the package object. However, this doesn't mean that packages behave like namespaces. What does this mean?

  • A block is connected to a package. This is necessary in order to be able to uninstall the block when removing the package along with some other reasons.
  • Even though there's a connection between the two objects, a block handle must be unique across all packages.

You've seen that we had to remove and reinstall the package several times while we only moved a block. At this point, it probably looks a bit weird to do that, especially as you're going to lose some content on your website.

However, when you're more familiar with the concrete5 framework, you'll usually know if you're going to need a package and make that decision before you start creating new blocks. If you're still in doubt, don't worry about it too much and create a package and not just a block. Using a package is usually the safest choice.

Don't forget that all instances of a block will be removed from all pages when you uninstall the block from your website. Make sure your package structure doesn't change before you start adding content to your website.

Time for action - moving the PDF block into the package

Some blocks depend on helpers, files and libraries, which aren't in the block directory. The PDF generator block is such an example. It depends on a file found in the tools directory in the root of your concrete5 website. How do we include such a file in a package?

  1. Move the pdf directory from blocks to packages/c5book/blocks since we also want to include the block in the package.
  2. Locate the c5book directory within packages and create a new subdirectory named tools.
  3. Move generate_pdf.php from tools to packages/c5book/tools.
  4. Create another directory named libraries in packages/c5book.
  5. Move the mpdf50 from libraries to packages/c5book/libraries. As we've moved two objects, we have to make sure our code looks for them in the right place. Open packages/c5book/tools/generate.php and look for Loader::library at the beginning of the file. We have to add a second parameter to Loader::library, as shown here:

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

    Loader::library('mpdf50/mpdf', 'c5book');
    $fh = Loader::helper('file');

    $header = <<<EOT
    <style type="text/css">
    body { font-family: Helvetica, Arial; }
    h1 { border-bottom: 1px solid black; }
    </style>
    EOT;

  6. Next, open packages/c5book/blocks/pdf/view.php. We have to add the package handle as the second parameter to make sure the tool file is loaded from the package.

    <!--hidden_in_pdf_start-->
    <?php
    defined('C5_EXECUTE') or die(_('Access Denied.'));

    $nh = Loader::helper('navigation');
    $url = Loader::helper('concrete/urls');

    $toolsUrl = $url->getToolsURL('generate_pdf', 'c5book');
    $toolsUrl .= '?p=' . rawurlencode($nh->getLinkToCollection($this-
    >c, true));

    echo "<a href=\"{$toolsUrl}\">PDF</a>";

    ?>
    <!--hidden_in_pdf_end-->

What just happened?

In the preceding example, we put got a file in the tools directory and a PDF generator in the libraries directory, which we had to move as well.

Even at the risk of saying the same thing several times: A package can contain any element of concrete5—libraries, tools, controllers, images, and so on. By putting all files in a single package directory, we can make sure that all files are installed at once, thus making sure all dependencies are met.

Nothing has changed beside the small changes we've made to the commands, which access or load an element. A helper behaves like a helper, no matter where it's located.

Have a go hero – move more add-ons

We've moved two different blocks into our new package, along with the slideshow block templates. These aren't all blocks we've created so far. Try to move all add-ons we've created into our new package. If you need more information about that process, have a look at the following page:

http://www.concrete5.org/documentation/developers/system/packages/

concrete5 Beginner's Guide Create and customize your own website with the Concrete5 Beginner's Guide
Published: March 2011
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources on this subject, see here.)

Hooking into core events

You've made it through a lot of different concrete5 features if you got to this point. We've changed the layout, added new styles, added new functionality, and even wrapped these things in a package.

However, what if you wanted to react to things happening in the concrete5 core? You want to know when a page has been added, a group deleted, or a new user added. All of that can be achieved by using events and concrete5 will tell you what's going on and let you execute custom code and even interrupt some processes.

Before you can start using events, you have to enable them. By default, they are not enabled, as most websites and website administrators don't need them and would be nothing but overhead.

To do this, you have to set the constant ENABLE_APPLICATION_EVENTS to true. Open config/site.php and insert the highlighted line:

<?php
define('DB_SERVER', 'localhost');
define('DB_USERNAME', 'concrete5');
define('DB_PASSWORD', 'concrete5');
define('DB_DATABASE', 'concrete5');
define('BASE_URL', 'http://localhost');
define('DIR_REL', '');
define('PASSWORD_SALT', 'DSiBDSPC0wQCa3pAfnhgC8o77rosAFvZAMMG');
define('ENABLE_APPLICATION_EVENTS', true);
?>

Event types

There are a lot of different events you can use to extend some core functions. The following table shows all the different events you can catch, along with their parameters described with their number and the data type, and a short description:

Page related events are fired for every action on a page. As the concrete5 dashboard has been built using pages too, an event is fired for every action happening on a dashboard page as well. You might want to think if you really want to execute an event for dashboard pages.

Extending an event

The process to extend an event is pretty much the same for all events.

There are different places where you can include the code to hook into an event but as we're dealing with packages we're going to include it in our package controller as well. Open the package controller from packages/c5book/controller.php and look for the on_start method.

It's going to look like the following code snippet if you've included the event the proper way:

function on_start() {
$html = Loader::helper('html');

// add advanced tooltips to every page
$v = View::getInstance();
$v->addHeaderItem($html->javascript('jquery.tipTip.minified.js',
$this->pkgHandle));
$v->addHeaderItem($html->css('tipTip.css', $this->pkgHandle));

$v->addHeaderItem('<script type="text/javascript">$(function(){
$("[title]").tipTip(); });</script>');

// inform about new users
Events::extend('on_user_add',
'UserInformation',
'userAdd',
'packages/' . $this->pkgHandle . '/models/user_information.php');
}

The call to Events::extend is rather simple; there are four parameters:

  1. The first parameter is obviously the name of the event you want to catch.
  2. The class name where to look for the method to be called.
  3. The method name which has to be called.
  4. Location of the file where the class and method can be found.

Before we create the actual file, we have to create a new user attribute in order to make sure the following example works. Go to your dashboard and click on Users and Groups and then User Attributes. Add a new text attribute with ip_address as the handle and IP Address as its name.

Next, we have to create the file which is called when the event is fired. Create a file named user_information.php in packages/c5book/models, open it, and put the following content in it:

<?php
defined('C5_EXECUTE') or die(_("Access Denied."));
class UserInformation {
public function userAdd($ui) {
$ui->setAttribute('ip_address', $_SERVER['REMOTE_ADDR']);
}
}
?>

This little code is called when a new user is added, and once that happens, the call to $ui->setAttribute saves the current IP address into the new attribute. This is an easy way to see the IP address for each user who has registered on your website. Nothing fancy, but it shows you how easily you can access objects from the core and add custom functionality to it.

Maintenance tasks andd jobs

Some features in concrete5 depend on jobs, which have to be periodically executed if you want to use them. By default there are three jobs installed, which you can find in the dashboard when you navigate to System & Maintenance:

  • Index Search Engine: The full text search engine uses the Zend Lucene Search library, which has to be updated by a maintenance job. If you don't execute this job regularly, the website's users will only find old, outdated content.
  • Generate Sitemap: This job writes a file named sitemap.xml in the root of your website, which helps search engine crawlers to index your site.
  • Process Email Posts: concrete5 has the ability to handle incoming e-mails. The included community-like messaging system depends on it.

The following screenshot shows you how these jobs look in the dashboard:

Attention: By default, these jobs aren't executed. It's your responsibility as a website administrator to use a scheduler available on your hosting system to make sure the jobs run as planned. As operating systems, web hosting companies, system configurations, and interfaces all differ greatly, no general solutions exist.

For now, you can simply execute the jobs by clicking on the Run Checked button. When your website runs on a server accessible from the Internet, you have to set up a scheduled task on that server. If you aren't familiar with that, get in contact with your hosting partner.

Time for action – execute concrete5 jobs periodically

Carry out the following steps to enable the scheduler on a Windows system:

  1. Download the binaries of wget from the following URL:http://gnuwin32.sourceforge.net/packages/wget.htm
  2. Extract wget into a directory without blanks.
  3. Press the Windows Key + R to open the run dialog and type cmd and confirm it by clicking on OK.
  4. In the command window, enter this command but modify the path to wget according to the path where you've installed it as well as the job URL, which you can see in your dashboard. The preceding screenshot shows the URL too.
    schtasks /create /tn "Concrete5 Jobs" /tr "C:\wget.exe http://your-site.com/index.php/tools/required/jobs?auth=9499e773311ba4305d5b4b7f35b2c115" /sc daily
  5. Confirm the command with the Enter key. You should get a confirmation that the job has been created.

SUCCESS: The scheduled task "concrete5 Jobs" has successfully been created.

What just happened?

The preceding steps installed a task to make sure that all concrete5 jobs run daily. If you want to test the command before you use it, open the console window again and run wget appended by the URL of your concrete5 job, as follows:

C:\wget.exe http://your-site.com/index.php/tools/required/jobs?auth=9499e773311ba4305d5b4b7f35b2c115

This command should return without an error and your jobs in concrete5 should all be executed. The date in the column Last Run should be updated for every job afterwards.

Please note: The scheduler runs on your local computer, which means that it will only work as long as your computer runs. If you run a website on a server, you shouldn't use your local computer to run jobs periodically, but instead use the solution from your hosting company.

If you want to, you can execute the jobs right now; it shouldn't take long and you should see an updated screen. The number of indexed pages should be higher if you haven't run the job before since we created a few new pages.

An example based on Linux cron which executes all the jobs 30 minutes past midnight would look like this:
30 * * * * /usr/bin/wget -O - -q -t 1 http://your-site.com/index.php/tools/required/jobs?auth=9499e773311ba4305d5b4b7f35b2c115

As you probably already expected, you can easily create your own jobs without touching the concrete5 core. You can also put them in a package, which is exactly what we're going to do.

Creating a new job

Jobs are always located in a directory named jobs but there are several other places you can find them:

  • In the root of your site
  • In the concrete directory
  • In every package directory

Our job is going to contain a little more PHP code than our usual examples, but since a job doesn't really produce any output, there's not much besides code. What we're going to do has been done before but not nicely integrated into concrete5. We're going to create a job, which checks all your pages for broken links. It doesn't look professional if your visitors click on links and get an ugly 404 error page.

The script is going to go through the following steps:

  1. Get a list of all pages and loop through them.
  2. Make sure the current page in the loop is accessible by the guest group as we don't want to check hidden pages to keep the output compact.
  3. If a page is accessible, we get a list of all blocks on that page.
  4. We check the block type for every block we find and skip all but the content blocks.
  5. The content block output is processed and every link is extracted using a regex pattern.
  6. As links can be absolute and relative, we have to prepend the server name in case it isn't there.
  7. We then check the HTTP status code for every link and save its result in a table.

There's going to be more than a hundred lines of code; if you don't want to type this, you can find a download link at the end of the article.

concrete5 Beginner's Guide Create and customize your own website with the Concrete5 Beginner's Guide
Published: March 2011
eBook Price: $26.99
Book Price: $44.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources on this subject, see here.)

Time for action – creating a job to check for broken links

Carry out the following steps:

  1. A package can depend on database tables just like a block does. The procedure is the same: create a db.xml file but this time it's located right in the package directory. Therefore, create a file at packages/c5book/db.xml and put the following content in it:

    <?xml version="1.0"?>
    <schema version="0.3">
    <table name="btLinkChecker">
    <field name="cID" type="I"> <field name="cID" type="I">
    <key />
    <unsigned />
    </field>
    <field name="link" type="C" size="255">
    <key />
    </field>
    <field name="linkStatusCode" type="I"></field>
    <field name="linkStatusName" type="C" size="255"></field>
    </table>
    </schema>

  2. Make sure the directory packages/c5book/jobs exists. It's where we're going to put our new job file.
  3. In that new directory, create a file named link_checker.php with the following content:

    <?php
    defined('C5_EXECUTE') or die(_("Access Denied."));
    class LinkChecker extends Job
    {
    public function getJobName()
    {
    return t("Link Checker");
    }

    public function getJobDescription()
    {
    return t("Checks your site for broken links.");
    }
    /**
    * Returns the HTTP status text for the URL
    * passed in the first argument. Uses cURL
    * with a 5 second timeout by default and
    * get_headers as a fallback in case cURL
    * isn't installed
    */
    protected function getHttpStatus($url)
    {
    if (in_array('curl', get_loaded_extensions()))
    {
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_HEADER, 1);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 5);
    curl_exec($curl);
    $ret = curl_getinfo($curl);
    curl_close($curl);
    return $ret['http_code'];
    }
    else
    {
    $headers = get_headers($url);
    return $headers[0];
    }
    }

    public function run()
    {
    Loader::model('page_list');
    $nh = Loader::helper('navigation');
    $db = Loader::db();
    $g = Group::getByID(GUEST_GROUP_ID);
    $linksFound = 0;
    $brokenLinksFound = 0;
    $pl = new PageList();
    $pl->ignoreAliases();
    $regexLinkPattern = 'href=\"([^\"]*)\"';
    $regexStatusPattern = '(.*) ([0-9]{3}) (.*)';
    $pages = $pl->get();
    // delete data from previous runs
    $db->Execute('DELETE FROM btLinkChecker');
    // 1. get all pages
    foreach ($pages as $page)
    {
    // 2. check permission
    $g->setPermissionsForObject($page);
    if ($g->canRead())
    {
    $collectionPath = $page->getCollectionPath();
    // 3. get all blocks
    $blocks = $page->getBlocks();
    foreach ($blocks as $block)
    {
    // 4. only process the output of content blocks
    if ($block->getBlockTypeHandle() == 'content')
    {
    $bi = $block->getInstance();
    // 5. get all links in the block output
    if(preg_match_all("/{$regexLinkPattern}/siU", $bi->content,
    $matches, PREG_SET_ORDER))
    {
    foreach($matches as $match)
    {
    $link = $match[1];
    // 6. check and fix link to make sure it is absolute
    if (substr($link,0,4) != 'http')
    {
    if (substr($link,0,1) == '/')
    {
    $link = BASE_URL . $link;
    }
    else
    {
    $link = $nh->getLinkToCollection($page, true) . $link;
    }
    }
    // 7. check link status and save it in btLinkChecker
    $statusHeader = $this->getHttpStatus($link);
    preg_match('/(.*) ([0-9]{3})(.*)/',
    $statusHeader,$statusCodeMatches);
    $statusCode = $statusCodeMatches[2];
    $statusText = $statusCodeMatches[3];
    $linksFound++;
    // we check for 404 and "NULL" which is returned
    // if there's no webserver responding. 404 is
    // only returned by a running webserver
    if ($statusCode == '404' || !$statusCode)
    {
    $brokenLinksFound++;
    }

    $values = array($page->getCollectionID(), $link,
    $statusCode, $statusText);
    $db->Execute('INSERT INTO btLinkChecker (cID, link,
    linkStatusCode, linkStatusName) VALUES (?,?,?,?)',
    $values);
    }
    }
    }
    }
    }
    }
    return t('Found %d links, out of which %d are broken.',
    $linksFound, $brokenLinksFound);
    }
    }
    ?>

  4. In order to make sure our job is installed during the package installation, open packages/c5book/controller.php and modify the install method to match the following code:

    public function install()
    {
    $pkg = parent::install();
    // install blocks
    BlockType::installBlockTypeFromPackage('jqzoom', $pkg);
    BlockType::installBlockTypeFromPackage('product_information',
    $pkg);
    BlockType::installBlockTypeFromPackage('product_list', $pkg);
    BlockType::installBlockTypeFromPackage('ftp_gallery', $pkg);
    BlockType::installBlockTypeFromPackage('pdf', $pkg);
    // install link checker job
    Loader::model("job");
    Job::installByPackage("link_checker", $pkg);
    }

  5. We've added two elements to our package which are processed during the installation. Go to Add Functionality and remove and install our package again. This will create the database table and add a new job, which you'll see when you go to System & Maintenance again.

What just happened?

After installing the package, you will see a new job named Link Checker. If you run the job like shown in the following screenshot, you should get a message telling you how many broken links there are on your site:

However, what if there's a message saying that there are broken links on the website? For now, there's no nice way to get access to that information, but have a look at phpMyAdmin by going to your XAMPP Control Panel and click on Admin next to MySQL. Select the concrete5 database and scroll down till you see btLinkChecker. Click on it and make sure you're on the Browse tab. You should see something like the following:

We're going to create a nice interface for this table, but before that a few words about the data you can find in the table.

Here you either see an entry with a 404 status code or an empty (NULL) value. As 404 is returned by a web server, it means that the domain is available, but the page isn't. In case the domain isn't available at all, you won't get anything back since there's no web server returning your requests, hence the NULL value.

If you found a link which isn't working, you have to know where this link is located in your website. This works by using the cID and appending it to index.php, which would look like this: http://localhost/index.php?cID=64. Using the internal collection ID has one big advantage: Even if you rename the page, you'll still be able to access the page and fix the link.

Anything else in the table output doesn't really hurt and is mostly informational. There's just one thing which you can check: Entries with a 301 status code indicate that the site owner has moved a page and you could check the link to see where you actually end up and replace it. By doing this, your links stay up to date and you save an additional redirection when your visitors click on the links, even if they probably won't notice the difference.

Please note: as we only check the links within content blocks, it might happen that you have broken links in another block which won't appear in the table of this add-on. Due to simplicity and performance reasons, the example doesn't check these blocks. If you wanted to extend it, look for the getBlockTypeHandle check and extend it by the blocks you want to include as well.

Injecting header items

Sometimes you want to make sure an element, such as a JavaScript, is shared in the header of your HTML document. concrete5 allows you to inject any element, such as a JavaScript or a CSS file into the head of your HTML document from your block or package controller.

Adding tooltips for every title tag

The template works well, but it only if you change the custom template for every content block where you want this tooltip to appear. Now, let's say you forget to change the template on one page. Nothing would be broken, but the look and feel of your website wouldn't be consistent, which is something we'd like to avoid.

What options are there?

  • We could create a block for this which would use the content of the whole page. This means that you wouldn't have to modify every content block but you'd still have to place that block on every page.
  • We could also place it in the theme. This is an easy option which would work just fine but has one little disadvantage. If you want to use this tooltip feature on other sites you have to modify the page theme in order to get the functionality on another page. This is not a big deal but can we avoid this?

We can!

Time for action - creating global tooltips

Carry out the following steps:

  1. We're going to use the same script again, so download the TipTip source code from the following URL:
    http://code.drewwilson.com/entry/tiptip-jquery-plugin
  2. Extract jquery.tipTip.minified.js to packages/c5book/js. You have to create the js folder first.
  3. Extract tipTip.css to packages/c5book/css. You have to have the css folder too.
  4. Now, we've got to make sure these files are loaded and properly called. To do this, open the package controller packages/c5book/controller.php and insert the highlighted lines, as follows:

    <?php
    defined('C5_EXECUTE') or die(_("Access Denied."));
    class c5bookPackage extends Package {
    protected $pkgHandle = 'c5book';
    protected $appVersionRequired = '5.4.0';
    protected $pkgVersion = '1.0';
    public function getPackageDescription() {
    return t("Theme, Templates and Blocks from concrete5 for
    Beginner's");
    }
    public function getPackageName() {
    return t("c5book");
    }

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

    // install blocks
    BlockType::installBlockTypeFromPackage('jqzoom', $pkg);
    BlockType::installBlockTypeFromPackage('product_
    information', $pkg);
    BlockType::installBlockTypeFromPackage(
    'product_list', $pkg);
    BlockType::installBlockTypeFromPackage(
    'ftp_gallery', $pkg);
    BlockType::installBlockTypeFromPackage('pdf', $pkg);
    }

    function on_start() {
    $html = Loader::helper('html');
    $v = View::getInstance();
    $v->addHeaderItem(
    $html->javascript('jquery.tipTip.minified.js','c5book'));
    $v->addHeaderItem($html->css('tipTip.css','c5book'));
    $v->addHeaderItem('<script
    type="text/javascript">$(function(){ $("[title]").tipTip();
    });</script>');
    }
    }
    ?>

What just happened?

The preceding code basically replaced the template we created before. However, the new code replaces every tooltip, not only those in the content block. If you don't want that behavior, you might want to keep using the previous template instead of this package controller.

How does the preceding code work? The method on_start is automatically called for every installed package during the page rendering process. This allows you to inject code into any page on a website without actually modifying any pages. Do it once, use it everywhere!

There's a rather simple, but neat, add-on in the concrete5 marketplace, which uses the same technique to include a little JavaScript check to detect old browsers and if found also shows a little toolbar to inform website visitors to update to a newer and more secure browser version. You can find it at the following URL:
http://www.concrete5.org/marketplace/addons/scala-it-browser-update-notification/

JavaScript browser fixes

A lot of web designers still make sure their website works for Internet Explorer 6.0 as well, even if version 9.0 is already released. It's painful, but for something like this, a package can make things a bit easier.

There are different projects you can use to help old browsers behave at least partially like they should. One example can be found at http://www.dustindiaz.com/min-height-fast-hack/ but there are a lot more out there. There's a complete package of fixes available at http://code.google.com/p/ie7-js/. What if you wanted to use this for all your websites? You could put this in your theme, but why not create a package for this?

Let's have a look at how easily we could integrate such a browser fix script in our c5book package.

Time for action – integrating CSS fix in the package

Carry out the following step:

  1. Open the controller from packages/c5book/controller.php and modify the on_start method to match the following code:

    function on_start() {
    $html = Loader::helper('html');
    // add advanced tooltips to every page
    $v = View::getInstance();
    $v->addHeaderItem($html->javascript('jquery.tipTip.minified.js',
    $this->pkgHandle));
    $v->addHeaderItem($html->css('tipTip.css', $this->pkgHandle));

    $v->addHeaderItem('<script type="text/javascript">$(function(){
    $("[title]").tipTip(); });</script>');

    // inform about new users
    Events::extend('on_user_add',
    'UserInformation',
    'userAdd',
    'packages/' . $this->pkgHandle .
    '/models/user_information.php');


    // include MSIE fix
    $v->addHeaderItem('<!--[if lt IE 8]><script src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original=
    "http://ie7-js.googlecode.com/svn/version/2.1(beta4)/IE8.js">
    </script><![endif]-->');
    }

What just happened?

We've included a simple JavaScript file to fix some issues with older browser versions. The same works for different elements, such as a CSS reset script to reduce inconsistency among different browsers. Check the following URLs if you want to start working with HTML5:

http://meyerweb.com/eric/tools/css/reset/

http://www.modernizr.com/

Include one of these scripts using the technique described earlier and parts of HTML5 will work in browsers that aren't really HTML5 ready.

Have a go hero – create a new package

We've included a lot of functionality in a single package. This simplifies a few things because we don't have to create new packages all the time, but it comes at a price—unnecessary overhead.

Try to move some of the created functionality into new packages. Having a package for a single JavaScript fix seems a bit extreme as it contains some overhead, but is easier to work with, as installing an additional package is only a matter of seconds.

Summary

In this article, you should have learned about the things we can do with a package. We started by moving some of our previous add-ons into a package, making it easier to handle and install. Creating a package is often about the installation process, which is one reason why you have to wrap add-ons in a package if you want to publish them on the concrete5 marketplace.

We also had a quick look at events, a nice but advanced feature that you can use to execute custom code upon certain events happening in the concrete5 core. An example: Being able to hook into actions happening on your user database allows you to synchronize accounts with another system. Think about third-party forum software you want to use—if you already have a concrete5 website, you could create an interface to keep both user databases up to date without needing your website's users to register twice.

Next, we created a maintenance job, which checks for broken links on your website. This was just one example. Maintenance jobs or tasks can be used to optimize databases, index pages, start an interface, and a lot more.

At the end of the article, we looked at a way to include JavaScripts in the header of every page—a simple but effective way to include JavaScripts to improve the look and usability as well as compatibility with older browsers.


Further resources on this subject:


About the Author :


Remo Laubacher

Remo Laubacher grew up in Central Switzerland in a small village surrounded by mountains and natural beauty. He started working with computers a long time ago and then, after various computer-related projects, focused on ERP and Oracle development. After completing his BSc in Business Administration, Remo became a partner at Ortic, his ERP and Oracle business, as well as a partner at Mesch web consulting and design GmbH. At Mesch—where he's responsible for all development-related topics—he discovered concrete5 as the perfect tool for their web-related projects and has since become a key member of the concrete5 community. You can find his latest publications on http://www.codeblog.ch/.

He has also authored concrete5 Beginner's Guide and Creating concrete5 Themes.

Books From Packt


Learning Ext JS 3.2
Learning Ext JS 3.2

Drupal 7
Drupal 7

jQuery 1.4 Reference Guide
jQuery 1.4 Reference Guide

Inkscape 0.48 Essentials for Web Designers
Inkscape 0.48 Essentials for Web Designers

MODx Web Development - Second Edition
MODx Web Development - Second Edition

Magento 1.4 Development Cookbook
Magento 1.4 Development Cookbook

OpenCart 1.4 Template Design Cookbook
OpenCart 1.4 Template Design Cookbook

ADempiere 3.6 Cookbook
ADempiere 3.6 Cookbook


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software