13. Using the Joomla! Platform as an Application Framework

With the release of Joomla! 1.7.0 in July 2011, the Joomla platform officially became its own project, separate from the Joomla content management system (CMS). This allows the platform to be developed independently of the Joomla CMS and makes it easier for the platform to be deployed on its own to develop and power a wide variety of web applications.

The Joomla platform is a large topic and deserves its own book. This chapter is intended to give an overview of the platform and some simple examples of its use.

What Is the Joomla Platform?

Joomla version 1.5 was designed so that classes that performed fundamental functions were placed in a folder called libraries/joomla. Although used in various places in the CMS, these classes perform functions that are common to most web applications and not specific to a CMS application. Examples include authenticating users, translating text into other languages, filtering input and output, and working with the server’s file system.

This initial separation of these programs went a long way toward allowing the Joomla platform to be used separately from the CMS. However, there were still a number of places where programs in the platform folder depended on programs in other places in the CMS to function correctly.

With the release of the platform as a separate project, these dependencies have been eliminated and now it is easy to install and use the platform without installing the Joomla CMS. The platform has its own code repository at the website https://github.com/joomla/joomla-platform. It can be downloaded and used on its own.

Why Have a Separate Project?

There are two overriding reasons for officially separating out the platform project from the CMS project.

The first reason is to make the project more attractive to developers. The Joomla CMS is a mature product with millions of users. Although there is exciting work yet to do to improve the CMS, it has been around since 2005 and it was created as a fork of the Mambo project, which was started in 2000. So the Joomla CMS is at a stage where most changes are incremental. Moreover, the entire CMS industry is at a relatively mature stage at this time, at least compared to many other types of web applications.

By contrast, the Joomla platform presents a unique opportunity for developers. It is a new project where developers can participate in working on exciting new functionality without the constraints of existing code. There are many opportunities to create new code and functionality from scratch.

On the other hand, because the platform is already used in millions of Joomla websites, it is well tested and has a lot of credibility. New functionality in the platform will, in many cases, be made available to the entire Joomla CMS user base over time, as the CMS takes advantage of new platform features. This means that developers who add important features to the platform will potentially see those features get used by millions of people.

The second important reason for creating the platform project is to make it very easy for developers to use the platform for non-CMS applications. Content management is an important type of web application, but it is only one of many possible application types. Today, the web is used for many things beyond creating websites for sharing information, and these include many of the most innovative web applications.

Most web applications need basic functionality, such as user authentication, file system interaction, filtering, and others. When a team is developing a new web application, instead of creating this functionality for themselves, they can use the Joomla platform to get a head start on the project. By deploying a set of programs that already includes methods for many common requirements, they can focus on the parts of the project that are unique and not spend time “reinventing the wheel.”

What Can the Platform Be Used For?

Today, many types of applications are being performed on the web. Just some of the examples are as follows:

• E-commerce programs designed to act as the “back-room” processing for any website that includes a shopping feature

• Web-based enterprise resource planning (ERP) systems, designed to provide financial accounting and related functions to various types of businesses and organizations

• Applications that allow websites to be modified by running a program at a server’s command-line console.

• User forums and bulletin-board systems.

The Joomla platform can be used as the foundation for developing almost any type of web application. One way to think about it is this: if your application will need to do any of the functionality built into the platform, then the platform can be a great starting point for your application.

Platform Example Programs

The best way to understand the platform is to see some examples. Note that there are a number of example applications available from the Joomla platform code repository here: https://github.com/joomla/joomla-platform-examples. We will start with two very simple examples, one that uses the browser and one that uses the system command-line interface (CLI). CLI programs are run from the command console, not from the browser.

Set Up Platform Project

The first thing we need to do is download the platform files and set up our folder structure. One simple way to do this is as follows:

1. Create a folder to hold the project files. We’ll call our folder platform-test. If you want to test the platform with browser applications as well as CLI applications, put this folder under your web server’s DocumentRoot folder (for example, under the htdocs folder).

2. Download the platform. One easy way to do this is to navigate to https://github.com/joomla/joomla-platform and press the Tags button. This will give you a list of the official release versions (for example, 11.4.zip). Alternatively, you can download the latest repository version by pressing Downloads → Download as zip.

3. Unpack the archive in the platform-test folder. The top-level folder in the archive is the same as the archive name, in the format joomla-joomla-platform-<version>-<changeset number> (for example, joomla-joomla-platform-11.4-0-g4329ba0.zip). After unpacking, rename this folder to joomla-platform. At this point, platform-test should have a subfolder called joomla-platform, which should have subfolders called build, docs, libraries, media, and tests. Note that you only need the libraries and media folders to run the platform. The other folders are for development.

