J4.x

CLI example - Onoffbydate

From Joomla! Documentation

Revision as of 01:16, 20 October 2021 by Ceford (talk | contribs) (First commit)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Introduction[edit]

There are occasions when a site needs to display or hide a module depending on the date. One example might be a custom html module displaying a message during winter. Another might be alternation of custom modules depending on the day of the week. Say one for weekdays and another for weekends. The example presented here uses a plugin, a cli command and a cron to achieve the desired effect.

The code is available on github: https://github.com/ceford/j4xdemos-plg-onoffbydate

The Plugin[edit]

The plugin is named onoffbydate because that is what it does. At the moment the plugin is coded as a system plugin but it probably merits a new cli type in due course. There are four files.

onoffbydate.xml[edit]

This is the installation file - a standard item for any extension.

<?xml version="1.0" encoding="utf-8"?>
<extension type="plugin" group="system" method="upgrade">
	<name>plg_system_onoffbydate</name>
	<author>Clifford E Ford</author>
	<creationDate>October 2021</creationDate>
	<copyright>(C) Clifford E Ford</copyright>
	<license>GNU General Public License version 3 or later</license>
	<authorEmail>cliff@ford.myzen.co.uk</authorEmail>
	<version>0.1.0</version>
	<description>PLG_SYSTEM_ONOFFBYDATE_DESCRIPTION</description>
	<files>
		<filename plugin="onoffbydate">onoffbydate.php</filename>
	</files>
	<languages>
		<language tag="en-GB">language/en-GB/plg_system_onoffbydate.ini</language>
		<language tag="en-GB">language/en-GB/plg_system_onoffbydate.sys.ini</language>
	</languages>
	<config>
	</config>
</extension>

onoffbydate.php[edit]

This is the code of the plugin.

<?php
/**
 * @package     Joomla.Plugin
 * @subpackage  System.onoffbydate
 *
 * @copyright   (C) 2021 Clifford E Ford. <https://fford.me.uk>
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('_JEXEC') or die;

use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Factory;
use Joomla\Event\SubscriberInterface;
use Joomla\CMS\Console\OnoffbydateCommand;

class Plgsystemonoffbydate extends CMSPlugin implements SubscriberInterface
{

	public static function getSubscribedEvents(): array
	{
		return [
				\Joomla\Application\ApplicationEvents::BEFORE_EXECUTE => 'registerCommands',
		];
	}

	public function registerCommands(): void
	{
		$serviceId = 'onoffbydate.action';

		Factory::getContainer()->share(
				$serviceId,
				function (\Psr\Container\ContainerInterface $container) {
					// do stuff to create command class and return it
					return new OnoffbydateCommand;
				},
				true
				);

		Factory::getContainer()->get(\Joomla\CMS\Console\Loader\WritableLoaderInterface::class)->add('onoffbydate:action', $serviceId);

	}
}

Note especially the line containing use Joomla\CMS\Console\OnoffbydateCommand; which indicates that the command code is to be found in libraries/src/Console and the line containing return new OnoffbydateCommand; which is where the class is loaded.

language files[edit]

There are two language files used during installation and plugin configuration. They are not covered here. Please view the code on github.

Note also that the command output is seen only by you on your Joomla installation so it is probably not worth making the output translatable. That seems to be the case with most of the Joomla core cli commands.

The Command File: OnoffbydateCommand.php[edit]

<?php
/**
 * @package     Joomla.Console
 * @subpackage  Onoffbydate
 *
 * @copyright   Copyright (C) 2005 - 2021 Clifford E Ford. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Console;

\defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\Console\Command\AbstractCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidOptionException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Style\SymfonyStyle;

class OnoffbydateCommand extends AbstractCommand
{
	/**
	 * The default command name
	 *
	 * @var    string
	 * @since  4.0.0
	 */
	protected static $defaultName = 'onoffbydate:action';

	/**
	 * @var InputInterface
	 * @since version
	 */
	private $cliInput;

	/**
	 * SymfonyStyle Object
	 * @var SymfonyStyle
	 * @since 4.0.0
	 */
	private $ioStyle;

	/**
	 * Configures the IO
	 *
	 * @param   InputInterface   $input   Console Input
	 * @param   OutputInterface  $output  Console Output
	 *
	 * @return void
	 *
	 * @since 4.0.0
	 *
	 */
	private function configureIO(InputInterface $input, OutputInterface $output)
	{
		$this->cliInput = $input;
		$this->ioStyle = new SymfonyStyle($input, $output);
	}

	/**
	 * Initialise the command.
	 *
	 * @return  void
	 *
	 * @since   4.0.0
	 */
	protected function configure(): void
	{
		$this->addArgument('action',
				InputArgument::REQUIRED,
				'name of action');
				$help = "<info>%command.name%</info> Toggles module Enabled/Disabled state
				\nUsage: <info>php %command.full_name% action ()</info>";

				$this->setDescription('Called by cron to run set the enabled state of a module.');
				$this->setHelp($help);

				$this->addArgument('module_id',
						InputArgument::REQUIRED,
						'module id');
	}

	/**
	 * Internal function to execute the command.
	 *
	 * @param   InputInterface   $input   The input to inject into the command.
	 * @param   OutputInterface  $output  The output to inject into the command.
	 *
	 * @return  integer  The command exit code
	 *
	 * @since   4.0.0
	 */
	protected function doExecute(InputInterface $input, OutputInterface $output): int
	{
		$this->configureIO($input, $output);

		$action = $this->cliInput->getArgument('action');
		$module_id = $this->cliInput->getArgument('module_id');

		switch ($action) {
			case 'oddday' :
				$result = $this->oddday($module_id);
				break;
			case 'winter' :
				$result = $this->winter($module_id);
				break;
			default:
				$this->ioStyle->error("Unknwon action: {$action}");
				return 0;
		}

		return 1;
	}

	protected function oddday($module_id)
	{
		// get the day of the month
		$day = date('j');
		if ($day % 2 == 0)
		{
			$msg = "Today is an even date.";
			$published = 0;
		}
		else
		{
			$msg = "Today is an odd date.";
			$published = 1;
		}

		$this->publish($module_id, $published);

		$state = empty($published) ? 'Unpublished' : 'Published';

		$this->ioStyle->success("That seemed to work. {$msg} Module {$module_id} has been {$state}");

	}

	protected function winter($module_id)
	{
		// get the month of the month
		$month = date('n');
		if (in_array($month, array(1,2,11,12)))
		{
			$msg = "Today is in winter.";
			$published = 0;
		}
		else
		{
			$msg = "Today is not in winter.";
			$published = 1;
		}

		$this->publish($module_id, $published);

		$state = empty($published) ? 'Unpublished' : 'Published';

		$this->ioStyle->success("That seemed to work. {$msg} Module {$module_id} has been {$state}");

	}

	protected function publish ($module_id, $published)
	{
		$db = Factory::getDbo();
		$query = $db->getQuery(true);
		$query->update('#__modules')
		->set('published = ' . $published)
		->where('id = ' . $module_id);
		$db->setQuery($query);
		$db->execute();
	}
}

