How does a new feature end up in Joomla?

“The Category Item Counter”

Category Item Counter

by Peter Martin / @pe7er

Joomla World Conference 2016, Vancouver, Canada,
Sunday 13th November 2016

About me

Peter Martin (@pe7er)

Nijmegen, Netherlands, Europe

About me
Joomla support, application development

db8 Site Dev - free checklist component for devs

* Linux Usergroup Nijmegen
* Open Coffee Nijmegen

Joomla volunteer:
* Global Forum Moderator
* Joomla Bug Squad
* Pizza Bugs & Fun (in NL)

* Former Joomla Community
   Leadership Team (6 yr)
* Mentor GSoC 2016

Overview presentation

  • Origin of Category Item Counter
  • Well kept secret
  • Joomla's development process
  • My workflow
  • More generic?
  • Problems
  • Components learn to count
  • More problems

Origin of this "nice" feature

Joomladay Nice

Saturday 9 May 2015
around 15:00 hours

“It's a pity that the
Joomla Category Manager doesn't have
an Article Counter anymore”

Marc Dechèvre

“That should not be
that hard to build in, should it?”

Yours truly

(had to go to the hairdresser's)

Lesson 1

Share your ideas
for improvements constructively,
preferably in real life
(and not on Twitter)

Joomla 1.5 - Category Manager

Lesson 2

Have fun

(or make it)

deux bières

encore deux bières

Back in the hotel

a well-kept secret

Joomla 1.5 & categories

  • Articles (com_content): Articles + Category Manager

  • Banners (com_banners): Banners + Category Manager

  • Contacts (com_contacts): Contacts + Category Manager

  • Newsfeeds (com_newsfeeds): Newsfeeds + Cat Manager

  • Weblinks (com_weblinks): Weblinks + Category Manager

Category Item Counter: items counted with an SQL query

Joomla 3.x & categories

  • Articles (com_content): Articles

  • Banners (com_banners): Banners

  • Contacts (com_contacts): Contacts

  • Newsfeeds (com_newsfeeds): Newsfeeds

  • Weblinks (com_weblinks): Weblinks

  • Categories (com_categories): generic component

Articles & categories

  • Article Manager: Articles
    (Content > Article Manager)
    URL: /administrator/index.php

  • Category Manager: Articles
    (Content > Category Manager)
    URL: /administrator/index.php

Banners & categories

  • Banner Manager: Banners
    (Components > Banners > Banners)
    URL: /administrator/index.php

  • Category Manager: Banners
    (Components > Banners > Categories)
    URL: /administrator/index.php

Use com_categories
in your own components

in administrator/components/

		<option value="0">JOPTION_SELECT_CATEGORY</option>

Lesson 3

Limit the scope
of your project...

Take small steps

My scope


	if($extension == "com_content"):
	//add count SQL query to $query object

Joomla's development process


Software Version Control

  • Distributed - no central server
  • Historic overview of code changes

Software as a Service

  • private git repository - paid
  • public git repository - free (as in beer)

  • PR = Pull Request
    ask Joomla
    to pull your code of your private fork
    into Joomla's core code

  • RTC = Ready To Commit
    ready to be merged in Joomla
    need 3 succesfull tests
    → code maybe in next Joomla version...

Lesson 4

Be prepared to test

Patch Tester Component

  1. Install Joomla staging
    + default testing data
  2. Install Patch Testing Component
  3. load list of Pull Requests
  4. read testing instructions, test
  5. install patch
  6. test, write report
  7. uninstall patch

Joomla releases

Current version: Joomla 3.6.4

Patch to solve bug → next subversion (3.6.5)

New Feature → next major version (3.7)
(labels: “milestone” + “New feature”)

New language string
(label: “new language string”)

My workflow

Create copy (fork)

Clone to PC

Share new code

Lesson 5

Write clear
testing instructions

Get tested

Write clear instructions

Automatic test

Codestyle test

tip: install PHP Code Sniffer in your IDE

2x manual test

Test report via

Lesson 6

Use screen dumps!
A picture = thousand words

Before the PR

After the PR

“Hathor template?”
“Alignment looks bad”
“Travis is not happy...
Can you try to fix the Travis errors?”

Almost there...

Foto: Pierre Sempé

...but not quite

like this?

End of Joomladay France 2015

Hathor... Anyone?

Generic = more beautiful

Lesson 7

Dare to ask

Articles (com_content)

  • Article Manager: Articles
    via Content > Article Manager
    URL /administrator/index.php ?option=com_content

  • Category Manager: Articles
    via Content > Category Manager
    URL /administrator/index.php?option=com_categories

Left menu in Articles

  • Category Manager (com_categories)
    knows via extension=com_content

    and asks for helper file:
    com_content/ helpers/content.php

    the helper file generates the left menu.

Extend $query

  • Category Manager (com_categories)
    contains protected function getListQuery()

    → extend $query object via

    → load specific code from helper file
    of the extension=com_componentname component.

HTML output: Column header

  • administrator/components/com_categories/views/
    <?php if (isset($this->items[0])
    && property_exists($this->items[0],
    'count_published')) :
    $columns++; ?>
    <th width="1%" class="nowrap center hidden-phone">
    <i class="icon-publish"></i>
    <?php endif;?>

HTML output:
clickable counter

  • administrator/components/com_categories/views/
    <?php if (isset($this->items[0])
    && property_exists($this->items[0], 'count_published')) : ?>
    <td class="center btns hidden-phone">
      <a class="badge <?php if ($item->count_published > 0)
      echo "badge-success"; ?>"
      title="<?php echo Jtext::_('COM_ CATEGORY_COUNT_PUBLISHED_ITEMS');?>"
      href="<?php echo JRoute::_('index.php?option=' . $component .
      '&filter[category_id]=' . (int) $item->id .
      '&filter[published]=1' .
      '&filter[level]=' . (int) $item->level);?>">
        <?php echo $item->count_published; ?>
    <?php endif;?>