4. Download the examples. To do this, navigate to https://github.com/joomla/joomla-platform-examples, press Download → Download as zip. This will download a file called something like joomla-joomla-platform-examples-544306f.zip.

5. Unpack this archive in a temporary folder and copy the cli and web folders to the platform-test folder. Also copy the bootstrap.dist.php file to the platform-test folder.

6. At this point, platform-test should have three subfolders: cli, joomla-platform, and web. You should also have a file called bootstrap.dist.php.

7. Copy the bootstrap.dist.php file to bootstrap.php. Edit this file to contain the following line of code:

require dirname(__FILE__).'/joomla-platform/libraries/import.php';

This code tells the platform where to find the import script. It uses the PHP dirname() command to get the directory name of the current file (bootstrap.php) and then uses that to create the full path name of the import script. In our setup, the bootstrap.php file is in the same folder as the joomla-platform folder.

At this point, you should be able to run all the example programs. To test this, try out the Hello World example, as follows:

• Open a command-line session in your computer and change to the directory platform-test/cli/101-hello-world.

• Make sure your PHP program is executable from the command line. If needed, add it to the execution path.

• At the command prompt enter the command php run.php

• You should see “Hello World!” output to the console.

If this doesn’t work, check the bootstrap.php file and the require statement that loads the bootstrap file.

If the platform-test folder is under your web server’s DocumentRoot folder, you should also be able to run the web applications. For example, to run the detect-client example web application, enter the following URL in your browser:

<path to the platform-test folder>/web/detect-client/index.php

For example, if your platform-test is a subfolder of htdocs on your local machine, the URL would be

http://localhost/platform-test/web/detect-client/index.php

You should see something like the following output in your browser:

Welcome to the Joomla! Platform's JWeb class.

    * User-agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.23) Gecko/20110920 Firefox/3.6.23 ( .NET CLR 3.5.30729; .NET4.0C)
    * Is a mobile device? No
    * Platform: 1
    * Engine: 13
    * Browser: 18 (3.6.23)

Again, if you get an error, check that the bootstrap.php file is correct.

Hello World CLI Application

Next, let’s look at the code for the hello world application (in the file platform-test/joomla-platform/cli/101-hello-world/run.php). The first part of the code is as follows:

// We are a valid Joomla! entry point.
// This is required to load the Joomla! Platform import.php file.
define('_JEXEC', 1);

// Setup the base path related constant.
// This is one of the few, mandatory constants needed for the Joomla! Platform.
define('JPATH_BASE', dirname(__FILE__));

// Bootstrap the application.
require dirname(dirname(dirname(__FILE__))).'/bootstrap.php';

// Import the JCli class from the platform.
jimport('joomla.application.cli'),

Here we define the _JEXEC constant. This tells subsequent program files that we are inside a Joomla application. This constant is required for the platform files to execute.

The next thing we do is define the JPATH_BASE constant. This constant needs to be defined, although its use is up to the application. It will normally point to the root folder of the application. In this case, we aren’t using it, so we define it to just point to the current folder.

The next command includes the bootstrap.php file we edited earlier. In this case, we have that file in the root folder of our structure (platform_test), which is three folders up from the current path (platform_test/cli/101-hello-world). So we use three dirname() commands to get to this folder. Note that we can put the bootstrap.php file anywhere we like. This is just the structure that the example programs use.

The bootstrap.php file loads the libraries/import.php file, which in turn creates a number of platform constants and loads some basic platform classes.

The last command imports the cli.php file from the libraries/joomla/application folder. This is the class that all platform CLI applications will normally extend.

The rest of the code for our Hello World example is as follows:

class HelloWorld extends JCli
{
    /**
     * Execute the application.
     *
     * The 'execute' method is the entry point for a command line application.
     *
     * @return  void
     *
     * @since   11.3
     */
    public function execute()
    {
       // Send a string to standard output.
       $this->out('Hello world!'),
    }
}
// Instantiate the application object, passing the class name to  JCli::getInstance
// and use chaining to execute the application.
JCli::getInstance('HelloWorld')->execute();

Here we create our class, which extends the JCli class. We have one method called execute(). In this case, it uses the out() method of the JCli class to output the message to the console. Then we close the method declaration and the class declaration.

The last line of the file uses method chaining to do two things. First, it gets a new JCli object using the getInstance() method. Then it calls the execute() method of that object. The end result is that “Hello world!” is shown on the console. Note that this last line is outside the class declaration and is executed immediately.

Web Hello WWW Application

This application, web/101-hello-www/index.php, outputs a simple text message in the browser. The first part of the code is as follows:

// We are a valid Joomla! entry point.
define('_JEXEC', 1);

// Setup the base path related constant.
define('JPATH_BASE', dirname(__FILE__));

