<?php
/**
* @package Regular Labs Library
* @version 25.6.10828
*
* @author Peter van Westen <[email protected]>
* @link https://regularlabs.com
* @copyright Copyright © 2025 Regular Labs All Rights Reserved
* @license GNU General Public License version 2 or later
*/
namespace RegularLabs\Library;
defined('_JEXEC') or die;
use Joomla\CMS\Component\ComponentHelper as JComponentHelper;
use Joomla\CMS\Factory as JFactory;
use Joomla\Registry\Registry as JRegistry;
class Article
{
static $articles = [];
/**
* Method to get article data.
*/
public static function get(int|string|null $id = null, bool $get_unpublished = \false, array $selects = []): object|null
{
$id = $id ?? null ?: (int) self::getId();
if ($id === 0) {
return null;
}
$cache = new \RegularLabs\Library\Cache();
if ($cache->exists()) {
return $cache->get();
}
$db = \RegularLabs\Library\DB::get();
$user = JFactory::getApplication()->getIdentity() ?: JFactory::getUser();
$custom_selects = !empty($selects);
$query = \RegularLabs\Library\DB::getQuery()->select($custom_selects ? $selects : [
'a.id',
'a.asset_id',
'a.title',
'a.alias',
'a.introtext',
'a.fulltext',
'a.state',
'a.catid',
'a.created',
'a.created_by',
'a.created_by_alias',
// Use created if modified is 0
'CASE WHEN a.modified = ' . \RegularLabs\Library\DB::quote($db->getNullDate()) . ' THEN a.created ELSE a.modified END as modified',
'a.modified_by',
'a.checked_out',
'a.checked_out_time',
'a.publish_up',
'a.publish_down',
'a.images',
'a.urls',
'a.attribs',
'a.version',
'a.ordering',
'a.metakey',
'a.metadesc',
'a.access',
'a.hits',
'a.metadata',
'a.featured',
'a.language',
])->from(\RegularLabs\Library\DB::quoteName('#__content', 'a'));
if (!is_numeric($id)) {
$query->where('(' . \RegularLabs\Library\DB::is('a.title', $id) . ' OR ' . \RegularLabs\Library\DB::is('a.alias', $id) . ')');
} else {
$query->where(\RegularLabs\Library\DB::is('a.id', (int) $id));
}
// Join on content_frontpage table
if (!$custom_selects) {
$query->select([\RegularLabs\Library\DB::quoteName('fp.featured_up'), \RegularLabs\Library\DB::quoteName('fp.featured_down')]);
}
$query->join('LEFT', \RegularLabs\Library\DB::quoteName('#__content_frontpage', 'fp') . ' ON ' . \RegularLabs\Library\DB::quoteName('fp.content_id') . ' = ' . \RegularLabs\Library\DB::quoteName('a.id'));
// Join on category table.
if (!$custom_selects) {
$query->select([\RegularLabs\Library\DB::quoteName('c.title', 'category_title'), \RegularLabs\Library\DB::quoteName('c.alias', 'category_alias'), \RegularLabs\Library\DB::quoteName('c.access', 'category_access'), \RegularLabs\Library\DB::quoteName('c.language', 'category_language'), \RegularLabs\Library\DB::quoteName('c.lft', 'category_lft'), \RegularLabs\Library\DB::quoteName('c.lft', 'category_ordering')]);
}
$query->innerJoin(\RegularLabs\Library\DB::quoteName('#__categories', 'c') . ' ON ' . \RegularLabs\Library\DB::quoteName('c.id') . ' = ' . \RegularLabs\Library\DB::quoteName('a.catid'))->where(\RegularLabs\Library\DB::is('c.published', '>0'));
// Join on user table.
if (!$custom_selects) {
$query->select(\RegularLabs\Library\DB::quoteName('u.name', 'author'));
}
$query->join('LEFT', \RegularLabs\Library\DB::quoteName('#__users', 'u') . ' ON ' . \RegularLabs\Library\DB::quoteName('u.id') . ' = ' . \RegularLabs\Library\DB::quoteName('a.created_by'));
// Join over the categories to get parent category titles
if (!$custom_selects) {
$query->select([\RegularLabs\Library\DB::quoteName('parent.title', 'parent_title'), \RegularLabs\Library\DB::quoteName('parent.id', 'parent_id'), \RegularLabs\Library\DB::quoteName('parent.path', 'parent_route'), \RegularLabs\Library\DB::quoteName('parent.alias', 'parent_alias'), \RegularLabs\Library\DB::quoteName('parent.language', 'parent_language')]);
}
$query->join('LEFT', \RegularLabs\Library\DB::quoteName('#__categories', 'parent') . ' ON ' . \RegularLabs\Library\DB::quoteName('parent.id') . ' = ' . \RegularLabs\Library\DB::quoteName('c.parent_id'));
// Join on voting table
if (!$custom_selects) {
$query->select(['ROUND(v.rating_sum / v.rating_count, 0) AS rating', \RegularLabs\Library\DB::quoteName('v.rating_count', 'rating_count')]);
}
$query->join('LEFT', \RegularLabs\Library\DB::quoteName('#__content_rating', 'v') . ' ON ' . \RegularLabs\Library\DB::quoteName('v.content_id') . ' = ' . \RegularLabs\Library\DB::quoteName('a.id'));
if (!$get_unpublished && !$user->authorise('core.edit.state', 'com_content') && !$user->authorise('core.edit', 'com_content')) {
\RegularLabs\Library\DB::addArticleIsPublishedFilters($query);
}
$db->setQuery($query);
$data = $db->loadObject();
if (empty($data)) {
return null;
}
if (isset($data->attribs)) {
// Convert parameter field to object.
$registry = new JRegistry($data->attribs);
$data->params = JComponentHelper::getParams('com_content');
$data->params->merge($registry);
}
if (isset($data->metadata)) {
// Convert metadata field to object.
$data->metadata = new JRegistry($data->metadata);
}
return $cache->set($data);
}
/**
* Gets the current article id based on url data
*/
public static function getId(): int|false
{
$id = \RegularLabs\Library\Input::getInt('id');
if (!$id || !(\RegularLabs\Library\Input::get('option', '') == 'com_content' && \RegularLabs\Library\Input::get('view', '') == 'article' || \RegularLabs\Library\Input::get('option', '') == 'com_flexicontent' && \RegularLabs\Library\Input::get('view', '') == 'item')) {
return \false;
}
return $id;
}
public static function getPageNumber(array|string &$all_pages, string $search_string): int
{
if (is_string($all_pages)) {
$all_pages = self::getPages($all_pages);
}
if (count($all_pages) < 2) {
return 0;
}
foreach ($all_pages as $i => $page_text) {
if ($i % 2) {
continue;
}
if (!str_contains($page_text, $search_string)) {
continue;
}
$all_pages[$i] = \RegularLabs\Library\StringHelper::replaceOnce($search_string, '---', $page_text);
return $i / 2;
}
return 0;
}
public static function getPages(string $string): array
{
if ($string == '') {
return [''];
}
return preg_split('#(<hr class="system-pagebreak" .*?>)#s', $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY);
}
/**
* Passes the different article parts through the given plugin method
*/
public static function process(?object &$article, string $context, object &$class, string $method, array $params = [], array $ignore_types = []): void
{
self::processText('title', $article, $class, $method, $params, $ignore_types);
self::processText('created_by_alias', $article, $class, $method, $params, $ignore_types);
$has_text = isset($article->text);
$has_description = isset($article->description);
$has_article_texts = isset($article->introtext) && isset($article->fulltext);
$text_same_as_description = $has_text && $has_description && $article->text == $article->description;
$text_same_as_article_text = \false;
self::processText('description', $article, $class, $method, $params, $ignore_types);
// This is a category page with a category description. So skip the text processing
if ($text_same_as_description) {
$article->text = $article->description;
return;
}
// Don't replace in text fields in the category list view, as they won't get used anyway
if (\RegularLabs\Library\Document::isCategoryList($context)) {
return;
}
// prevent fulltext from being messed with, when it is a json encoded string (Yootheme Pro templates do this for some weird f-ing reason)
if (!empty($article->fulltext) && str_starts_with($article->fulltext, '<!-- {')) {
self::processText('text', $article, $class, $method, $params, $ignore_types);
return;
}
if ($has_text && $has_article_texts) {
$check_text = \RegularLabs\Library\RegEx::replace('\s', '', $article->text);
$check_introtext_fulltext = \RegularLabs\Library\RegEx::replace('\s', '', $article->introtext . ' ' . $article->fulltext);
$text_same_as_article_text = $check_text == $check_introtext_fulltext;
}
if ($has_article_texts && !$has_text) {
self::processText('introtext', $article, $class, $method, $params, $ignore_types);
self::processText('fulltext', $article, $class, $method, $params, $ignore_types);
return;
}
if ($has_article_texts && $text_same_as_article_text) {
$splitter = '͞';
if (str_contains($article->introtext, $splitter) || str_contains($article->fulltext, $splitter)) {
$splitter = 'Ͽ';
}
$article->text = $article->introtext . $splitter . $article->fulltext;
self::processText('text', $article, $class, $method, $params, $ignore_types);
[$article->introtext, $article->fulltext] = explode($splitter, $article->text, 2);
$article->text = str_replace($splitter, ' ', $article->text);
return;
}
self::processText('text', $article, $class, $method, $params, $ignore_types);
self::processText('introtext', $article, $class, $method, $params, $ignore_types);
// Don't handle fulltext on category blog views
if ($context == 'com_content.category' && \RegularLabs\Library\Input::get('view', '') == 'category') {
return;
}
self::processText('fulltext', $article, $class, $method, $params, $ignore_types);
}
public static function processText(string $type, ?object &$article, object &$class, string $method, array $params = [], array $ignore_types = []): void
{
if (empty($article->{$type})) {
return;
}
if (in_array($type, $ignore_types, \true)) {
return;
}
call_user_func_array([$class, $method], [&$article->{$type}, ...$params]);
}
}