More problems
in the core code

Triggering the Filters

  • Publish state in core components

  • Publish state in Banners (com_banners)

Teach your own
components to count

Count published items


class YourcomponentHelper extends JHelperContent
  public static function countItems(&$query)
    // Join articles to cats and count published items
    $query->select('COUNT(DISTINCT AS count_published');
    $query->join('LEFT', '#__yourcomponent_items AS cp
    ON cp.catid = AND cp.state = 1');

    return $query;

* Do NOT use this SQL query... I'll explain in a minute...

Count other states

// Count unpublished items
$query->select('COUNT(DISTINCT AS count_unpublished');
$query->join('LEFT', '#__yourcomponent_items AS cu
ON cu.catid = AND cu.state = 0');

// Count archived items
$query->select('COUNT(DISTINCT AS count_archived');
$query->join('LEFT', '#__yourcomponent_items AS ca
ON ca.catid = AND ca.state = 2');

// Count trashed items
$query->select('COUNT(DISTINCT AS count_trashed');
$query->join('LEFT', '#__yourcomponent_items AS ct
ON ct.catid = AND ct.state = -2');

* Do NOT use this SQL query... I'll explain in a minute NOW:

Joomla, we have a problem

Just before the release of Joomla 3.5.0

Go on, count...

Some sites :-)

But not ours... Our websites are all perfectly orgganizes, aren't they?

Performance problem

“Category Manager is very slow on my site with 100 categories and 85.000 articles”


“New category count feature performance degrade #9420”
“Database queries total: 46.3 ms > 31,358.46 ms (31 sec!)”

Count faster...

1. Moved


// Load Helper file of the component for
// which com_categories displays the categories
$classname = ucfirst(substr($extension, 4)) . 'Helper';

if (class_exists($classname) && method_exists($classname,
  // Get the SQL to extend the com_category $query object
  // with item count (published, unpublished, trashed)
  // $classname::countItems($query);
to administrator/components/

2. Removed


Extending COUNT DISTINCT from $query object removed:

$query->select('COUNT(DISTINCT AS count_published');
$query->join('LEFT', '#__content AS cp
ON cp.catid = AND cp.state = 1');

3a. Changed


public static function countItems(&$items)
  $db = JFactory::getDbo();
  foreach ($items as $i => $item)
    $item->count_trashed = 0;
    $item->count_archived = 0;
    $item->count_unpublished = 0;
    $item->count_published = 0;
    $query = $db->getQuery(true);
    $query->select('state, count(*) AS count')
          ->where('catid = ' . (int) $item->id)

3b. Changed


    $arts = $db->loadObjectList();

    foreach ($arts as $i => $art) {
      if ($art->state == 1) { $item->count_published = $art->count; }

      if ($art->state == 0) {
          $item->count_unpublished = $art->count; }

      if ($art->state == 2) { $item->count_archived = $art->count; }

      if ($art->state == -2) { $item->count_trashed = $art->count; }
return $items;

Prob 2: New countItems feature

“only works for basic category calls without sections
because the extension string is not handled properly” (fixed #9186 #9462 )



  1. Share your ideas for improvements
    constructively, preferably in real life
    (and not on Twitter)
  2. Have fun (or make it)
  3. Limit the scope of your project...
    Take small steps
  4. Be prepared to test
  5. Write clear testing instructions
  6. Use screen dumps!
    A picture = thousand words
  7. Dare to ask


Thanks to everybody that helped developing this new feature!

Peter Martin
e-mail: info at
twitter: @pe7er