// Increase error reporting to that any errors are displayed.
// Note, you would not use these settings in production.
error_reporting(E_ALL);
ini_set('display_errors', true);

// Bootstrap the application.
require dirname(dirname(dirname(__FILE__))).'/bootstrap.php';

// Import the JWeb class from the platform.
jimport('joomla.application.web'),

As before, we define our constants. The next two lines set up maximum error reporting. This is helpful during development to allow you to see any code that violates PHP strict standards. Also, note that the platform is designed to run for PHP versions 5.3 and higher.

The next two lines require the bootstrap.php file from our project root folder and then import the file libraries/application/web.php file. This defines the JWeb class.

The rest of the index.php file is as follows:

class HelloWww extends JWeb
{
    /**
     * Overrides the parent doExecute method to run the web application.
     *
     * This method should include your custom code that runs the application.
     *
     * @return  void
     *
     * @since   11.3
     */
       protected function doExecute()
   {
      // This application will just output a simple HTML document.
      // Use the setBody method to set the output.
      // JWeb will take care of all the headers and such for you.

      $this->setBody('<html>
         <head>
            <title>Hello WWW</title>
         </head>
         <body style="font-family:verdana;">
            <p>Hello WWW!</p>
         </body>
         </html>'
      );
   }
}
// Instantiate the application object, passing the class name to  JWeb::getInstance
// and use chaining to execute the application.
JWeb::getInstance('Hellowww')->execute();

This creates the HelloWww class, extending the JWeb class. Most web applications using the platform will extend the JWeb class.

Then we create a protected method called doExecute(). This overrides the doExecute() method of the JWeb class. Recall in the CLI Hello World example we created an execute() method. We can use either method to execute code inside the class. However, there is an important difference. If you look at the code for the execute() method of the JWeb class, you will see that it triggers events before and after the doExecute() method is called. Then it renders the document and triggers some more events. Finally, it sends the response to the client, again with before and after events triggered. Because these are steps we normally want done when we are working with HTML documents, the preferred approach is to override the doExecute() method and let the JWeb execute() method do the rest of this work.

The code in the doExecute() method uses the setBody() method of JWeb to add our HTML to show the hello message. JWeb includes various methods for working with the document header and body.

The last line of our file is similar to the prior example. We get an instance of the object and then run its execute() method. The result is that the message displays in an HTML document in our browser.

Subscription Monitoring Example

Now that we understand the basics of using the platform CLI, let’s look at a realistic example using the Joompro Subscriptions component we created earlier in the book. Recall that this component creates subscriptions that expire after a certain number of days. In a real-life situation, we would want to have a way to monitor and manage these subscriptions.

In this example, we will create a CLI application that does two things. First, it checks the database for subscriptions that will expire in the next five days and sends the subscriber a reminder e-mail. Second, if a subscription has expired, it deletes the subscription row from the mapping table and removes the user from the subscription’s group.

We will assume that the application will be run unattended on a schedule (for example, using the Linux cron command). For this reason, we will record the results of the program in a log file for the system administrator to review (instead of printing information out in the console).

Project Structure

For this example, we will use a somewhat different project structure from those the earlier examples used. We will put our program files in a folder called src under the joomla-platform folder. The src folder will hold our program files and log files.

To set this up, create a new folder called platform-test/joomla-platform/src. Then under the src folder create a folder called logs to hold the log files.

In the src folder, we will have three files and one folder, as follows:

monitor.php: The program to run from the command line

subscriptionmonitor.php: The program that does the subscription checking

configuration.php: The file that holds the site-specific configuration data

logs: The folder where our log files will be created

Configuration File

The first file we need to create is our configuration file. This has the exact same structure as the Joomla CMS configuration file (and in fact you can copy the configuration.php file from the root folder of a Joomla installation). In this example, we only require the fields needed to connect to the CMS database and send an e-mail, but we have included the other fields as well.

The code for configuration.php is as follows:

<?php

// Prevent direct access to this file outside of a calling application.
defined('_JEXEC') or die;
class JConfig
{
    public $dbtype     = 'mysqli';
    public $host       = '127.0.0.1';
    public $user       = 'test';
    public $password   = 'password';
    public $db         = 'book_170';
    public $dbprefix   = 'jos_';
    public $ftp_host   = '127.0.0.1';
    public $ftp_port   = '21';
    public $ftp_user   = '';
    public $ftp_pass   = '';
    public $ftp_root   = '';
    public $ftp_enable = 0;
    public $tmp_path   = '/tmp';
    public $log_path   = '/var/logs';
    public $mailer     = 'smtp';
    public $mailfrom   = '[email protected]';
    public $fromname   = 'Joomla! Programming';
    public $sendmail   = '/usr/sbin/sendmail';
    public $smtpauth   = '1';
    public $smtpuser   = '<gmail user name>';
    public $smtppass   = '<gmail password>';
    public $smtphost   = 'smtp.gmail.com';
    public $smtpsecure = 'ssl';
    public $smtpport   = '465';
    public $debug      = 0;
    public $caching    = '0';
    public $cachetime  = '900';
    public $language   = 'en-GB';
    public $secret     = null;
    public $editor     = 'none';
    public $offset     = 0;
    public $lifetime   = 15;
}