The configure function establishes that two command line arguments are required:

  • An action keyword
  • A module id

The doExecute function is where the work gets done. Two action options are illustrated:

  • winter calls a function to publish a module in winter months.
  • oddday calls a function to publish a module if the date is odd.

Clearly you can add as many date related actions as you need.

The Module[edit]

This code simply changes the published state of a module depending on some function of the date. So you need the module id as illustrated in the right hand column of the Modules (Site) list page.

The Site Module List

The Command Line[edit]

If the code works you will see onoffbydate among the list of available commands. Open a terminal window and enter:

php cli/joomla.php list

If something goes wrong at this stage check that the php version invoked is the command line version and not that used by the web server. If you see onoffbydate among the list of commands you can invoke help to see how it should be used:

php cli/joomla.php onoffbydate:action --help    
Usage:
  onoffbydate:action <action> <module_id>

Arguments:
  action                name of action
  module_id             module id

Options:
  -h, --help            Display the help information
  -q, --quiet           Flag indicating that all output should be silenced
  -V, --version         Displays the application version
      --ansi            Force ANSI output
      --no-ansi         Disable ANSI output
  -n, --no-interaction  Flag to disable interacting with the user
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Help:
  onoffbydate:action Toggles module Enabled/Disabled state
  				
  Usage: php cli/joomla.php onoffbydate:action action ()

And then just try it out:

php cli/joomla.php onoffbydate:action winter 130

                                                                                                                        
 [OK] That seemed to work. Today is not in winter. Module 130 has been Published                                        
                                                                                                                        

The cron[edit]

The command can be tested in a terminal window but you probably want to use it from a cron. The winter option could be run on the first day of each month. The oddday option would be run daily. The important point is that you can have as many crons as you need to change the published state of as many modules as you like at any appropriate intervals. The same code works for all.

On a hosting service you need to give the full paths to the php executable and the joomla cli command. Example:

/usr/local/bin/php /home/username/public_html/pathtojoomla/cli/joomla.php onoffbydate:action winter 130

Depending on how you have set up your cron and your system you may get a comforting email containing exactly the same information you see in the command line.

Check[edit]

And of course: go to your home page and check that the module really has been published or unpublished.