You will need to change the settings to match your environment. These will include $user (database user name), $password (database password), $db (database name), and $dbprefix (database table prefix) and possibly the e-mail settings (depending on your system). You can run the example code here without e-mail if needed. The e-mail settings shown here are for a Google Gmail account.

Monitor File

This file is the entry point for our application. It is the command that will be run from the command line. The first part of the code is as follows:

<?php
// Declare that we are a valid Joomla! entry point if not declared.
if (!defined('_JEXEC'))
{
        define('_JEXEC', 1);
}
// Setup the application base path constant if not already set.
if (!defined('JPATH_BASE'))
{
        define('JPATH_BASE', dirname(__FILE__));
}
// Import the Joomla! Platform.
require dirname(dirname(__FILE__)) . '/libraries/import.php';
// Import library dependencies.
jimport('joomla.log.log'),
require JPATH_BASE . '/subscriptionmonitor.php';

Here we make sure that the _JEXEC and JPATH_BASE constants are defined. Then we require the libraries/import.php file. Note that we don’t use a bootstrap.php file in this example. Instead, we just require the import file. Note also that we adjust the command to find the import.php file relative to the location of the current file (monitor.php).

Once we have the import.php file loaded, we can import the library classes we need for this program. In this case, it’s just the JLog class. Then we require the subscriptionmonitor.php program.

The next part of the file is as follows:

// Load the configuration.php file
$config = JFactory::getConfig(JPATH_BASE.'/configuration.php'),

// Create the log file
// Get the date so that we can roll the logs over a time interval.
$date = JFactory::getDate()->format('Y-m-d'),

// Add the logger.
JLog::addLogger(
    // Pass an array of configuration options.
    // Note that the default logger is 'formatted_text' - logging to a file.
    array(
    // Set the name of the log file.
        'text_file' => 'monitor-.'.$date.'.php',
        // Set the path for log files.
        'text_file_path' => __DIR__.'/logs/'
        )
    );

Here we set the configuration using the configuration.php file we created. Note that we don’t use the $config object. However, executing this method creates the JConfig object (JFactory::$config), which is used in the JFactory::getDate() and JFactory::getMailer() methods we use later on. By setting this at the start of our program, we know it will be set with the desired configuration.php file. If, for example, we call JFactory::getDate() before creating JConfig, it will load configuration.php from the default location (JPATH_PLATFORM), which is not what we want.

Next we create the log file using the JLog class. We make the current date part of the file name. That way, we get a new file every day. We place this file in the logs folder we created earlier.

The next part of the file is as follows:

// Wrap the execution in a try statement to catch any exceptions thrown anywhere in the script.
try
{
        // Instantiate and execute the application.
        JCli::getInstance('SubscriptionMonitor')->execute();
}
catch (Exception $e)
{
        // An exception has been caught, add the message to the log.
        JLog::add($e->getMessage(), JLog::ERROR);
        exit($e->getCode());
}

Here we create a try/catch block and call the execute() method of the SubscriptionMonitor class inside the try block. As we will see, several methods in the SubscriptionMonitor class throw exceptions. By calling the execute() method inside the try block, we ensure that any exceptions thrown anywhere in the method are caught and handled.

In the catch block, we add the exception message to the log. Keep in mind that this program will normally be run automatically by the server, so logging errors in a log file is preferable to showing them in the console.

We could have put all the code for checking the subscriptions in this one file instead of creating a second file. The approach used here has an important advantage: It makes it very easy to add more monitoring functions. Say, for example, we also wanted to monitor the stop publishing date for articles. All we would do is create the new article monitoring class and add the line to execute it in the try block of monitor.php. This one monitor.php file could run all our monitoring functions for the entire site and could be run by the server as one simple cron job.

Subscription Monitoring File

This file, subscriptionmonitor.php, actually does the work of checking and updating the database and sending the e-mails. The first part of this file is as follows:

jimport('joomla.application.cli'),
jimport('joomla.database.database'),

/**
 * A Joomla! Platform application to monitor subscription status and take action when necessary.
 *
 * @package  Subscriptions
 * @since    1.0
 */
class SubscriptionMonitor extends JCli
{
        /**
         * @var    JDatabase  The application database connection object.
         * @since  1.0
         */
        protected $db;

This imports the JCli and JDatabase classes and then declares the SubscriptionMonitor class as a subclass of JCli. Finally, we add a protected member to hold the JDatabase object for the class.

The next part of the code is the class constructor, as follows:

public function __construct(JInputCli $input = null, JRegistry $config = null, JDispatcher $dispatcher = null, JDatabase $db = null)
{
    // Call the parent constructor for basic setup of the object.
    parent::__construct($input, $config, $dispatcher);

    // If a database connection object is given use it.
    if ($db instanceof JDatabase)
    {
        $this->db = $db;
    }
    // Create the database connection based on the application logic.
    else
    {
        $this->loadDatabase();
    }

}

The constructor has four arguments in its method signature: $input, $config, $dispatcher, and $db. We don’t use these arguments in this example, but they are there in case we later want to reuse this class in a different context. For example, the first argument allows you to pass a JInputCli object to the constructor. This object simulates command-line arguments. The other arguments allow you to call the constructor with your own configuration file, dispatcher class, and database class.

Note that there is something different about the method signature from what we have seen before. Here we indicate the class name before each variable (for example, JInputCli $input). This is known in PHP as type hinting. It tells the PHP interpreter (and someone reading the code) that this argument must be an object of that type. Otherwise, you will get a fatal error when executing the method. Note that type hinting is only available in PHP 5.3 and later.

The constructor code calls the parent constructor and then checks whether the $db field has been set. If not, it calls the loadDatabase() method to set this field.

The next method in this class is doExecute(). Recall that this method is run by the execute() method of the JCli class. The code for this is as follows:

protected function doExecute()
{
    // Log start of program
    JLog::add('Subscription monitoring started'),

    // Get a list of the ended subscriptions and remove them.
    $ended = $this->getEndedSubscriptions();
    foreach ($ended as $sub)
    {
       $this->doEndSubscription($sub);
    }

    // Get a list of the ending subscriptions and send a notification.
    $ending = $this->getEndingSubscriptions();
    foreach ($ending as $sub)
    {
       $this->doSendSubscriptionEndingNotification($sub);
    }

    // Log end of program
    JLog::add('Subscription monitoring ended'),
}

This is the method that actually controls the program flow and does the work. We start by adding a log entry indicating the program has started. Then we call the getEndedSubscriptions() method to get a list of subscriptions that have expired. Then we loop through each of these and call the doEndSubscription() to actually process each expired subscription.

There is an interesting point about using foreach loops in this context. It is quite possible in normal use that there could be no expiring subscriptions. In this case, $ended will be an empty array. In that case, the foreach loop is simply skipped, and there are no errors or notices.

After we process the expired subscriptions, we call the getEndingSubscriptions() to get a list of subscriptions that are close to expiration. Again, we process the list in a foreach loop, calling the doSendSubscriptionEndingNotification() method for each one in the list. The last thing we do is add a log entry indicating that we have finished.

The next method in the class is getEndedSubscriptions(). This method queries the database to get a list of expired subscriptions. The code is as follows:

protected function getEndedSubscriptions()
{
    // Get a date object for now.
    $now = JFactory::getDate($this->get('execution.datetime'));

    // Get the query builder class from the database.
    $query = $this->db->getQuery(true);

    // Set up a query to select subscriptions that have end dates before now.
    $query->select('*')
        ->from($this->db->qn('#__joompro_sub_mapping'))
        ->where($this->db->qn('end_date') . ' < ' . $this->db->q($now->toSQL()))
        ->where($this->db->qn('end_date') . ' > ' . $this->db->q('0000-00-00 00:00:00'));

    // Set the database query object to the database connection object.
    $this->db->setQuery($query);

    // Get all the returned rows from the query as an array of objects.
    $rows = $this->db->loadObjectList();

    return $rows;
}

Here we get the current date and time and then build a query that selects all rows in the mapping table that have an end_date column that is less than the current date and time. Notice that we use the code

($this->get('execution.datetime')

to get the current time. This calls the get() method of the JCli class. That method has the following code:

public function get($key, $default = null)
{
    return $this->config->get($key, $default);
}

Here we are calling the get() method of the config field of the class. Where is the execution.datetime being set? The answer is in the contructor of the JCli class. There we see the following line of code:

$this->set('execution.datetime', gmdate('Y-m-d H:i:s'));

The set() method adds the results of the gmdate() method to the config field of the class. The gmdate() method returns the current time for the Greenwich Mean Time (GMT) time zone. Why do we save this value in the config field instead of just calling gmdate() each time we need the current time? The answer is that the gmdate() method will return a different value each time it is called. By calling it once in the constructor, we have one time value to use in both of our queries, so they will always give a consistent result.

Note that we also check that the end date is greater than a zero date. Recall that in the Joomla CMS we use the convention of a zero date indicating an indefinite expiration date. Adding this would allow us to use the same convention for subscriptions.

Here we use method chaining to build the query. The entire query is built with one command, using the results of one query method as the object to call the next method. Alternatively, we could have used separate lines of code to call the select(), from(), and the two where() methods.

After we build the query, we pass it to the database object and return the results using loadObjectList(). Recall that this method returns an array of objects.

There is one last interesting thing to notice about this code. In several places, we use the JDatabase qn() method. However, if we look at the JDatabase class (libraries/joomla/database/database.php), we don’t see any method called qn(). The JDatabase class doesn’t extend another class, so it isn’t coming from a parent class. Where is qn() defined?

The answer is in the __call() method of JDatabase. This method is one of several “magic” methods in PHP. It is called any time we call a method that doesn’t exist in the current class. One common use for the __call() method is to create aliases for commonly used methods. If we look at this method, we see the following code:

switch ($method)
{
    case 'q':
       return $this->quote ($args[0], isset($args[1]) ? $args[1] : true);
       break;
    case 'nq':
    case 'qn':
              return $this->quoteName ($args[0]);
       break;
}

Here we test for methods called q, nq, and qn and in turn calling either the quote() method or the quoteName() method. So the qn() method is just an alias for the quoteName() method. Note that we have to be careful to pass the arguments (an array called $args) through to the quote() and quoteName() methods.

The next method in the SubscriptionMonitor class is getEndingSubscriptions(). This is similar to the previous method, except that it selects subscriptions that will expire within the next five days. Here is the code:

protected function getEndingSubscriptions()
{
    // Get a date object for now and five days into the future.
    $now    = JFactory::getDate($this->get('execution.datetime'));
    $future = JFactory::getDate($this->get('execution.datetime'));
    $future->add(new DateInterval('P5D'));

    // Get the query builder class from the database.
    $query = $this->db->getQuery(true);

    // Set up a query to select subscriptions that have end dates between now and 5 days from now.
    $query->select('*')
        ->from($this->db->qn('#__joompro_sub_mapping'))
        ->where($this->db->qn('end_date') . ' < ' . $this->db->q($future->toSQL()))
        ->where($this->db->qn('end_date') . ' > ' . $this->db->q($now->toMySQL()));

    // Set the database query object to the database connection object.
    $this->db->setQuery($query);

    // Get all the returned rows from the query as an array of objects.
    $rows = $this->db->loadObjectList();

    return $rows;
}

The first thing we do is calculate a date and time that is five days in the future. We create a date called $future equal to the current date and then we add five days to it using its add() method. To get the five-day interval, we use the PHP DateInterval class. This takes an argument in the form

P + <number> + <time interval>

In our example, we use P5D to indicate five days. The result is that $future now holds a date-time object five days later than $now.

Now we use these date-time values to build a query that selects any subscriptions with ending dates later than now but before five days from now. As before, we return these as an array of objects.

Notice that there is a potential design problem with this query. If we run our monitor program on a daily basis, a subscriber would get an e-mail every day for up to five days, which could be annoying. If we didn’t want that result, we could run the monitor program less often (say every five days) or we could change the query to select subscriptions in a narrower time window (for example, subscriptions that will expire in between four and five days). Alternatively, we could record in the database that a reminder e-mail has been sent and use that to filter the query results.

The next method is doEndSubscriptions(). This method does several things:

• Gets the subscription title and user name for the e-mail

• Deletes the row for the user-to-usergroup mapping table (thereby removing the user from the group)

• Deletes the row from the mapping table (thereby removing the subscription)

• Sends an e-mail to the user to notify them the subscription has been removed

The first part of the method is as follows:

    protected function doEndSubscription($subscriptionMapping)
   {
      $subscription = $this->getSubscription( $subscriptionMapping->subscription_id);
       $user = $this->getUser($subscriptionMapping->user_id);

Here we call the getSubscription() and getUser() methods. These check that the subscription and user ids are valid and also give us the subscription title and user name for the e-mail.

The next section of code is as follows:

// Set up a query to remove the user group mapping.
$query = $this->db->getQuery(true);
$query->delete()
   ->from($this->db->qn('#__user_usergroup_map'))
   ->where($this->db->qn('group_id') . ' = ' . (int) $subscription->group_id)
   ->where($this->db->qn('user_id') . ' = ' .  (int) $subscriptionMapping->user_id);

// Set the database query object to the database connection object.
$this->db->setQuery($query);

// Execute the query.
$this->db->query();

At this point, we know that the subscription and user from the mapping table are valid. Now we begin updating the database. We build a new delete query to delete the row for the user in the user-to-user group mapping table. Then we execute the query. By deleting this row, we remove the user from the group. Recall that the user was added to the group when the user subscribed to the subscription.

The next code block is as follows:

// Get the query builder class from the database.
$query = $this->db->getQuery(true);

// Set up a query to remove the subscription mapping.
$query->delete()
    ->from($this->db->qn('#__joompro_sub_mapping'))
    ->where($this->db->qn('subscription_id') . ' = ' . (int) $subscriptionMapping->subscription_id)
    ->where($this->db->qn('user_id') . ' = ' . (int) $subscriptionMapping->user_id);

// Set the database query object to the database connection object.
$this->db->setQuery($query);

// Execute the query.
$this->db->query();

Here we create a new delete query—this time, to remove the row from the subscription mapping table. This unsubscribes the user from this subscription.

The last portion of the method is as follows:

     // Build expiration email.
     $subject = 'Your subscription has expired.';
     $body[] = 'Hi, '.$user->name.',';
     $body[] = '';
     $body[] = 'Your subscription to '.$subscription->title.' has expired.';
     $body[] = '';
     $body[] = 'Regards,';
     $body[] = $this->get('fromname'),

     // Send the notification email.
     $this->sendNotificationEmail($user->email, $subject, implode(" ", $body));
     JLog::add('Subscription removed for user='.$user->name.', title='.$subscription->title);
}

Here we build the contents of the notification e-mail to the user. Then we call the sendNotificationEmail() method to send the e-mail. Finally, we add a log entry to indicate that this user’s subscription was removed.

Next comes the doSendSubscriptionEndingNotification() method. Its code is as follows:

protected function doSendSubscriptionEndingNotification($subscriptionMapping)
    {
       $subscription = $this->getSubscription( $subscriptionMapping->subscription_id);
       $user = $this->getUser($subscriptionMapping->user_id);

       $subject = 'Your Subscription is ending soon.';
       $body[] = 'Hi, '.$user->name.',';
       $body[] = '';
       $body[] = 'Your subscription to '.$subscription->title. ' will end on '.$subscriptionMapping->end_date.'.';
       $body[] = '';
       $body[] = 'Regards,';
       $body[] = $this->get('fromname'),

       // Send the notification email.
       $this->sendNotificationEmail($user->email, $subject, implode(" ", $body));

       // Log the notification
       JLog::add('Subscription ending notification sent for user='. $user->name.', title='.$subscription->title);
    }

Again, we start by getting the user and subscription objects. Then we build the e-mail to send and send it using the sendNotificationEmail() method. Finally, we log that this has been done. Note that in this case we aren’t changing the database. We are just sending the user a reminder.

The next method is getSubscription(), as follows:

protected function getSubscription($subID)
{
    // Set up a query to get the group_id from the subscription record.
    $query = $this->db->getQuery(true);
    $query->select('*')
        ->from($this->db->qn('#__joompro_subscriptions'))
        ->where($this->db->qn('id') . ' = ' . (int) $subID);

    // Set the database query object to the database connection object.
    $this->db->setQuery($query);

    // Validate that the subscription exists.
    $subscription = $this->db->loadObject();
    if (empty($subscription))
    {
        throw new Exception('Invalid Subscription.'),
    }
    return $subscription;
}

Here we build a query to read the row from the subscriptions table using the subscription id. Recall that the id must be unique, so this query will return at most one row. If the subscription doesn’t exist for this item, we throw an exception to log this event. At the end, we return the $subscription object with the data for this row of the database table.

The next method is getUser(), as follows:

protected function getUser($userID)
{
    // Set up a query to get the user object.
    $query = $this->db->getQuery(true);
    $query->select('*')
        ->from($this->db->qn('#__users'))
        ->where($this->db->qn('id') . ' = ' . (int) $userID);

    // Set the database query object to the database connection object.
    $this->db->setQuery($query);

    // Validate that the user exists.
    $user = $this->db->loadObject();
    if (empty($user))
    {
        throw new Exception('Invalid User.'),
    }
    return $user;
}

This method is similar to the previous one. We try to read the row from the users table for this user id. If successful, we return the row as an object. If not, we throw an exception indicating that the user id was not valid.

The next method is sendNotificationEmail(), as follows:

protected function sendNotificationEmail($email, $subject, $body)
{
    JFactory::getMailer()->sendMail(
       $this->get('mailfrom'),
       $this->get('fromname'),
       $email,
       $subject,
       $body
    );
}

This method sends the e-mail using the sendMail() method of the JMail class (libraries/joomla/mail/mail.php). Note that this requires that your website is configured to send e-mail. If you are working on a local machine that isn’t set up to send e-mails, you can disable this method simply by commenting out all the lines, leaving an empty method. Make sure that the method is still defined. That way, you can test and run the rest of the program even if your e-mail is not set up.

The last method is loadDatabase(). Recall that this is called in the constructor to create a database object with the information from our configuration file. The code is as follows:

     protected function loadDatabase()
     {
       // Note, this will throw an exception if there is an error creating the database connection.
       $this->db = JDatabase::getInstance(
           array(
               'driver' => $this->get('dbtype'),
               'host' => $this->get('host'),
               'user' => $this->get('user'),
               'password' => $this->get('password'),
               'database' => $this->get('db'),
               'prefix' => $this->get('dbprefix'),
           )
       );
     }
} // end of class

Here we simply create a new JDatabase object using the values from our configuration file. Recall that the get() method of JCli reads values from the configuration object.

Running Our Monitor Program

To test the program, follow these steps:

1. Enter one or more subscriptions into the subscriptions table.

2. Create a menu item to show the subscriptions.

3. Log in to the front end of the site and subscribe to some subscriptions.

4. Using phpMyAdmin (or some other database tool), change the end date on one subscription in the jos_joompro_sub_mapping table to be in the past and change another to be less than five days in the future.

5. Make sure the configuration.php file has the correct information to connect to the site’s database.

6. If needed, comment out the code in the sendNotificationEmail() method (if you don’t have your test machine set up to send e-mails). Be sure to keep the empty method.

7. Open a console session and navigate to the platform-test/src folder. Enter the command: php monitor.php

The program should run and return to the system prompt. At this point, a log file should be created in your logs folder and should have entries similar to that shown here:

#<?php die('Forbidden.'), ?>
#Date: 2012-10-15 01:07:35 UTC
#Software: Joomla! Platform 11.2.0 Stable [ Omar ] 27-Jul-2011 00:00 GMT

#Fields: datetime    priority       category             message
2012-10-15T01:07:35+00:00   WARNING   deprecated JDatabaseMySQLi::hasUTF() is deprecated.
2012-10-15T01:07:35+00:00   INFO   -   Subscription monitoring started
2012-10-15T01:07:35+00:00   INFO   -   Subscription removed for user=George Washington, title=Pontiac GTO
2012-10-15T01:07:35+00:00   INFO   -   Subscription ending notification sent for user=George Washington, title=Ford Mustang
2012-10-15T01:07:35+00:00   INFO   -   Subscription monitoring ended

Note that the first log entry is warning us that we are using a deprecated method, JDatabaseMySQLi::hasUTF(). We can ignore this message. The next line shows that we started the monitoring program. Then we removed one subscription and sent one notification. Then the program finished.

Test the different error conditions by entering invalid data in the tables or by making the queries fail. For example, change a user or subscription id in the subscription mapping to a number that doesn’t match an existing user or subscription. Or change the table name to be an invalid table. In these cases, you should see the error messages reflected in the log.

Running CLI Programs Inside the Joomla CMS

If we want to write command-line programs that interact with the CMS, we have two options for where to locate those programs. In this example, we created an entirely separate application that uses its own version of the platform and is completely independent of the CMS programs. All we need to know about the CMS is how to connect to its database and e-mail program. The monitor application could easily be on a separate server and could be used, for example, to monitor any number of different Joomla CMS sites (just by using different configuration files).

Alternatively, we can create command-line applications inside the Joomla CMS folder structure. In version 1.7, a folder called cli was added to the CMS folder structure for this purpose.

We can run this example program inside an instance of the CMS simply by copying the logs folder and the three program files (configuration.php, monitor.php, and subscriptionmonitor.php) from the src folder in the platform-test project to the cli folder of a Joomla CMS installation. Make sure the configuration file has the desired values. Then navigate to the CMS cli folder and run the monitor.php program from the command line. It should work exactly as before.

Also, note how similar the programming techniques used in this example are to the techniques used in the previous examples in this book. Programming in the platform is very similar to programming for the CMS. Once we have the platform loaded, the classes used, database access, and other techniques are very similar to the techniques used for programming in the CMS.

Summary

In this chapter, we introduced the Joomla platform. We covered how to install the platform and how to run the example programs both for the command line and the browser. Then we examined how the programs work, including how they load the required library classes.

Finally, we created a realistic working command-line program that could be run unattended in the web server. This program monitors our subscriptions for expired subscriptions and ones about to expire. This demonstrates how the platform can allow us to create programs that interact with a Joomla website.

The platform is an exciting new project with almost unlimited potential. It is likely that a number of interesting programs will be built using the platform. Features added to the platform will also benefit the Joomla CMS.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.99.71