primo commit

This commit is contained in:
2024-12-17 17:34:10 +01:00
commit e650f8df99
16435 changed files with 2451012 additions and 0 deletions

View File

@ -0,0 +1,51 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2021-2024 Joomlashack.com. All rights reserved
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\Button;
use Joomla\CMS\Button\ActionButton;
use Joomla\CMS\Language\Text;
defined('_JEXEC') or die();
class DefaultButton extends ActionButton
{
protected function preprocess()
{
$this->addState(
0,
'sitemap.setAsDefault',
'icon-unfeatured',
Text::_('COM_OSMAP_SITEMAP_IS_DEFAULT_LABEL'),
['tip_title' => Text::_('COM_OSMAP_SITEMAP_IS_DEFAULT_DESC')]
);
$this->addState(
1,
'sitemap.setAsDefault',
'icon-color-featured icon-star',
Text::_('COM_OSMAP_SITEMAP_IS_DEFAULT_LABEL')
);
parent::preprocess();
}
}

View File

@ -0,0 +1,49 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\Component;
use Joomla\CMS\Component\ComponentHelper;
defined('_JEXEC') or die();
abstract class Helper extends ComponentHelper
{
/**
* @inheritDoc
*/
public static function getParams($option = 'com_osmap', $strict = false)
{
return parent::getParams($option, $strict);
}
/**
* @inheritDoc
*/
public static function getComponent($option = 'com_osmap', $strict = false)
{
return parent::getComponent($option, $strict);
}
}

View File

@ -0,0 +1,190 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap;
defined('_JEXEC') or die();
/**
* A simple class for handling layered variables
*
* Class AbstractConfiguration
*
* @package OSMap
*/
class Configuration
{
/**
* @var array
*/
protected $settings = null;
public function __construct(array $settings = [])
{
$this->settings = $settings;
}
/**
* Confirm that the current configuration is valid
*
* @return bool
*/
public function isValid()
{
return true;
}
/**
* Translate dot notation into array keys
*
* @param string $name
* @param mixed $default
*
* @return mixed
*/
public function get($name, $default = null)
{
if (strpos($name, '.') === false) {
return $this->settings[$name] ?? $default;
}
$levels = explode('.', $name);
$value = &$this->settings;
for ($i = 0; $i < count($levels) - 1; $i++) {
$key = $levels[$i];
if (is_array($value) && isset($value[$key])) {
$value = &$value[$key];
} elseif (is_object($value) && isset($value->{$key})) {
$value = $value->{$key};
} else {
return $default;
}
}
$key = $levels[$i];
if (isset($value[$key])) {
return $value[$key];
}
return $default;
}
/**
* Save a dot notation key to the setting array
*
* @param string $name
* @param mixed $newValue
*
* @return mixed
* @throws \Exception
*/
public function set($name, $newValue)
{
$oldValue = $this->get($name);
if (strpos($name, '.') === false) {
$this->settings[$name] = $newValue;
} else {
$keys = explode('.', $name);
$tree = &$this->settings;
for ($i = 0; $i < count($keys) - 1; $i++) {
$key = $keys[$i];
if (empty($tree[$key]) || !is_array($tree[$key])) {
$tree[$key] = [];
}
$tree = &$tree[$key];
}
$final = array_pop($keys);
if ($newValue === null) {
unset($tree[$final]);
} else {
$tree[$final] = $newValue;
}
}
return $oldValue;
}
/**
* Return as Configuration class
*
* @param string $key
*
* @return Configuration
*/
public function toConfig($key = null)
{
if ($key) {
return new static($this->get($key, []));
}
return clone $this;
}
/**
* Return as JSON string
*
* @param string $key
*
* @return string
*/
public function toString($key = null)
{
$value = $key ? $this->get($key) : $this->settings;
return json_encode($value);
}
/**
* Return as stdClass
*
* @param string $key
*
* @return object
*/
public function toObject($key = null)
{
$value = $key ? $this->get($key) : $this->settings;
return json_decode(json_encode($value));
}
/**
* Return as array
*
* @param string $key
*
* @return array
*/
public function toArray($key = null)
{
$value = $key ? $this->get($key) : $this->settings;
return json_decode(json_encode($value), true);
}
/*
* @return string
*/
public function __toString()
{
return $this->toString();
}
}

View File

@ -0,0 +1,154 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap;
use Alledia\Framework\Profiler;
use Alledia\OSMap\Helper\Images;
use JDatabaseDriver;
use Joomla\CMS\Application\WebApplication;
use Joomla\CMS\Language\Language;
use Joomla\CMS\Uri\Uri;
use Joomla\CMS\User\User;
use Joomla\Input\Input;
use ReflectionClass;
defined('_JEXEC') or die();
/**
* Class Container
*
* @package OSMap
*
* @property WebApplication $app
* @property JDatabaseDriver $db
* @property Input $input
* @property User $user
* @property Language $language
* @property Profiler $profiler
* @property Router $router
* @property Uri $uri
* @property Images $imagesHelper
*
* @method WebApplication getApp()
* @method JDatabaseDriver getDb()
* @method Input getInput()
* @method User getUser()
* @method Language getLanguage()
* @method Profiler getProfiler()
* @method Router getRouter()
* @method Uri getUri()
*
*/
class Container extends \Pimple\Container
{
public function __get($name)
{
if (isset($this[$name])) {
return $this[$name];
}
return null;
}
public function __call($name, $args)
{
if (strpos($name, 'get') === 0 && !$args) {
$key = strtolower(substr($name, 3));
if (isset($this[$key])) {
return $this[$key];
}
}
return null;
}
/**
* Get instance of a class using parameter autodetect
*
* @param string $className
*
* @return object
* @throws \Exception
*/
public function getInstance($className)
{
$class = new ReflectionClass($className);
if ($instance = $this->getServiceEntry($class)) {
return $instance;
}
$dependencies = [];
if (!is_null($class->getConstructor())) {
$params = $class->getConstructor()->getParameters();
foreach ($params as $param) {
$dependentClass = $param->getClass();
if ($dependentClass) {
$dependentClassName = $dependentClass->name;
$dependentReflection = new ReflectionClass($dependentClassName);
if ($dependentReflection->isInstantiable()) {
//use recursion to get dependencies
$dependencies[] = $this->getInstance($dependentClassName);
} elseif ($dependentReflection->isInterface()) {
// Interfaces need to be pre-registered in the container
if ($concrete = $this->getServiceEntry($dependentReflection, true)) {
$dependencies[] = $concrete;
}
}
}
}
}
return $class->newInstanceArgs($dependencies);
}
/**
* Find a service in the container based on class name
* Classes can be registered either through their short name
* or full class name. Short name take precedence.
*
* @param ReflectionClass $class
* @param bool $require
*
* @return object|null
* @throws \Exception
*/
protected function getServiceEntry(ReflectionClass $class, $require = false)
{
$key = strtolower($class->getShortName());
if (isset($this[$key])) {
return $this[$key];
}
$name = $class->getName();
if (isset($this[$name])) {
return $this[$name];
}
if ($require) {
throw new \Exception($class->getName() . ' - is not registered in the container');
}
return null;
}
}

View File

@ -0,0 +1,95 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\Controller;
use Alledia\OSMap\Factory;
use Exception;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\AdminController;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Session\Session;
defined('_JEXEC') or die();
abstract class Admin extends AdminController
{
/**
* @inheritDoc
*/
public function execute($task)
{
$this->task = $task;
$task = strtolower($task);
PluginHelper::importPlugin('osmap');
$controllerName = strtolower(str_replace('OSMapController', '', get_class($this)));
$eventParams = [$controllerName, $task];
$results = Factory::getApplication()->triggerEvent('osmapOnBeforeExecuteTask', $eventParams);
// Check if any of the plugins returned the exit signal
if (is_array($results) && in_array('exit', $results, true)) {
Factory::getApplication()->enqueueMessage('COM_OSMAP_MSG_TASK_STOPPED_BY_PLUGIN', 'warning');
return null;
}
if (isset($this->taskMap[$task])) {
$doTask = $this->taskMap[$task];
} elseif (isset($this->taskMap['__default'])) {
$doTask = $this->taskMap['__default'];
} else {
throw new Exception(Text::sprintf('JLIB_APPLICATION_ERROR_TASK_NOT_FOUND', $task), 404);
}
// Record the actual task being fired
$this->doTask = $doTask;
$result = $this->$doTask();
// Runs the event after the task was executed
$eventParams[] = &$result;
Factory::getApplication()->triggerEvent('osmapOnAfterExecuteTask', $eventParams);
return $result;
}
/**
* @inheritDoc
*/
public function checkToken($method = 'post', $redirect = true)
{
if (is_callable([parent::class, 'checkToken'])) {
return parent::checkToken($method, $redirect);
}
Session::checkToken() or jexit(Text::_('JINVALID_TOKEN'));
return true;
}
}

View File

@ -0,0 +1,51 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\Controller;
use Alledia\OSMap\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\BaseController;
use Joomla\CMS\Session\Session;
defined('_JEXEC') or die();
class Base extends BaseController
{
public function checkToken($method = 'post', $redirect = true)
{
$valid = Session::checkToken();
if (!$valid && $redirect) {
$home = Factory::getApplication()->getMenu()->getDefault();
$container = Factory::getPimpleContainer();
$app = Factory::getApplication();
$app->enqueueMessage(Text::_('JINVALID_TOKEN'), 'error');
$app->redirect($container->router->routeURL('index.php?Itemid=' . $home->id));
}
return $valid;
}
}

View File

@ -0,0 +1,79 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\Controller;
use Alledia\OSMap\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\FormController;
use Joomla\CMS\Plugin\PluginHelper;
defined('_JEXEC') or die();
abstract class Form extends FormController
{
/**
* @inheritDoc
*/
public function execute($task)
{
$this->task = $task;
$task = strtolower($task);
PluginHelper::importPlugin('osmap');
$controllerName = strtolower(str_replace('OSMapController', '', get_class($this)));
$eventParams = [
$controllerName,
$task
];
$results = Factory::getApplication()->triggerEvent('osmapOnBeforeExecuteTask', $eventParams);
// Check if any of the plugins returned the exit signal
if (is_array($results) && in_array('exit', $results, true)) {
Factory::getApplication()->enqueueMessage('COM_OSMAP_MSG_TASK_STOPPED_BY_PLUGIN', 'warning');
return null;
}
if (isset($this->taskMap[$task])) {
$doTask = $this->taskMap[$task];
} elseif (isset($this->taskMap['__default'])) {
$doTask = $this->taskMap['__default'];
} else {
throw new \Exception(Text::sprintf('JLIB_APPLICATION_ERROR_TASK_NOT_FOUND', $task), 404);
}
// Record the actual task being fired
$this->doTask = $doTask;
$result = $this->$doTask();
// Runs the event after the task was executed
$eventParams[] = &$result;
Factory::getApplication()->triggerEvent('osmapOnAfterExecuteTask', $eventParams);
return $result;
}
}

View File

@ -0,0 +1,48 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\Controller;
use Joomla\CMS\Language\Text;
defined('_JEXEC') or die();
class Json extends \JControllerLegacy
{
/**
* @param string $method
* @param bool $redirect
*
* @return bool
* @throws \Exception
*/
public function checkToken($method = 'post', $redirect = true)
{
if (!\JSession::checkToken()) {
throw new \Exception(Text::_('JINVALID_TOKEN'), 403);
}
return true;
}
}

View File

@ -0,0 +1,107 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap;
use Alledia\OSMap\Sitemap\SitemapInterface;
use Joomla\CMS\Table\Table;
defined('_JEXEC') or die();
/**
* OSMap Factory
*/
class Factory extends \Alledia\Framework\Factory
{
/**
* @var Container
*/
protected static $pimpleContainer;
/**
* Get a OSMap container class
*
* @return Container
*/
public static function getPimpleContainer(): Container
{
if (empty(static::$pimpleContainer)) {
$config = [];
$container = new Container(['configuration' => new Configuration($config)]);
// Load the Service class according to the current license
$serviceClass = '\\Alledia\\OSMap\\Services\\' . ucfirst(OSMAP_LICENSE);
$container->register(new $serviceClass());
static::$pimpleContainer = $container;
}
return static::$pimpleContainer;
}
/**
* Returns an instance of the Sitemap class according to the given id and
* sitemap type.
*
* @param ?int $id
* @param string $type
*
* @return SitemapInterface
* @throws \Exception
*/
public static function getSitemap(?int $id, string $type = 'standard'): ?SitemapInterface
{
if ($id > 0) {
switch ($type) {
case 'standard':
return new Sitemap\Standard($id);
case 'images':
return new Sitemap\Images($id);
case 'news':
return new Sitemap\News($id);
}
}
return null;
}
/**
* Returns an instance of a table. If no prefix is set, we use OSMap's table
* prefix as default.
*
* @param string $tableName
* @param ?string $prefix
*
* @return Table
*/
public static function getTable(string $tableName, ?string $prefix = 'OSMapTable'): ?Table
{
return Table::getInstance($tableName, $prefix);
}
}

View File

@ -0,0 +1,403 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\Helper;
use Alledia\OSMap\Factory;
use Joomla\CMS\Filesystem\File;
use Joomla\CMS\HTML\Helpers\Sidebar;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Version;
use Joomla\Registry\Registry;
defined('_JEXEC') or die();
abstract class General
{
protected static $plugins = [];
/**
* Build the submenu in admin if needed. Triggers the
* onAdminSubmenu event for component addons to attach
* their own admin screens.
*
* The expected response must be an array
* [
* "text" => Static language string,
* "link" => Link to the screen
* "view" => unique view name
* ]
*
* @param string $viewName
*
* @return void
* @throws \Exception
*/
public static function addSubmenu(string $viewName)
{
if (Version::MAJOR_VERSION > 3) {
// Not needed for Joomla 4+
return;
}
$submenus = [
[
'text' => 'COM_OSMAP_SUBMENU_SITEMAPS',
'link' => 'index.php?option=com_osmap&view=sitemaps',
'view' => 'sitemaps'
],
[
'text' => 'COM_OSMAP_SUBMENU_EXTENSIONS',
'link' => 'index.php?option=com_plugins&view=plugins&filter_folder=osmap',
'view' => 'extensions'
]
];
Factory::getApplication()->triggerEvent('onOSMapAddAdminSubmenu', [&$submenus]);
if (!empty($submenus)) {
foreach ($submenus as $submenu) {
if (is_array($submenu)) {
Sidebar::addEntry(
Text::_($submenu['text']),
$submenu['link'],
$viewName == $submenu['view']
);
}
}
}
}
/**
* Returns the sitemap type checking the input.
* The expected types:
* - standard
* - images
* - news
*
* @return string
* @throws \Exception
*/
public static function getSitemapTypeFromInput(): string
{
$input = Factory::getPimpleContainer()->input;
if ($input->getBool('images', false)) {
return 'images';
} elseif ($input->getBool('news', false)) {
return 'news';
} else {
return 'standard';
}
}
/**
* Returns a list of plugins from the database. Legacy plugins from XMap
* will be returned first, then OSMap plugins. Always respecting the
* ordering.
*
* @return object[]
*/
public static function getPluginsFromDatabase(): array
{
$db = Factory::getPimpleContainer()->db;
// Get all the OSMap and XMap plugins. Get XMap plugins first
// then OSMap. Always respecting the ordering.
$query = $db->getQuery(true)
->select([
'folder',
'params',
'element'
])
->from('#__extensions')
->where('type = ' . $db->quote('plugin'))
->where(
sprintf(
'folder IN (%s)',
join(',', $db->quote(['osmap', 'xmap']))
)
)
->where('enabled = 1')
->order('folder DESC, ordering');
return $db->setQuery($query)->loadObjectList();
}
/**
* Returns true if the plugin is compatible with the given option
*
* @param object $plugin
* @param ?string $option
*
* @return bool
*/
protected static function checkPluginCompatibilityWithOption(object $plugin, ?string $option): bool
{
if (empty($option)) {
return false;
}
$path = JPATH_PLUGINS . '/' . $plugin->folder . '/' . $plugin->element . '/' . $plugin->element . '.php';
$compatible = false;
if (File::exists($path)) {
/*
* Legacy plugins have element == option.
* But may still not be compatible with
* the current content/option
*/
$isLegacy = $plugin->element === $option;
$className = $isLegacy
? ($plugin->folder . '_' . $option)
: ('Plg' . ucfirst($plugin->folder) . ucfirst($plugin->element));
if (!class_exists($className)) {
require_once $path;
}
// Instantiate the plugin if the class exists
if (class_exists($className)) {
$dispatcher = Factory::getDispatcher();
$instance = method_exists($className, 'getInstance') ?
$className::getInstance() : new $className($dispatcher);
// If is legacy, we know it is compatible since the element and option were already validated
$compatible = $isLegacy
|| (
method_exists($instance, 'getComponentElement')
&& $instance->getComponentElement() === $option
);
if ($compatible) {
$plugin->instance = $instance;
$plugin->className = $className;
$plugin->isLegacy = $isLegacy;
$plugin->params = new Registry($plugin->params);
}
}
}
return $compatible;
}
/**
* Gets the plugins according to the given option/component
*
* @param ?string $option
*
* @return object[]
*/
public static function getPluginsForComponent(?string $option): array
{
// Check if there is a cached list of plugins for this option
if ($option && empty(static::$plugins[$option])) {
$compatiblePlugins = [];
$plugins = static::getPluginsFromDatabase();
if ($plugins) {
foreach ($plugins as $plugin) {
if (static::checkPluginCompatibilityWithOption($plugin, $option)) {
$compatiblePlugins[] = $plugin;
}
}
}
static::$plugins[$option] = $compatiblePlugins;
}
return static::$plugins[$option] ?? [];
}
/**
* Extracts pagebreaks from the given text. Returns a list of subnodes
* related to each pagebreak.
*
* @param string $text
* @param string $baseLink
* @param ?string $uid
*
* @return object[]
*/
public static function getPagebreaks(string $text, string $baseLink, ?string $uid = ''): array
{
$matches = $subNodes = [];
if (preg_match_all(
'/<hr\s*[^>]*?(?:(?:\s*alt="(?P<alt>[^"]+)")|(?:\s*title="(?P<title>[^"]+)"))+[^>]*>/i',
$text,
$matches,
PREG_SET_ORDER
)) {
$i = 2;
foreach ($matches as $match) {
if (strpos($match[0], 'class="system-pagebreak"') !== false) {
$link = $baseLink . '&limitstart=' . ($i - 1);
$subNode = (object)[
'name' => $match['alt'] ?? $match['title'] ?? Text::sprintf('Page #', $i),
'uid' => $uid . '.' . ($i - 1),
'expandible' => false,
'link' => $link,
];
$subNodes[] = $subNode;
$i++;
}
}
}
return $subNodes;
}
/**
* Returns true if the given date is empty, considering not only as string,
* but integer, boolean or date.
*
* @param mixed $date
*
* @return bool
*/
public static function isEmptyDate($date): bool
{
$db = Factory::getPimpleContainer()->db;
$invalidDates = [
'',
null,
false,
0,
'0',
-1,
'-1',
$db->getNullDate(),
'0000-00-00'
];
return in_array($date, $invalidDates);
}
/**
* Returns an array or string with the authorised view levels for public or
* guest users. If the param $asString is true, it returns a string as CSV.
* If false, an array. If the current view was called by the admin to edit
* the sitemap links, we return all access levels to give access for everything.
*
* @param bool $asString
*
* @return string|string[]
*/
public static function getAuthorisedViewLevels(bool $asString = true)
{
$container = Factory::getPimpleContainer();
$levels = [];
// Check if we need to return all levels, if it was called from the admin to edit the link list
if ($container->input->get('view') === 'adminsitemapitems') {
$db = $container->db;
// Get all access levels
$query = $db->getQuery(true)
->select('id')
->from($db->quoteName('#__viewlevels'));
$rows = $db->setQuery($query)->loadRowList();
foreach ($rows as $row) {
$levels[] = $row[0];
}
} else {
// Only shows returns the level for the current user
$levels = Factory::getUser()->getAuthorisedViewLevels();
}
if ($asString) {
$levels = implode(',', $levels);
}
return $levels;
}
/**
* Make sure the appropriate component language files are loaded
*
* @param string $option
* @param string $adminPath
* @param string $sitePath
*
* @return void
* @throws \Exception
*/
public static function loadOptionLanguage(
string $option = 'com_osmap',
string $adminPath = OSMAP_ADMIN_PATH,
string $sitePath = OSMAP_SITE_PATH
) {
$app = Factory::getApplication();
switch ($app->getName()) {
case 'administrator':
Factory::getLanguage()->load($option, $adminPath);
break;
case 'site':
Factory::getLanguage()->load($option, $sitePath);
break;
}
}
/**
* Check if the needed method is static or not and call it in the proper
* way, avoiding Strict warnings in 3rd party plugins. It returns the
* result of the called method.
*
* @param string $class
* @param object $instance
* @param string $method
* @param array $params
*
* @return mixed
*/
public static function callUserFunc(string $class, object $instance, string $method, array $params = [])
{
try {
$reflection = new \ReflectionMethod($class, $method);
return $reflection->isStatic()
? call_user_func_array([$class, $method], $params)
: call_user_func_array([$instance, $method], $params);
} catch (\Exception $error) {
// Just ignore this?
}
return null;
}
}

View File

@ -0,0 +1,133 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\Helper;
use Alledia\OSMap\Factory;
use Exception;
// phpcs:disable PSR1.Files.SideEffects
defined('_JEXEC') or die();
// phpcs:enable PSR1.Files.SideEffects
// phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
class Images
{
/**
* Extracts images from the given text.
*
* @param string $text
* @param ?int $max
*
* @return array
* @throws Exception
*/
public function getImagesFromText(string $text, int $max = 0): array
{
$container = Factory::getPimpleContainer();
$images = [];
// Look for <img> tags
preg_match_all(
'/<img[^>](?=.*(?:src="(?P<src>[^"]*)"))?(?=.*(title="(?P<title>[^"]*)"))?(?=.*(alt="(?P<alt>[^"]*)"))?.[^>]*>/i',
$text,
$matches1,
PREG_SET_ORDER
);
// Look for <a> tags with href to images
preg_match_all(
'/<a[^>](?=.*(?:href="(?P<src>[^"]+\.(gif|png|jpg|jpeg))"))(?=.*(title="(?P<title>[^"]*)"))?.[^>]*>/i',
$text,
$matches2,
PREG_SET_ORDER
);
$matches = array_merge($matches1, $matches2);
if (count($matches)) {
if ($max > 0) {
$matches = array_slice($matches, 0, $max);
}
foreach ($matches as $match) {
if (
($src = trim($match['src'] ?? ''))
&& $container->router->isInternalURL($src)
) {
if ($container->router->isRelativeUri($src)) {
$src = $container->router->convertRelativeUriToFullUri($src);
}
$title = trim($match['title'] ?? '');
$alt = trim($match['alt'] ?? '');
$images[] = (object)[
'src' => $src,
'title' => $title ?: ($alt ?: '')
];
}
}
}
return $images;
}
/**
* Return an array of images from the content image params.
*
* @param object $item
*
* @return array
* @throws Exception
*/
public function getImagesFromParams($item)
{
$container = Factory::getPimpleContainer();
$imagesParam = json_decode($item->images);
$images = [];
if (isset($imagesParam->image_intro) && !empty($imagesParam->image_intro)) {
$ignoreAlt = $imagesParam->image_intro_alt_empty ?? false;
$images[] = (object)[
'src' => $container->router->convertRelativeUriToFullUri($imagesParam->image_intro),
'title' => $imagesParam->image_intro_caption
?: ($ignoreAlt ? null : $imagesParam->image_intro_alt)
];
}
if (isset($imagesParam->image_fulltext) && !empty($imagesParam->image_fulltext)) {
$ignoreAlt = $imagesParam->image_fulltext_alt_empty ?? true;
$images[] = (object)[
'src' => $container->router->convertRelativeUriToFullUri($imagesParam->image_fulltext),
'title' => $imagesParam->image_fulltext_caption
?: ($ignoreAlt ? null : $imagesParam->image_fulltext_all)
];
}
return $images;
}
}

View File

@ -0,0 +1,61 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\Helper;
use Alledia\OSMap\Factory;
defined('_JEXEC') or die();
abstract class Legacy
{
/**
* Method to return a list of language home page menu items.
* Added here to keep compatibility with Joomla 3.4.x.
*
* @return array of menu objects.
*/
public static function getSiteHomePages()
{
// To avoid doing duplicate database queries.
static $multilangSiteHomePages = null;
if (!isset($multilangSiteHomePages)) {
// Check for Home pages languages.
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('language')
->select('id')
->from($db->quoteName('#__menu'))
->where('home = 1')
->where('published = 1')
->where('client_id = 0');
$db->setQuery($query);
$multilangSiteHomePages = $db->loadObjectList('language');
}
return $multilangSiteHomePages;
}
}

View File

@ -0,0 +1,127 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\Plugin;
use Alledia\Framework\Exception;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Plugin\CMSPlugin;
defined('_JEXEC') or die();
abstract class Base extends CMSPlugin
{
/**
* @var int
*/
protected static $memoryLimit = null;
/**
* Minimum memory in MB required to continue on sites with limited memory
*
* @var int
*/
protected static $memoryMinimum = 4;
/**
* @inheritDoc
*/
public function __construct(&$subject, $config = [])
{
require_once JPATH_ADMINISTRATOR . '/components/com_osmap/include.php';
parent::__construct($subject, $config);
}
/**
* Set memory limit to unlimited. If unable to do so,
* we'll want to check that we have enough memory left to continue,
* so we can fail gracefully
*
* @return void
*/
protected static function fixMemoryLimit()
{
if (static::$memoryLimit === null) {
$limit = @ini_set('memory_limit', -1);
if (empty($limit)) {
$mags = [
'K' => 1024,
'M' => 1024 * 1024,
'G' => 1024 * 1024 * 1024
];
$limit = ini_get('memory_limit');
$regex = sprintf('/(\d*)([%s])/', join(array_keys($mags)));
if (preg_match($regex, $limit, $match)) {
$limit = $match[1] * $mags[$match[2]];
}
static::$memoryLimit = $limit;
static::$memoryMinimum *= $mags['M'];
}
}
}
/**
* Check to see if we're about to run out of memory. If things get too tight
* all we can do is throw an informative message or redirect somewhere else
* that isn't an OSMap page
*
* @TODO: Decide whether to implement the redirect option
*
* @return void
* @throws Exception
*/
protected static function checkMemory()
{
if (static::$memoryLimit === null) {
static::fixMemoryLimit();
}
if (static::$memoryLimit && (static::$memoryLimit - memory_get_usage(true) < static::$memoryMinimum)) {
$message = Text::sprintf('COM_OSMAP_WARNING_OOM', get_called_class());
throw new Exception($message, 500);
}
}
/**
* @param string $url
*
* @return string
*/
protected static function getViewFromUrl(string $url): string
{
$linkUrl = parse_url($url);
if (isset($linkUrl['query'])) {
parse_str($linkUrl['query'], $linkQuery);
if (isset($linkQuery['view'])) {
return $linkQuery['view'];
}
}
return '';
}
}

View File

@ -0,0 +1,73 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\Plugin;
use Alledia\OSMap\Sitemap\Collector;
use Alledia\OSMap\Sitemap\Item;
use Joomla\Registry\Registry;
defined('_JEXEC') or die();
interface ContentInterface
{
/**
* Returns the unique instance of the plugin
*
* @return object
*/
public static function getInstance();
/**
* Returns the element of the component which this plugin supports.
*
* @return string
*/
public function getComponentElement();
/**
* This function is called before a menu item is used. We use it to set the
* proper uniqueid for the item
*
* @param Item $node Menu item to be "prepared"
* @param Registry $params The extension params
*
* @return void
* @since 1.2
*/
public static function prepareMenuItem($node, $params);
/**
* Expands a com_content menu item
*
* @param Collector $collector
* @param Item $parent
* @param Registry $params
*
* @return void
* @since 1.0
*/
public static function getTree($collector, $parent, $params);
}

View File

@ -0,0 +1,189 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Uri\Uri;
use Joomla\CMS\Version;
defined('_JEXEC') or die();
class Router
{
/**
* Route the given URL using the site application. If in admin, the result
* needs to be the same as the frontend.
*
* @param string $url
* @param bool $absolute
*
* @return string
*/
public function routeURL(string $url, bool $absolute = false): string
{
if ((strpos($url, '&') !== 0) && (strpos($url, 'index.php') !== 0)) {
// Not a routable URL
return $url;
}
if ($absolute) {
return Route::link('site', $url, true, Route::TLS_IGNORE, true);
}
return Route::link('site', $url);
}
/**
* Checks if the supplied URL is internal
*
* @param ?string $url
*
* @return bool
*/
public function isInternalURL(?string $url): bool
{
$uri = Uri::getInstance($url);
$base = $uri->toString(['scheme', 'host', 'port', 'path']);
$host = $uri->toString(['scheme', 'host', 'port']);
$path = $uri->toString(['path']);
$baseHost = Uri::getInstance($uri::root())->toString(['host']);
if ($path === $url) {
return true;
} elseif (
empty($host)
&& strpos($path, 'index.php') === 0
|| empty($host) == false && preg_match('#' . preg_quote($uri::root(), '#') . '#', $base)
|| empty($host) == false && $host === $baseHost && strpos($path, 'index.php') !== false
|| empty($host) == false && $base === $host
&& preg_match('#' . preg_quote($base, '#') . '#', $uri::root())
) {
return true;
}
return false;
}
/**
* Check if the given URL is a relative URI. Returns true, if affirmative.
*
* @param ?string $url
*
* @return bool
*/
public function isRelativeUri(?string $url): bool
{
if ($url) {
$urlPath = (new Uri($url))->toString(['path']);
return $urlPath === $url;
}
return false;
}
/**
* Converts an internal relative URI into a full link.
*
* @param string $path
*
* @return string
*/
public function convertRelativeUriToFullUri(string $path): string
{
if (
Version::MAJOR_VERSION > 3
&& strpos($path, '#joomlaImage:') !== false
) {
$media = HTMLHelper::_('cleanImageURL', $path);
$path = $media->url;
}
if ($path[0] == '/') {
$scheme = ['scheme', 'user', 'pass', 'host', 'port'];
$path = Uri::getInstance()->toString($scheme) . $path;
} elseif ($this->isRelativeUri($path)) {
$path = Uri::root() . $path;
}
return $path;
}
/**
* Returns a sanitized URL, removing double slashes and trailing slash.
*
* @param ?string $url
*
* @return ?string
*/
public function sanitizeURL(?string $url): ?string
{
if ($url) {
return preg_replace('#([^:])(/{2,})#', '$1/', $url);
}
return null;
}
/**
* Returns a URL without the hash
*
* @param ?string $url
*
* @return ?string
*/
public function removeHashFromURL(?string $url): ?string
{
if ($url) {
// Check if the URL has a hash to remove it (XML sitemap shouldn't have hash on the URL)
$hashPos = strpos($url, '#');
if ($hashPos !== false) {
// Remove the hash
$url = substr($url, 0, $hashPos);
}
return trim($url);
}
return null;
}
/**
* Create a consistent url hash regardless of scheme or site root.
*
* @param ?string $url
*
* @return string
*/
public function createUrlHash(?string $url): string
{
return md5(str_replace(Uri::root(), '', (string)$url));
}
}

View File

@ -0,0 +1,109 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\Services;
use Alledia\Framework\Profiler;
use Alledia\OSMap\Factory;
use Alledia\OSMap\Helper\Images;
use Alledia\OSMap\Router;
use Joomla\CMS\Uri\Uri;
use Pimple\Container as Pimple;
use Pimple\ServiceProviderInterface;
defined('_JEXEC') or die();
/**
* Class Services
*
* Pimple services for OSMap. The container must be instantiated with
* at least the following values:
*
* new \OSMap\Container(
* array(
* 'configuration' => new Configuration($config)
* )
* )
*
* @package OSMap
*/
class Free implements ServiceProviderInterface
{
/**
* Registers services on the given container.
*
* This method should only be used to configure services and parameters.
* It should not get services.
*
* @param Pimple $pimple
*/
public function register(Pimple $pimple)
{
$pimple['app'] = function () {
return Factory::getApplication();
};
$pimple['db'] = function () {
return Factory::getDbo();
};
$pimple['input'] = function () {
return Factory::getApplication()->input;
};
$pimple['user'] = function () {
return Factory::getUser();
};
$pimple['language'] = function () {
return Factory::getLanguage();
};
$pimple['profiler'] = function () {
return new Profiler();
};
$pimple['router'] = function () {
return new Router();
};
$pimple['uri'] = function () {
return new Uri();
};
$this->registerHelper($pimple);
}
/**
* @param Pimple $pimple
*
* @return void
*/
protected function registerHelper(Pimple $pimple)
{
$pimple['imagesHelper'] = function () {
return new Images();
};
}
}

View File

@ -0,0 +1,794 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\Sitemap;
use Alledia\OSMap\Factory;
use Alledia\OSMap\Helper\General;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\Registry\Registry;
defined('_JEXEC') or die();
/**
* Sitemap items collector
*/
class Collector
{
/**
* @var SitemapInterface
*/
protected $sitemap;
/**
* @var array
*/
protected $uidList = [];
/**
* @var array
*/
protected $urlHashList = [];
/**
* Callback used to trigger the desired action while fetching items.
* This is only used in the legacy method printNode, which is called by
* the osmap plugins to process the additional items.
*
* @var callable
*/
protected $printNodeCallback;
/**
* The current view: xml or html. Kept for backward compatibility with
* the legacy plugins. It is always XML since the collector is generic now
* and needs to have the information about the item's level even for the
* XML view in the Pro version
*
* @var string
*/
public $view = 'xml';
/**
* Legacy property used by some plugins. True if we are collecting news.
*
* @var string
*
* @deprecated
*/
public $isNews = false;
/**
* The items counter.
*
* @var int
*/
protected $counter = 0;
/**
* The custom settings for items
*
* @var array
*/
protected $itemsSettings = null;
/**
* The legacy custom settings for items. Which will be upgraded
*
* @var array
*/
protected $legacyItemsSettings = null;
/**
* If false, say that any next sub-level should be unpublished
*
* @var mixed
*/
protected $unpublishLevel = false;
/**
* @var array
*/
protected $tmpItemDefaultSettings = [
'changefreq' => 'weekly',
'priority' => '0.5'
];
/**
* The current items level
*
* @var int
*/
protected $currentLevel = 0;
/**
* The reference for the instance of the current menu for item and its
* subitems.
*
* @var object
*/
protected $currentMenu;
/**
* The ID of the current menu item for nodes
*
* @var int
*/
protected $currentMenuItemId = null;
/**
* The component's params
*
* @var Registry
*/
public $params;
/**
* Collector constructor.
*
* @param SitemapInterface $sitemap
*
* @return void
*/
public function __construct(SitemapInterface $sitemap)
{
$this->sitemap = $sitemap;
$this->params = ComponentHelper::getParams('com_osmap');
/*
* Try to detect the current view. This is just for backward compatibility
* for legacy plugins. New plugins doesn't need to know what is the view.
* They always calculate the visibility for both views and the view is
* the one who decides to whow or not. If not equals HTML, is always XML.
*/
$inputView = Factory::getPimpleContainer()->input->get('view', 'xml');
if ($inputView === 'html') {
$this->view = 'html';
}
}
/**
* Collects sitemap items based on the selected menus. This is the main
* method of this class. For each found item, it will call the given
* callback, so it can manipulate the data in many ways. It returns the
* total of found items.
*
* @param callable $callback
*
* @return int
*/
public function fetch(callable $callback): int
{
$menus = $this->getSitemapMenus();
$this->counter = 0;
if ($menus) {
$this->getLegacyItemsSettings();
$this->getItemsSettings();
foreach ($menus as $menu) {
$this->currentMenu = &$menu;
$items = $this->getMenuItems($menu);
foreach ($items as $item) {
if ($this->itemIsBlackListed($item)) {
$item = null;
continue;
}
// Store the current menu item id. Added to use it while defining the node's settings hash, so same
// items, but from different menus can have individual settings
$this->currentMenuItemId = $item['id'];
// Set the menu item UID. The UID can be changed by 3rd party plugins, according to the content
$item['uid'] = 'menuitem.' . $item['id'];
// Store the menu settings to use in the submitItemToCallback called by callbacks
$this->tmpItemDefaultSettings['changefreq'] = $menu->changefreq;
$this->tmpItemDefaultSettings['priority'] = $menu->priority;
// Check the level of menu
$level = (int)$item['level'] - 1;
if ($level !== $this->currentLevel) {
$this->changeLevel($level - $this->currentLevel);
}
// Submit the item and prepare it calling the plugins
$this->submitItemToCallback($item, $callback, true);
// Internal links can trigger plugins to grab more items
// The child items are not displayed if the parent item is ignored
if ($item->isInternal && !$item->ignore) {
// Call the plugin to get additional items related to it
$this->callPluginsGetItemTree($item, $callback);
}
// Make sure the memory is cleaned up
$item = null;
}
$items = [];
unset($items);
}
$menu = null;
}
$this->currentMenu = null;
$this->tmpItemDefaultSettings = [];
return $this->counter;
}
/**
* Submit the item to the callback, checking duplicity and incrementing
* the counter. It can receive an array or object and returns true or false
* according to the result of the callback.
*
* @param array|object $item
* @param callable $callback
* @param bool $prepareItem
*
* @return bool
*/
public function submitItemToCallback(&$item, callable $callback, bool $prepareItem = false): bool
{
$currentMenuItemId = $this->getCurrentMenuItemId();
$item = (object)$item;
// Add the menu information
$item->menuItemId = $this->currentMenu->id;
$item->menuItemTitle = $this->currentMenu->name;
$item->menuItemType = $this->currentMenu->menutype;
// Converts to an Item instance, setting internal attributes
$item = new Item($item, $currentMenuItemId);
if ($prepareItem) {
// Call the plugins to prepare the item
$this->callPluginsPreparingTheItem($item);
}
// Make sure to have the correct date format (UTC)
$item->setModificationDate();
$item->setAdapter();
$item->visibleForRobots = $item->adapter->checkVisibilityForRobots();
// Set the current level to the item
$item->level = $this->currentLevel;
$this->setItemCustomSettings($item);
$this->checkParentIsUnpublished($item);
$this->checkDuplicatedUIDToIgnore($item);
// Verify if the item can be displayed to count as unique for the XML sitemap
if (
!$item->ignore
&& $item->published
&& $item->visibleForRobots
&& (!$item->duplicate || !$this->params->get('ignore_duplicated_uids', 1))
) {
// Check if the URL is not duplicated (specially for the XML sitemap)
$this->checkDuplicatedURLToIgnore($item);
if (!$item->duplicate || !$this->params->get('ignore_duplicated_uids', 1)) {
++$this->counter;
}
}
return (bool)call_user_func_array($callback, [&$item]);
}
/**
* Gets the list of selected menus for the sitemap.
* It returns a list of objects with the attributes:
* - name
* - menutype
* - priority
* - changefrq
* - ordering
*
* @return object[];
*/
protected function getSitemapMenus(): array
{
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select([
'mt.id',
'mt.title AS ' . $db->quoteName('name'),
'mt.menutype',
'osm.changefreq',
'osm.priority',
'osm.ordering'
])
->from('#__osmap_sitemap_menus AS osm')
->join('LEFT', '#__menu_types AS mt ON (osm.menutype_id = mt.id)')
->where('osm.sitemap_id = ' . $db->quote($this->sitemap->id))
->order('osm.ordering');
return $db->setQuery($query)->loadObjectList('menutype');
}
/**
* Get the menu items as a tree
*
* @param object $menu
*
* @return array
*/
protected function getMenuItems(object $menu): array
{
$container = Factory::getPimpleContainer();
$db = $container->db;
$app = $container->app;
$lang = $container->language;
$query = $db->getQuery(true)
->select([
'm.id',
'm.title AS ' . $db->quoteName('name'),
'm.alias',
'm.path',
'm.level',
'm.type',
'm.home',
'm.params',
'm.parent_id',
'm.browserNav',
'm.link',
'1 AS ' . $db->quoteName('isMenuItem'), // Say that the menu came from a menu
'0 AS ' . $db->quoteName('ignore') // Flag that allows child classes choose to ignore items
])
->from('#__menu AS m')
->join('INNER', '#__menu AS p ON (p.lft = 0)')
->where([
'm.menutype = ' . $db->quote($menu->menutype),
'm.published = 1',
sprintf('m.access IN (%s)', General::getAuthorisedViewLevels()),
'm.lft > p.lft',
'm.lft < p.rgt'
])
->order('m.lft');
if ($app->isClient('site')) {
if ($app->getLanguageFilter()) {
$languageTags = array_map([$db, 'quote'], [$lang->getTag(), '*']);
$query->where(sprintf('m.language IN (%s)', join(',', $languageTags)));
}
}
$items = $db->setQuery($query)->loadAssocList();
if ($this->params->get('ignore_hidden_menus', false)) {
$items = array_filter(
$items,
function ($menu) {
$params = json_decode($menu['params']);
if (isset($params->menu_show) && $params->menu_show == 0) {
return false;
}
return true;
}
);
}
return $items;
}
/**
* Checks if the item's uid was already registered. If positive, set the
* item to be ignored and return true. If negative, register the item and
* return false.
*
* @param Item $item
*
* @return bool
*/
protected function checkDuplicatedUIDToIgnore(Item $item): bool
{
// If is already set, interrupt the flux and ignore the item
if (isset($this->uidList[$item->uid])) {
$item->duplicate = true;
if ($this->params->get('ignore_duplicated_uids', 1)) {
$item->addAdminNote('COM_OSMAP_ADMIN_NOTE_DUPLICATED_IGNORED');
} else {
$item->addAdminNote('COM_OSMAP_ADMIN_NOTE_DUPLICATED');
}
return true;
}
// Not set and published, so let's register
if ($item->published && $item->visibleForRobots && !$item->ignore) {
$this->uidList[$item->uid] = 1;
}
return false;
}
/**
* Checks if the item's full link was already registered. If positive,
* set the item to be ignored and return true. If negative, register the item and return false
*
* @param Item $item
*
* @return bool
*/
protected function checkDuplicatedURLToIgnore(Item $item): bool
{
if (!empty($item->fullLink)) {
$container = Factory::getPimpleContainer();
// We need to make sure to have a URL free of hash chars
$url = $container->router->removeHashFromURL($item->fullLink);
$hash = $container->router->createUrlHash($url);
if (isset($this->urlHashList[$hash])) {
$item->duplicate = true;
$item->addAdminNote('COM_OSMAP_ADMIN_NOTE_DUPLICATED_URL_IGNORED');
return true;
}
// Not set and published, so let's register
if ($item->published && $item->visibleForRobots && !$item->ignore) {
$this->urlHashList[$hash] = 1;
}
}
return false;
}
/**
* Calls the respective OSMap and XMap plugin, according to the item's
* component/option. If the plugin's method returns false, it will set
* the item's ignore attribute to true.
*
* @param Item $item
*
* @return void
*/
protected function callPluginsPreparingTheItem(Item $item)
{
$plugins = General::getPluginsForComponent($item->component);
foreach ($plugins as $plugin) {
$className = '\\' . $plugin->className;
if (method_exists($className, 'prepareMenuItem')) {
if ($plugin->isLegacy) {
$params = $plugin->params->toArray();
} else {
$params =& $plugin->params;
}
$arguments = [
&$item,
&$params
];
// If a legacy plugin doesn't specify this method as static, fix the plugin to avoid warnings
$result = General::callUserFunc(
$className,
$plugin->instance,
'prepareMenuItem',
$arguments
);
// If a plugin doesn't return true we ignore the item and break
if ($result === false) {
$item->set('ignore', true);
break;
}
}
$plugin = null;
}
}
/**
* Calls the respective OSMap and XMap plugin, according to the item's
* component/option. Get additional items and send to the callback.
*
* @param Item $item
* @param callable $callback
*
* @return void
*/
protected function callPluginsGetItemTree(Item $item, callable $callback)
{
$this->printNodeCallback = $callback;
// Call the OSMap and XMap legacy plugins
$plugins = General::getPluginsForComponent($item->component);
foreach ($plugins as $plugin) {
$className = '\\' . $plugin->className;
if (method_exists($className, 'getTree')) {
if ($plugin->isLegacy) {
$params = $plugin->params->toArray();
} else {
$params = $plugin->params;
}
$arguments = [
&$this,
&$item,
&$params
];
General::callUserFunc(
$className,
$plugin->instance,
'getTree',
$arguments
);
}
}
}
/**
* Returns true if the link of the item is in the blacklist array.
*
* @param array $item
*
* @return bool
*/
protected function itemIsBlackListed(array $item): bool
{
$blackList = [
'administrator' => 1
];
$link = $item['link'];
return isset($blackList[$link]);
}
/**
* This method is used for backward compatibility. The plugins will call
* it. In the legacy XMap, its behavior depends on the sitemap view type,
* only changing the level in the HTML view. OSMap will always consider the
* level of the item, even for XML view. XML will just ignore that.
*
* @param int $step
*
* @return void
*/
public function changeLevel(int $step)
{
$this->currentLevel += $step;
}
/**
* Method called by legacy plugins, which will pass the new item to the
* callback. Returns the result of the callback converted to boolean.
*
* @param array|object $node
*
* @return bool
*/
public function printNode($node): bool
{
return $this->submitItemToCallback($node, $this->printNodeCallback);
}
/**
* This method gets the settings for all items which have custom settings.
*
* @return array;
*/
protected function getItemsSettings(): array
{
if (empty($this->itemsSettings)) {
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select([
'*',
sprintf(
'IF (IFNULL(settings_hash, %1$s) = %1$s, uid, CONCAT(uid, %2$s, settings_hash)) AS %3$s',
$db->quote(''),
$db->quote(':'),
$db->quoteName('key')
)
])
->from('#__osmap_items_settings')
->where('sitemap_id = ' . $db->quote($this->sitemap->id))
->where($db->quoteName('format') . ' = 2');
$this->itemsSettings = $db->setQuery($query)->loadAssocList('key');
}
return $this->itemsSettings;
}
/**
* Gets the item custom settings if set. If not set, returns false.
*
* @param string $key
*
* @return ?array[]
*/
public function getItemCustomSettings(string $key): ?array
{
if (isset($this->itemsSettings[$key])) {
return $this->itemsSettings[$key];
}
return null;
}
/**
* This method gets the legacy settings for all items to be loaded avoiding
* lost the custom settings for items after the migration to v4.2.1.
*
* @return array
*/
protected function getLegacyItemsSettings(): array
{
if ($this->legacyItemsSettings === null) {
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('*')
->from('#__osmap_items_settings')
->where('sitemap_id = ' . $db->quote($this->sitemap->id))
->where($db->quoteName('format') . ' IS NULL');
$this->legacyItemsSettings = $db->setQuery($query)->loadAssocList('uid');
}
return $this->legacyItemsSettings;
}
/**
* Returns the settings based on the UID only. Used when we have legacy
* settings on the database.
*
* @param string $uid
*
* @return ?array[]
*/
protected function getLegacyItemCustomSettings(string $uid): ?array
{
if (isset($this->legacyItemsSettings[$uid])) {
return $this->legacyItemsSettings[$uid];
}
return null;
}
/**
* Sets the item's custom settings if exists. If no custom settings are
* found and is a menu item, use the menu's settings. If is s subitem
* (from plugins), we consider it already set the respective settings. But
* if there is a custom setting for the item, we use that overriding what
* was set in the plugin.
*
* @param Item $item
*
* @return void
*/
public function setItemCustomSettings(Item $item)
{
// Check if the menu item has custom settings. If not, use the values from the menu
// Check if there is a custom settings specific for this URL. Sometimes the same page has different URLs.
// We can have different settings for items with the same UID, but different URLs
$key = $item->uid . ':' . $item->settingsHash;
$settings = $this->getItemCustomSettings($key);
// Check if there is a custom settings for all links with that UID (happens right after a migration from
// versions before 4.0.0 or before 4.2.1)
if (empty($settings)) {
$settings = $this->getLegacyItemCustomSettings($item->uid);
// The Joomla plugin changed the UID
// from joomla.archive => joomla.archive.[id] and joomla.featured => joomla.featured[id]
// So we need to try getting the settings from the old UID
if ($settings === null) {
if (preg_match('/^joomla.(archive|featured)/', $item->uid, $matches)) {
$settings = $this->getLegacyItemCustomSettings('joomla.' . $matches[1]);
}
}
}
if (empty($settings)) {
// No custom settings, so let's use the menu's settings
if ($item->isMenuItem) {
$item->changefreq = $this->tmpItemDefaultSettings['changefreq'];
$item->priority = $this->tmpItemDefaultSettings['priority'];
}
} else {
// Apply the custom settings
$item->changefreq = $settings['changefreq'] ?? 'weekly';
$item->priority = (float)$settings['priority'] ?? .5;
$item->published = (bool)$settings['published'] ?? true;
}
}
/**
* Check if the parent is unpublished or ignored and makes sure to ignore any item on it's sublevel
*
* @param Item $item
*
* @return void
*/
protected function checkParentIsUnpublished(Item $item)
{
// Check if this item belongs to a sub-level which needs to be unpublished
if ($this->unpublishLevel !== false && $item->level > $this->unpublishLevel) {
$item->set('published', false);
$item->addAdminNote('COM_OSMAP_ADMIN_NOTE_PARENT_UNPUBLISHED');
}
// If the item is unpublished and the 'ignore' level is false, mark the level to ignore sub-items
$displayable = $item->published
&& !$item->ignore
&& (!$item->duplicate || !$this->params->get('ignore_duplicated_uids', 1));
if (!$displayable && $this->unpublishLevel === false) {
$this->unpublishLevel = $item->level;
}
// If the item won't be ignored, make sure to reset the 'ignore' level
if (
$item->published
&& !$item->ignore
&& (!$item->duplicate || !$this->params->get('ignore_duplicated_uids', 1))
) {
$this->unpublishLevel = false;
}
}
/**
* Returns the current menu item id
*
* @return int
*/
public function getCurrentMenuItemId(): int
{
return (int)$this->currentMenuItemId;
}
/**
* Removes circular reference
*/
public function cleanup()
{
$this->sitemap = null;
$this->printNodeCallback = null;
$this->params = null;
$this->currentMenu = null;
}
}

View File

@ -0,0 +1,37 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\Sitemap;
use Alledia\OSMap;
defined('_JEXEC') or die();
class Images extends Standard
{
/**
* @var string
*/
protected $type = 'images';
}

View File

@ -0,0 +1,609 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\Sitemap;
use Alledia\OSMap\Factory;
use Alledia\OSMap\Helper\General;
use Joomla\CMS\Date\Date;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Object\CMSObject;
use Joomla\Registry\Registry;
defined('_JEXEC') or die();
/**
* Sitemap item
*/
class Item extends CMSObject
{
/**
* @var int;
*/
public $id = null;
/**
* @var string;
*/
public $uid = null;
/**
* Item's link, which can be relative or un-routed
*
* @var string
*/
public $link = null;
/**
* Routed full link, sanitized and without any hash segment
*
* @var string
*/
public $fullLink = null;
/**
* Routed full link, sanitized but can contain a hash segment
*
* @var string
*/
public $rawLink = null;
/**
* @var Registry
*/
public $params = null;
/**
* @var string
*/
public $priority = null;
/**
* @var string
*/
public $changefreq = null;
/**
* @var string
*/
public $created = null;
/**
* @var string
*/
public $modified = null;
/**
* @var string
*/
public $publishUp = null;
/**
* The component associated to the option URL param
*
* @var string
*/
public $component = null;
/**
* @var bool
*/
public $ignore = false;
/**
* @var bool
*/
public $duplicate = false;
/**
* @var int
*/
public $browserNav = null;
/**
* @var bool
*/
public $isInternal = true;
/**
* @var bool
*/
public $home = false;
/**
* @var string
*/
public $type = null;
/**
* @var bool
*/
public $expandible = false;
/**
* @var bool
*/
public $secure = false;
/**
* @var int
*/
public $isMenuItem = 0;
/**
* @var bool
*/
public $published = 1;
/**
* @var string
*/
public $name = '';
/**
* @var array
*/
public $images = [];
/**
* @var string
*/
public $settingsHash = null;
/**
* @var int
*/
public $level = null;
/**
* @var string
*/
public $adapterName = 'Generic';
/**
* @var object
*/
public $adapter = null;
/**
* If true, says the item is visible for robots
*
* @var bool
*/
public $visibleForRobots = true;
/**
* If true, says the item's parent is visible for robots
*
* @var bool
*/
public $parentIsVisibleForRobots = true;
/**
* Stores a list of notes generated by the collector to display in the admin
*
* @var string[]
*/
public $adminNotes = null;
/**
* @var bool
*/
public $visibleForXML = true;
/**
* @var bool
*/
public $visibleForHTML = true;
/**
* @var int
*/
public $menuItemId = 0;
/**
* @var string
*/
public $menuItemName = null;
/**
* @var string
*/
public $menuItemType = null;
/**
* @var array
*/
public $subnodes = null;
/**
* @var string
*/
public $slug = null;
/**
* @inheritDoc
*/
public function __construct($itemData, $currentMenuItemId)
{
parent::__construct($itemData);
if (class_exists('\\Alledia\\OSMap\\Sitemap\\ItemAdapter\\GenericPro')) {
$this->adapterName = 'GenericPro';
}
$this->published = (bool)$this->published;
$this->isMenuItem = (bool)$this->isMenuItem;
$this->params = new Registry($this->params);
// Check if the link is an internal link
$this->isInternal = $this->checkLinkIsInternal();
$this->prepareDate('created');
$this->prepareDate('modified');
$defaultDate = is_null($this->created) ? $this->modified : $this->created;
$this->prepareDate('publishUp', $defaultDate);
$this->setLink();
$this->extractComponentFromLink();
$this->setFullLink();
// Sanitize internal links
if ($this->isInternal) {
$this->sanitizeFullLink();
}
$this->rawLink = $this->fullLink;
// Removes the hash segment from the Full link, if exists
$container = Factory::getPimpleContainer();
$this->fullLink = $container->router->removeHashFromURL($this->fullLink);
// Make sure to have a unique hash for the settings
$this->settingsHash = $container->router->createUrlHash($this->fullLink . $currentMenuItemId);
/*
* Do not use a "prepare" method because we need to make sure it will
* be calculated after the link is set.
*/
$this->calculateUID();
}
/**
* Adds the note to the admin note attribute and initialize the variable
* if needed
*
* @param string $note
*
* @return void
*/
public function addAdminNote(string $note)
{
if (!is_array($this->adminNotes)) {
$this->adminNotes = [];
}
$this->adminNotes[] = Text::_($note);
}
/**
* @return bool
*/
protected function checkLinkIsInternal(): bool
{
$container = Factory::getPimpleContainer();
return $container->router->isInternalURL($this->link)
|| in_array(
$this->type,
[
'separator',
'heading',
]
);
}
/**
* Set the correct date for the attribute
*
* @param string $attributeName
* @param ?string $default
*
* @return void
*/
public function prepareDate(string $attributeName, ?string $default = null)
{
if ($default !== null && empty($this->{$attributeName})) {
$this->{$attributeName} = $default;
}
if (General::isEmptyDate($this->{$attributeName})) {
$this->{$attributeName} = null;
}
if (!General::isEmptyDate($this->{$attributeName})) {
if (!is_numeric($this->{$attributeName})) {
$date = new Date($this->{$attributeName});
$this->{$attributeName} = $date->toUnix();
}
// Convert dates from UTC
if (intval($this->{$attributeName})) {
if ($this->{$attributeName} < 0) {
$this->{$attributeName} = null;
} else {
$date = new Date($this->{$attributeName});
$this->{$attributeName} = $date->toISO8601();
}
}
}
}
/**
* @return bool
*/
public function hasCompatibleLanguage(): bool
{
// Check the language
if (Multilanguage::isEnabled() && isset($this->language)) {
if ($this->language === '*' || $this->language === Factory::getLanguage()->getTag()) {
return true;
}
return false;
}
return true;
}
/**
* Calculate a hash based on the link, to avoid duplicated links. It will
* set the new UID to the item.
*
* @param bool $force
* @param string $prefix
*
* @return void
*/
public function calculateUID(bool $force = false, string $prefix = '')
{
if (empty($this->uid) || $force) {
$this->uid = $prefix . md5($this->fullLink);
}
}
/**
* Set the link in special cases, like alias, where the link doesn't have
* the correct related menu id.
*
* @return void
*/
protected function setLink()
{
// If is an alias, use the Itemid stored in the parameters to get the correct url
if ($this->type === 'alias') {
// Get the related menu item's link
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('link')
->from('#__menu')
->where('id = ' . $db->quote($this->params->get('aliasoptions')));
$this->link = $db->setQuery($query)->loadResult();
}
}
/**
* Sanitize the link removing double slashes and trailing slash
*
* @return void
*/
protected function sanitizeFullLink()
{
$container = Factory::getPimpleContainer();
$this->fullLink = $container->router->sanitizeURL($this->fullLink);
}
/**
* Converts the current link to a full URL, including the base URI.
* If the item is the home menu, will return the base URI. If internal,
* will return a routed full URL (SEF, if enabled). If it is an external
* URL, won't change the link.
*
* @return void
*/
protected function setFullLink()
{
$container = Factory::getPimpleContainer();
if ($this->home) {
// Correct the URL for the home page.
// Check if multi-language is enabled to use the proper route
if (Multilanguage::isEnabled()) {
$lang = Factory::getLanguage();
$tag = $lang->getTag();
$lang = null;
$homes = Multilanguage::getSiteHomePages();
$home = $homes[$tag] ?? $homes['*'];
$this->fullLink = $container->router->routeURL('index.php?Itemid=' . $home->id, true);
} else {
$this->fullLink = $container->uri::root();
}
$this->fullLink = $container->router->sanitizeURL($this->fullLink);
return;
}
if ($this->type === 'separator' || $this->type === 'heading') {
// Not a url so disable browser nav
$this->browserNav = 3;
$this->uid = $this->type . '.' . md5($this->name . $this->id);
return;
}
if ($this->type === 'url') {
$this->link = trim($this->link);
// Check if it is a single Hash char, the user doesn't want to point to any URL
if ($this->link === '#' || empty($this->link)) {
$this->fullLink = '';
$this->visibleForXML = false;
return;
}
// Check if it is a relative URI
if ($container->router->isRelativeUri($this->link)) {
$this->fullLink = $container->router->sanitizeURL(
$container->router->convertRelativeUriToFullUri($this->link)
);
return;
}
$this->fullLink = $this->link;
if (!$this->isInternal) {
// External URLS have UID as a hash as part of its url
$this->calculateUID(true, 'external.');
return;
}
}
if ($this->type === 'alias') {
// Use the destination itemid, instead of the alias' item id.
// This will make sure we have the correct routed url.
$this->fullLink = 'index.php?Itemid=' . $this->params->get('aliasoptions');
}
// If is a menu item but not an alias, force to use the current menu's item id
if ($this->isMenuItem && $this->type !== 'alias' && $this->type !== 'url') {
$this->fullLink = 'index.php?Itemid=' . $this->id;
}
// If is not a menu item, use as base for the fullLink, the item link
if (!$this->isMenuItem) {
$this->fullLink = $this->link;
}
if ($this->isInternal) {
$this->fullLink = $container->router->routeURL($this->fullLink, true);
}
$this->fullLink = $container->router->sanitizeURL($this->fullLink);
}
/**
* Set the item adapter according to the type of content. The adapter can
* extract custom params from the item like params and metadata.
*
* @return void
*/
public function setAdapter()
{
// Check if there is class for the option
$adapterClass = '\\Alledia\\OSMap\\Sitemap\\ItemAdapter\\' . $this->adapterName;
if (class_exists($adapterClass)) {
$this->adapter = new $adapterClass($this);
} else {
$this->adapter = new ItemAdapter\Generic($this);
}
}
public function cleanup()
{
$this->adapter->cleanup();
$this->adapter = null;
$this->params = null;
}
/**
* Extract the option from the link, to identify the component called by
* the link.
*
* @return void
*/
protected function extractComponentFromLink()
{
$this->component = null;
if (preg_match('#^/?index.php.*option=(com_[^&]+)#', $this->link, $matches)) {
$this->component = $matches[1];
}
}
/**
* Returns the admin notes as a string.
*
* @return string
*/
public function getAdminNotesString(): string
{
if (is_array($this->adminNotes)) {
return implode("\n", $this->adminNotes);
}
return '';
}
/**
* Set the correct modification date.
*
* @return void
*/
public function setModificationDate()
{
if (General::isEmptyDate($this->modified)) {
$this->modified = null;
}
if (!General::isEmptyDate($this->modified)) {
if (!is_numeric($this->modified)) {
$date = new Date($this->modified);
$this->modified = $date->toUnix();
}
// Convert dates from UTC
if (intval($this->modified)) {
if ($this->modified < 0) {
$this->modified = null;
} else {
$date = new \JDate($this->modified);
$this->modified = $date->toISO8601();
}
}
}
}
}

View File

@ -0,0 +1,38 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\Sitemap\ItemAdapter;
defined('_JEXEC') or die();
interface AdapterInterface
{
/**
* Gets the visible state for robots. Each adapter will check specific params. Returns
* true if the item is visible.
*
* @return bool
*/
public function checkVisibilityForRobots();
}

View File

@ -0,0 +1,56 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\Sitemap\ItemAdapter;
use Alledia\OSMap\Sitemap\Item;
defined('_JEXEC') or die();
class Generic implements AdapterInterface
{
/**
* @var Item
*/
protected $item;
/**
* @param Item $item
*
* @return void
*/
public function __construct(Item $item)
{
$this->item = $item;
}
/**
* @inheritDoc
*/
public function checkVisibilityForRobots()
{
// Always visible for the Free version
return true;
}
}

View File

@ -0,0 +1,37 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\Sitemap;
use Alledia\OSMap;
defined('_JEXEC') or die();
class News extends Standard
{
/**
* @var string
*/
protected $type = 'news';
}

View File

@ -0,0 +1,43 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\Sitemap;
defined('_JEXEC') or die();
interface SitemapInterface
{
/**
* Traverse the sitemap items recursively and call the given callback,
* passing each node as parameter.
*
* @param callable $callback
* @param bool $triggerEvents
* @param bool $updateCount
*
* @return void
* @throws \Exception
*/
public function traverse(callable $callback, bool $triggerEvents = true, bool $updateCount = false);
}

View File

@ -0,0 +1,184 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\Sitemap;
use Alledia\OSMap\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Registry\Registry;
defined('_JEXEC') or die();
class Standard implements SitemapInterface
{
/**
* @var int
*/
public $id;
/**
* @var string
*/
public $name;
/**
* @var Registry
*/
public $params;
/**
* @var bool
*/
public $isDefault = false;
/**
* @var bool
*/
public $isPublished = true;
/**
* @var string
*/
public $createdOn;
/**
* @var int
*/
public $linksCount = 0;
/**
* @var string
*/
protected $type = 'standard';
/**
* @var Collector
*/
protected $collector;
/**
* Limit in days for news sitemap items
*
* @var int
*/
public $newsDateLimit = 2;
/**
* The constructor
*
* @param int $id
*
* @return void
* @throws \Exception
*/
public function __construct(int $id)
{
/** @var \OSMapTableSitemap $sitemap */
$sitemap = Factory::getTable('Sitemap');
$sitemap->load($id);
if (empty($sitemap) || !$sitemap->id) {
throw new \Exception(Text::_('COM_OSMAP_SITEMAP_NOT_FOUND'), 404);
}
$this->id = $sitemap->id;
$this->name = $sitemap->name;
$this->isDefault = $sitemap->is_default == 1;
$this->isPublished = $sitemap->published == 1;
$this->createdOn = $sitemap->created_on;
$this->linksCount = (int)$sitemap->links_count;
$this->params = new Registry($sitemap->params);
$this->initCollector();
}
/**
* Method to initialize the items collector
*
* @return void
*/
protected function initCollector()
{
$this->collector = new Collector($this);
}
/**
* @inheritDoc
*/
public function traverse(callable $callback, $triggerEvents = true, $updateCount = false)
{
if ($triggerEvents) {
// Call the plugins, allowing to interact or override the collector
PluginHelper::importPlugin('osmap');
$eventParams = [$this, $callback];
$results = Factory::getApplication()->triggerEvent('osmapOnBeforeCollectItems', $eventParams);
// A plugin asked to stop the traverse
if (in_array(true, $results)) {
return;
}
$results = null;
}
// Fetch the sitemap items
$count = $this->collector->fetch($callback);
if ($updateCount) {
// Update the links count in the sitemap
$this->updateLinksCount($count);
}
// Cleanup
$this->collector->cleanup();
$this->collector = null;
}
/**
* Updates the count of links in the database
*
* @param int $count
*
* @return void
*/
protected function updateLinksCount(int $count)
{
$db = Factory::getDbo();
$updateObject = (object)[
'id' => $this->id,
'links_count' => $count
];
$db->updateObject('#__osmap_sitemaps', $updateObject, ['id']);
}
public function cleanup()
{
$this->collector = null;
$this->params = null;
}
}

View File

@ -0,0 +1,98 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2021-2024 Joomlashack.com. All rights reserved
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\View\Admin;
use Alledia\OSMap\Controller\Form;
use Alledia\OSMap\View\TraitOSMapView;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
defined('_JEXEC') or die();
class AbstractForm extends \Alledia\Framework\Joomla\View\Admin\AbstractForm
{
use TraitOSMapView;
/**
* Render a form fieldset with the ability to compact two fields
* into a single line
*
* @param string $fieldSet
* @param array $sameLine
* @param bool $tabbed
*
* @return string
*/
protected function renderFieldset(string $fieldSet, array $sameLine = [], ?bool $tabbed = false): string
{
$html = [];
if ($this->form && $this->form instanceof Form) {
$fieldSets = $this->form->getFieldsets();
if ($fieldSets[$fieldSet]) {
$name = $fieldSets[$fieldSet]->name;
$label = $fieldSets[$fieldSet]->label;
$html = [];
if ($tabbed) {
$html[] = HTMLHelper::_('bootstrap.addTab', 'myTab', $name, Text::_($label));
}
$html[] = '<div class="row-fluid">';
$html[] = '<fieldset class="adminform">';
foreach ($this->form->getFieldset($name) as $field) {
if (in_array($field->fieldname, $sameLine)) {
continue;
}
$fieldHtml = [
'<div class="control-group">',
'<div class="control-label">',
$field->label,
'</div>',
'<div class="controls">',
$field->input
];
$html = array_merge($html, $fieldHtml);
if (isset($sameLine[$field->fieldname])) {
$html[] = ' ' . $this->form->getField($sameLine[$field->fieldname])->input;
}
$html[] = '</div>';
$html[] = '</div>';
}
$html[] = '</fieldset>';
$html[] = '</div>';
if ($tabbed) {
$html[] = HTMLHelper::_('bootstrap.endTab');
}
}
}
return join('', $html);
}
}

View File

@ -0,0 +1,40 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\View\Admin;
use Alledia\OSMap\Controller\Form;
use Alledia\OSMap\Factory;
use Alledia\OSMap\View\TraitOSMapView;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Toolbar\ToolbarHelper;
defined('_JEXEC') or die();
class AbstractList extends \Alledia\Framework\Joomla\View\Admin\AbstractList
{
use TraitOSMapView;
}

View File

@ -0,0 +1,375 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\View\Site;
use Alledia\OSMap\Factory;
use Alledia\OSMap\Sitemap\Standard;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Language\Text;
use Joomla\Registry\Registry;
defined('_JEXEC') or die();
class AbstractList extends \Alledia\Framework\Joomla\View\Site\AbstractList
{
/**
* @var Standard
*/
protected $sitemap = null;
/**
* @var Registry
*/
protected $osmapParams = null;
/**
* @var bool
*/
protected $debug = false;
/**
* @var int
*/
protected $showExternalLinks = 0;
/**
* @var int
*/
protected $showMenuTitles = 1;
/**
* @var int
*/
public $generalCounter = 0;
/**
* List of found items to render the sitemap
*
* @var array
*/
protected $menus = [];
/**
* A list of last items per level. Used to identify the parent items
*
* @var array
*/
protected $lastItemsPerLevel = [];
/**
* @inheritDoc
* @throws \Exception
*/
public function __construct($config = [])
{
parent::__construct($config);
$this->params = Factory::getApplication()->getParams();
$this->debug = (bool)$this->params->get('debug', 0);
$this->osmapParams = ComponentHelper::getParams('com_osmap');
$this->showExternalLinks = (int)$this->osmapParams->get('show_external_links', 0);
$this->showMenuTitles = (int)$this->params->get('show_menu_titles', 1);
}
/**
* The display method
*
* @param string $tpl
*
* @return void
* @throws \Exception
*/
public function display($tpl = null)
{
$container = Factory::getPimpleContainer();
$id = $container->input->getInt('id');
$this->osmapParams = ComponentHelper::getParams('com_osmap');
$this->sitemap = Factory::getSitemap($id);
if (!$this->sitemap->isPublished) {
throw new \Exception(Text::_('COM_OSMAP_MSG_SITEMAP_IS_UNPUBLISHED'), 404);
}
$app = Factory::getApplication();
if ($title = $this->params->def('page_title')) {
if ($app->get('sitename_pagetitles', 0) == 1) {
$title = Text::sprintf('JPAGETITLE', $app->get('sitename'), $title);
} elseif ($app->get('sitename_pagetitles', 0) == 2) {
$title = Text::sprintf('JPAGETITLE', $title, $app->get('sitename'));
}
$this->document->setTitle($title);
}
if ($description = $this->params->get('menu-meta_description')) {
$this->document->setDescription($description);
}
if ($keywords = $this->params->get('menu-meta_keywords')) {
$this->document->setMetaData('keywords', $keywords);
}
if ($robots = $this->params->get('robots')) {
$this->document->setMetaData('robots', $robots);
}
parent::display($tpl);
}
/**
* The callback called to print each node. Returns true if it was
* able to print. False, if not.
*
* @param object $node
*
* @return bool
*/
public function registerNodeIntoList(object $node): bool
{
$ignoreDuplicatedUIDs = (int)$this->osmapParams->get('ignore_duplicated_uids', 1);
$display = !$node->ignore
&& $node->published
&& (!$node->duplicate || !$ignoreDuplicatedUIDs)
&& $node->visibleForHTML;
if ($display && !$node->isInternal) {
// Show external links if so configured
$display = $this->showExternalLinks > 0;
}
if (!$node->hasCompatibleLanguage()) {
$display = false;
}
if (!$display) {
return false;
}
// Check if the menu was already registered and register if needed
if ($node->level === 0 && !isset($this->menus[$node->menuItemType])) {
$queueItem = (object)[
'menuItemId' => $node->menuItemId,
'menuItemTitle' => $node->menuItemTitle,
'menuItemType' => $node->menuItemType,
'level' => -1,
'children' => []
];
// Add the menu to the main list of items
$this->menus[$node->menuItemType] = $queueItem;
// Add this menu as the last one on the list of menus
$this->lastItemsPerLevel[-1] = $queueItem;
}
// Instantiate the current item
$queueItem = (object)[];
$queueItem->rawLink = $node->rawLink;
$queueItem->type = $node->type;
$queueItem->level = $node->level;
$queueItem->name = $node->name;
$queueItem->uid = $node->uid;
$queueItem->children = [];
// Add debug information, if debug is enabled
if ($this->debug) {
$queueItem->fullLink = $node->fullLink;
$queueItem->link = $node->link;
$queueItem->modified = $node->modified;
$queueItem->duplicate = $node->duplicate;
$queueItem->visibleForRobots = $node->visibleForRobots;
$queueItem->adapter = get_class($node->adapter);
$queueItem->menuItemType = $node->menuItemType;
}
// Add this item to its parent children list
$this->lastItemsPerLevel[$queueItem->level - 1]->children[] = $queueItem;
// Add this item as the last one on the level
$this->lastItemsPerLevel[$queueItem->level] = $queueItem;
unset($node);
return true;
}
/**
* Print debug info for a note
*
* @param object $node
*
* @return void
*/
public function printDebugInfo($node)
{
$debugRow = '<div><span>%s:</span>&nbsp;%s</div>';
echo '<div class="osmap-debug-box">'
. sprintf('<div><span>#:</span>&nbsp;%s</div>', $this->generalCounter)
. sprintf($debugRow, Text::_('COM_OSMAP_UID'), $node->uid)
. sprintf($debugRow, Text::_('COM_OSMAP_FULL_LINK'), htmlspecialchars($node->fullLink))
. sprintf($debugRow, Text::_('COM_OSMAP_RAW_LINK'), htmlspecialchars($node->rawLink))
. sprintf($debugRow, Text::_('COM_OSMAP_LINK'), htmlspecialchars($node->link))
. sprintf($debugRow, Text::_('COM_OSMAP_MODIFIED'), htmlspecialchars($node->modified))
. sprintf($debugRow, Text::_('COM_OSMAP_LEVEL'), $node->level)
. sprintf($debugRow, Text::_('COM_OSMAP_DUPLICATE'), Text::_($node->duplicate ? 'JYES' : 'JNO'))
. sprintf(
$debugRow,
Text::_('COM_OSMAP_VISIBLE_FOR_ROBOTS'),
Text::_($node->visibleForRobots ? 'JYES' : 'JNO')
)
. sprintf($debugRow, Text::_('COM_OSMAP_ADAPTER_CLASS'), $node->adapter);
if (method_exists($node, 'getAdminNotesString')) {
if ($adminNotes = $node->getAdminNotesString()) {
echo sprintf($debugRow, Text::_('COM_OSMAP_ADMIN_NOTES'), nl2br($adminNotes));
}
}
echo '</div>';
}
/**
* Print an item
*
* @param object $item
*
* @return void
*/
public function printItem(object $item)
{
$this->generalCounter++;
$liClass = $this->debug ? 'osmap-debug-item' : '';
$liClass .= $this->generalCounter % 2 == 0 ? ' even' : '';
if (empty($item->children) == false) {
$liClass .= ' osmap-has-children';
}
$sanitizedUID = ApplicationHelper::stringURLSafe($item->uid);
echo sprintf('<li class="%s" id="osmap-li-uid-%s">', $liClass, $sanitizedUID);
// Some items are just separator, without a link. Do not print as link then
if (trim($item->rawLink ?? '') === '') {
$type = $item->type ?? 'separator';
echo sprintf('<span class="osmap-item-%s">%s</span>', $type, htmlspecialchars($item->name));
} else {
echo sprintf(
'<a href="%s" target="_self" class="osmap-link">%s</a>',
$item->rawLink,
htmlspecialchars($item->name)
);
}
if ($this->debug) {
$this->printDebugInfo($item);
}
if (empty($item->children) == false) {
$this->printMenu($item);
}
echo '</li>';
}
/**
* Renders html sitemap
*
* @return void
*/
public function renderSitemap()
{
if (!empty($this->menus)) {
$columns = max((int)$this->params->get('columns', 1), 1);
foreach ($this->menus as $menuType => $menu) {
if (
isset($menu->menuItemTitle)
&& $this->showMenuTitles
&& empty($menu->children) == false
) {
if ($this->debug) {
$debug = sprintf(
'<div><span>%s:</span>&nbsp;%s: %s</div>',
Text::_('COM_OSMAP_MENUTYPE'),
$menu->menuItemId,
$menu->menuItemType
);
}
echo sprintf(
'<h2 id="osmap-menu-uid-%s">%s%s</h2>',
ApplicationHelper::stringURLSafe($menu->menuItemType),
$menu->menuItemTitle,
empty($debug) ? '' : $debug
);
}
$this->printMenu($menu, $columns);
}
}
}
/**
* Render the menu item and its children items
*
* @param object $menu
* @param ?int $columns
*
* @return void
*/
protected function printMenu(object $menu, ?int $columns = null)
{
if (isset($menu->menuItemType)) {
$sanitizedUID = ApplicationHelper::stringURLSafe($menu->menuItemType);
} else {
$sanitizedUID = ApplicationHelper::stringURLSafe($menu->uid);
}
$class = ['level_' . ($menu->level + 1)];
if ($columns && $columns > 1) {
$class[] = 'columns_' . $columns;
}
echo sprintf(
'<ul class="%s" id="osmap-ul-uid-%s">',
join(' ', $class),
$sanitizedUID
);
foreach ($menu->children as $item) {
$this->printItem($item);
}
echo '</ul>';
}
}

View File

@ -0,0 +1,76 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2021-2024 Joomlashack.com. All rights reserved
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\OSMap\View;
use Alledia\OSMap\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Toolbar\ToolbarHelper;
defined('_JEXEC') or die();
trait TraitOSMapView
{
/**
* Default admin screen title
*
* @param ?string $sub
* @param string $icon
*
* @return void
*/
protected function setTitle(?string $sub = null, string $icon = 'osmap')
{
$title = Text::_('COM_OSMAP');
if ($sub) {
$title .= ': ' . Text::_($sub);
}
ToolbarHelper::title($title, $icon);
}
/**
* Render the admin screen toolbar buttons
*
* @return void
* @throws \Exception
*/
protected function setToolBar()
{
$user = Factory::getUser();
if ($user->authorise('core.admin', 'com_osmap')) {
ToolbarHelper::preferences('com_osmap');
}
PluginHelper::importPlugin('osmap');
Factory::getApplication()->triggerEvent(
'osmapOnAfterSetToolBar',
[
strtolower(str_replace('OSMapView', '', $this->getName()))
]
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,221 @@
<?php
/**
* @package ShackInstaller
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2016-2023 Joomlashack.com. All rights reserved
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of ShackInstaller.
*
* ShackInstaller is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* ShackInstaller is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ShackInstaller. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\Installer;
defined('_JEXEC') or die();
class AutoLoader
{
/**
* Associative array where the key is a namespace prefix and the value
* is an array of base directories for classes in that namespace.
*
* @var array
*/
protected static $prefixes = [];
/**
* Associative array of prefixes for loading specialized camelCase classes
* where Uppercase letters in the class name indicate directory structure
*
* @var array
*/
protected static $camelPrefixes = [];
/**
* @var AutoLoader
*/
protected static $instance = null;
/**
* @param string $method
*/
protected static function registerLoader($method)
{
if (static::$instance === null) {
static::$instance = new static();
}
spl_autoload_register([static::$instance, $method]);
}
/**
* Register a psr4 namespace
*
* @param string $prefix The namespace prefix.
* @param string $baseDir A base directory for class files in the
* namespace.
* @param ?bool $prepend If true, prepend the base directory to the stack
* instead of appending it; this causes it to be searched first rather
* than last.
*
* @return void
*/
public static function register(string $prefix, string $baseDir, ?bool $prepend = false)
{
if (count(self::$prefixes) == 0) {
static::registerLoader('loadClass');
}
// normalize namespace prefix
$prefix = trim($prefix, '\\') . '\\';
// normalize the base directory with a trailing separator
$baseDir = rtrim($baseDir, '\\/') . '/';
// initialise the namespace prefix array
if (empty(self::$prefixes[$prefix])) {
self::$prefixes[$prefix] = [];
}
// retain the base directory for the namespace prefix
if ($prepend) {
array_unshift(self::$prefixes[$prefix], $baseDir);
} else {
array_push(self::$prefixes[$prefix], $baseDir);
}
}
/**
* Loads the class file for a given class name.
*
* @param string $class The fully-qualified class name.
*
* @return ?string The mapped file name on success
*/
protected function loadClass(string $class): ?string
{
$prefixes = explode('\\', $class);
$className = '';
while ($prefixes) {
$className = array_pop($prefixes) . $className;
$prefix = join('\\', $prefixes) . '\\';
if ($filePath = $this->loadMappedFile($prefix, $className)) {
return $filePath;
}
$className = '\\' . $className;
}
return null;
}
/**
* Load the mapped file for a namespace prefix and class.
*
* @param string $prefix The namespace prefix.
* @param string $className The relative class name.
*
* @return ?string path that was loaded
*/
protected function loadMappedFile(string $prefix, string $className): ?string
{
// are there any base directories for this namespace prefix?
if (isset(self::$prefixes[$prefix]) === false) {
return null;
}
// look through base directories for this namespace prefix
foreach (self::$prefixes[$prefix] as $baseDir) {
$path = $baseDir . str_replace('\\', '/', $className) . '.php';
if (is_file($path)) {
require_once $path;
return $path;
}
}
return null;
}
/**
* Register a base directory for classes organized using camelCase.
* Class names beginning with the prefix will be automatically loaded
* if there is a matching file in the directory tree starting with $baseDir.
* File names and directory names are all expected to be lower case.
*
* Example:
*
* $prefix = 'Simplerenew'
* $baseDir = '/library/joomla'
*
* A class name of: SimplerenewViewAdmin
* Would be in : /library/joomla/view/admin.php
*
* This system is intended for situations where full name spacing is either
* unavailable or impractical due to integration with other systems.
*
* @param string $prefix
* @param string $baseDir
*
* @return void
* @throws \Exception
*/
public static function registerCamelBase(string $prefix, string $baseDir)
{
if (!is_dir($baseDir)) {
throw new \Exception("Cannot register '{$prefix}'. The requested base directory does not exist!'");
}
if (count(self::$camelPrefixes) == 0) {
// Register function on first call
static::registerLoader('loadCamelClass');
}
if (empty(self::$camelPrefixes[$prefix])) {
self::$camelPrefixes[$prefix] = $baseDir;
}
}
/**
* Autoload a class using the camelCase structure
*
* @param string $class
*
* @return string
*/
protected function loadCamelClass(string $class): ?string
{
if (!class_exists($class)) {
foreach (self::$camelPrefixes as $prefix => $baseDir) {
if (strpos($class, $prefix) === 0) {
$parts = preg_split('/(?<=[a-z])(?=[A-Z])/x', substr($class, strlen($prefix)));
$file = strtolower(join('/', $parts));
$filePath = $baseDir . '/' . $file . '.php';
if (is_file($filePath)) {
require_once $filePath;
return $filePath;
}
}
}
}
return null;
}
}

View File

@ -0,0 +1,435 @@
<?php
/**
* @package ShackInstaller
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2016-2023 Joomlashack.com. All rights reserved
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of ShackInstaller.
*
* ShackInstaller is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* ShackInstaller is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ShackInstaller. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\Installer\Extension;
defined('_JEXEC') or die();
use Joomla\CMS\Factory;
use Joomla\Registry\Registry;
/**
* Generic extension class
*/
class Generic
{
/**
* The extension namespace
*
* @var string
*/
protected $namespace = null;
/**
* The extension type
*
* @var string
*/
protected $type = null;
/**
* The extension id
*
* @var int
*/
protected $id = null;
/**
* The extension name
*
* @var string
*/
protected $name = null;
/**
* The extension params
*
* @var Registry
*/
public $params = null;
/**
* The extension enable state
*
* @var bool
*/
protected $enabled = null;
/**
* The element of the extension
*
* @var string
*/
protected $element = null;
/**
* @var string
*/
protected $folder = null;
/**
* Base path
*
* @var string
*/
protected $basePath = null;
/**
* The manifest information
*
* @var \SimpleXMLElement
*/
public $manifest = null;
/**
* The config.xml information
*
* @var \SimpleXMLElement
*/
public $config = null;
/**
* Class constructor, set the extension type.
*
* @param string $namespace The element of the extension
* @param string $type The type of extension
* @param ?string $folder The folder for plugins (only)
* @param string $basePath
*
* @return void
*/
public function __construct(string $namespace, string $type, ?string $folder = '', string $basePath = JPATH_SITE)
{
$this->type = $type;
$this->element = strtolower($namespace);
$this->folder = $folder;
$this->basePath = $basePath;
$this->namespace = $namespace;
$this->getManifest();
$this->getDataFromDatabase();
}
/**
* Get information about this extension from the database
* NOTE: This is duplicated code from the corresponding class in
* \Alledia\Framework\Joomla\Extension\Generic
*/
protected function getDataFromDatabase()
{
$element = $this->getElementToDb();
// Load the extension info from database
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select([
$db->quoteName('extension_id'),
$db->quoteName('name'),
$db->quoteName('enabled'),
$db->quoteName('params')
])
->from('#__extensions')
->where($db->quoteName('type') . ' = ' . $db->quote($this->type))
->where($db->quoteName('element') . ' = ' . $db->quote($element));
if ($this->type === 'plugin') {
$query->where($db->quoteName('folder') . ' = ' . $db->quote($this->folder));
}
$db->setQuery($query);
$row = $db->loadObject();
if (is_object($row)) {
$this->id = $row->extension_id;
$this->name = $row->name;
$this->enabled = (bool)$row->enabled;
$this->params = new Registry($row->params);
} else {
$this->id = null;
$this->name = null;
$this->enabled = false;
$this->params = new Registry();
}
}
/**
* Check if the extension is enabled
*
* @return bool
*/
public function isEnabled(): bool
{
return (bool)$this->enabled;
}
/**
* Get the path for the extension
*
* @return string The path
*/
public function getExtensionPath(): string
{
$folders = [
'component' => 'administrator/components/',
'plugin' => 'plugins/',
'template' => 'templates/',
'library' => 'libraries/',
'cli' => 'cli/',
'module' => 'modules/'
];
$basePath = $this->basePath . '/' . $folders[$this->type];
switch ($this->type) {
case 'plugin':
$basePath .= $this->folder . '/';
break;
case 'module':
if (!preg_match('/^mod_/', $this->element)) {
$basePath .= 'mod_';
}
break;
case 'component':
if (!preg_match('/^com_/', $this->element)) {
$basePath .= 'com_';
}
break;
}
$basePath .= $this->element;
return $basePath;
}
/**
* Get the full element
*
* @return string
*/
public function getFullElement(): string
{
$prefixes = [
'component' => 'com',
'plugin' => 'plg',
'template' => 'tpl',
'library' => 'lib',
'cli' => 'cli',
'module' => 'mod'
];
$fullElement = $prefixes[$this->type];
if ($this->type === 'plugin') {
$fullElement .= '_' . $this->folder;
}
$fullElement .= '_' . $this->element;
return $fullElement;
}
/**
* Get the element to match the database records.
* Only components and modules have the prefix.
*
* @return string The element
*/
public function getElementToDb(): string
{
$prefixes = [
'component' => 'com_',
'module' => 'mod_'
];
$fullElement = '';
if (array_key_exists($this->type, $prefixes)) {
if (!preg_match('/^' . $prefixes[$this->type] . '/', $this->element)) {
$fullElement = $prefixes[$this->type];
}
}
$fullElement .= $this->element;
return $fullElement;
}
/**
* Get manifest path for this extension
*
* @return string
*/
public function getManifestPath(): string
{
$extensionPath = $this->getExtensionPath();
switch ($this->type) {
case 'template':
$fileName = 'templateDetails.xml';
break;
case 'library':
$fileName = $this->element . '.xml';
if (!is_file($extensionPath . '/' . $fileName)) {
$extensionPath = JPATH_MANIFESTS . '/libraries';
}
break;
case 'module':
$fileName = 'mod_' . $this->element . '.xml';
break;
case 'pkg':
$extensionPath = JPATH_MANIFESTS . '/packages';
$fileName = 'pkg_' . $this->element . '.xml';
break;
case 'file':
$extensionPath = JPATH_MANIFESTS . '/files';
$fileName = 'file_' . $this->element . '.xml';
break;
default:
$fileName = $this->element . '.xml';
break;
}
return $extensionPath . '/' . $fileName;
}
/**
* Get extension information
*
* @param bool $force If true, force to load the manifest, ignoring the cached one
*
* @return ?\SimpleXMLElement
*/
public function getManifest(bool $force = false): ?\SimpleXMLElement
{
if ($this->manifest === null || $force) {
$this->manifest = false;
$path = $this->getManifestPath();
if (is_file($path)) {
$this->manifest = simplexml_load_file($path);
}
}
return $this->manifest ?: null;
}
/**
* Get extension config file
*
* @param bool $force Force to reload the config file
*
* @return \SimpleXMLElement
*/
public function getConfig(bool $force = false)
{
if ($this->config === null || $force) {
$path = $this->getExtensionPath() . '/config.xml';
$this->config = is_file($path) ? simplexml_load_file($path) : false;
}
return $this->config;
}
/**
* Returns the update URL from database
*
* @return string
*/
public function getUpdateURL(): string
{
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('sites.location')
->from('#__update_sites AS sites')
->leftJoin('#__update_sites_extensions AS extensions ON (sites.update_site_id = extensions.update_site_id)')
->where('extensions.extension_id = ' . $this->id);
return $db->setQuery($query)->loadResult();
}
/**
* Set the update URL
*
* @param string $url
*/
public function setUpdateURL(string $url)
{
$db = Factory::getDbo();
// Get the update site id
$join = $db->quoteName('#__update_sites_extensions') . ' AS extensions '
. 'ON (sites.update_site_id = extensions.update_site_id)';
$query = $db->getQuery(true)
->select('sites.update_site_id')
->from($db->quoteName('#__update_sites') . ' AS sites')
->leftJoin($join)
->where('extensions.extension_id = ' . $this->id);
$db->setQuery($query);
$siteId = (int)$db->loadResult();
if (!empty($siteId)) {
$query = $db->getQuery(true)
->update($db->quoteName('#__update_sites'))
->set($db->quoteName('location') . ' = ' . $db->quote($url))
->where($db->quoteName('update_site_id') . ' = ' . $siteId);
$db->setQuery($query);
$db->execute();
}
}
/**
* Store the params on the database
*
* @return void
*/
public function storeParams()
{
$db = Factory::getDbo();
$params = $db->quote($this->params->toString());
$id = $db->quote($this->id);
$query = "UPDATE `#__extensions` SET params = {$params} WHERE extension_id = {$id}";
$db->setQuery($query);
$db->execute();
}
/**
* Get extension name
*
* @return string
*/
public function getName(): string
{
return $this->name;
}
}

View File

@ -0,0 +1,147 @@
<?php
/**
* @package ShackInstaller
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2016-2023 Joomlashack.com. All rights reserved
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of ShackInstaller.
*
* ShackInstaller is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* ShackInstaller is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ShackInstaller. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\Installer\Extension;
defined('_JEXEC') or die();
use Alledia\Installer\AutoLoader;
/**
* Licensed class, for extensions with Free and Pro versions
*/
class Licensed extends Generic
{
/**
* License type: free or pro
*
* @var string
*/
protected $license = null;
/**
* The path for the pro library
*
* @var string
*/
protected $proLibraryPath = null;
/**
* The path for the free library
*
* @var string
*/
protected $libraryPath = null;
/**
* @inheritDoc
*/
public function __construct(string $namespace, string $type, ?string $folder = '', string $basePath = JPATH_SITE)
{
parent::__construct($namespace, $type, $folder, $basePath);
$this->license = $this->manifest ? strtolower($this->manifest->alledia->license) : 'free';
$this->getLibraryPath();
$this->getProLibraryPath();
}
/**
* Check if the license is pro
*
* @return bool True for pro license
*/
public function isPro(): bool
{
return $this->license === 'pro';
}
/**
* Check if the license is free
*
* @return bool
*/
public function isFree(): bool
{
return !$this->isPro();
}
/**
* Get the path for the free library, based on the extension type
*
* @return string The path for pro
*/
public function getLibraryPath(): string
{
if (empty($this->libraryPath)) {
$basePath = $this->getExtensionPath();
$this->libraryPath = $basePath . '/library';
}
return $this->libraryPath;
}
/**
* Get path for the pro library, based on the extension type
*
* @return string The path for pro
*/
public function getProLibraryPath(): string
{
if ($this->proLibraryPath === null) {
$basePath = $this->getLibraryPath();
$this->proLibraryPath = $basePath . '/Pro';
}
return $this->proLibraryPath;
}
/**
* Loads the library, if existent (including the Pro Library)
*
* @return bool
* @throws \Exception
*/
public function loadLibrary(): bool
{
$libraryPath = $this->getLibraryPath();
// If we have a library path, lets load it
if (is_dir($libraryPath)) {
if ($this->isPro()) {
// Check if the pro library exists
if (!is_dir($this->getProLibraryPath())) {
throw new \Exception("Pro library not found: {$this->type}, {$this->element}");
}
}
AutoLoader::register('Alledia\\' . $this->namespace, $libraryPath);
return true;
}
return false;
}
}

View File

@ -0,0 +1,101 @@
<?php
/**
* @package ShackInstaller
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2016-2023 Joomlashack.com. All rights reserved
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of ShackInstaller.
*
* ShackInstaller is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* ShackInstaller is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ShackInstaller. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\Installer;
use Alledia\Framework\Factory;
use Joomla\CMS\Log\Log;
defined('_JEXEC') or die();
abstract class Loader
{
protected static $logRegistered = false;
/**
* Safely include a PHP file, making sure it exists before import.
*
* This method will register a log message and display a warning for admins
* in case the file is missed.
*
* @param string $path The file path you want to include
*
* @return bool
* @throws \Exception
*/
public static function includeFile(string $path): bool
{
if (!static::$logRegistered) {
Log::addLogger(
['text_file' => 'shackframework.loader.errors.php'],
Log::ALL,
['allediaframework']
);
static::$logRegistered = true;
}
// Check if the file doesn't exist
if (!is_file($path)) {
$logMsg = 'Required file is missed: ' . $path;
// Generate a backtrace to know from where the request cames
if (version_compare(phpversion(), '5.4', '<')) {
$backtrace = debug_backtrace();
} else {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
}
if (!empty($backtrace)) {
$logMsg .= sprintf(
' (%s:%s)',
$backtrace[0]['file'],
$backtrace[0]['line']
);
}
// Register the log
Log::add($logMsg, Log::ERROR, 'allediaframework');
// Warn admin users
$app = Factory::getApplication();
if ($app->getName() == 'administrator') {
$app->enqueueMessage(
'ShackInstaller Loader detected that a required file was not found! Please, check the logs.',
'error'
);
}
// Stand up a flag to warn a required file is missed
if (!defined('SHACK_INSTALLER_MISSED_FILE')) {
define('SHACK_INSTALLER_MISSED_FILE', true);
}
return false;
}
include_once $path;
return true;
}
}

View File

@ -0,0 +1,536 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\Installer\OSMap\Free;
use Alledia\Installer\OSMap\XmapConverter;
use Joomla\CMS\Application\SiteApplication;
use Joomla\CMS\Factory;
use Joomla\CMS\Filesystem\File;
use Joomla\CMS\Filesystem\Folder;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Table\Table;
// phpcs:disable PSR1.Files.SideEffects
defined('_JEXEC') or die();
// phpcs:enable PSR1.Files.SideEffects
class AbstractScript extends \Alledia\Installer\AbstractScript
{
/**
* @var bool
*/
protected $isXmapDataFound = false;
/**
* @inheritDoc
*/
protected function customPostFlight(string $type, InstallerAdapter $parent): void
{
if ($type == 'uninstall') {
return;
}
// Check if XMap is installed, to start a migration
$xmapConverter = new XmapConverter();
// This attribute will be used by the custom template to display the option to migrate legacy sitemaps
$this->isXmapDataFound = $this->findTable('#__xmap_sitemap') && $xmapConverter->checkXmapDataExists();
// If Xmap plugins are still available, and we don't have the OSMap plugins yet,
// save Xmap plugins params to re-apply after install OSMap plugins
$xmapConverter->saveXmapPluginParamsIfExists();
// Load Alledia Framework
require_once JPATH_ADMINISTRATOR . '/components/com_osmap/include.php';
switch ($type) {
case 'install':
case 'discover_install':
$this->createDefaultSitemap();
$link = HTMLHelper::_(
'link',
'index.php?option=com_plugins&view=plugins&filter.search=OSMap',
Text::_('COM_OSMAP_INSTALLER_PLUGINS_PAGE')
);
$this->sendMessage(Text::sprintf('COM_OSMAP_INSTALLER_GOTOPLUGINS', $link), 'warning');
break;
case 'update':
$this->checkDatabase();
$this->fixXMLMenus();
$this->clearLanguageFiles();
break;
}
$xmapConverter->moveXmapPluginsParamsToOSMapPlugins();
}
/**
* Creates a default sitemap if no one is found.
*
* @return void
*/
protected function createDefaultSitemap(): void
{
$db = Factory::getDbo();
// Check if we have any sitemaps, otherwise let's create a default one
$query = $db->getQuery(true)
->select('COUNT(*)')
->from('#__osmap_sitemaps');
$noSitemaps = ((int)$db->setQuery($query)->loadResult()) === 0;
if ($noSitemaps) {
// Get all menus
// Search for home menu and language if exists
$subQuery = $db->getQuery(true)
->select('b.menutype, b.home, b.language, l.image, l.sef, l.title_native')
->from('#__menu AS b')
->leftJoin('#__languages AS l ON l.lang_code = b.language')
->where('b.home != 0')
->where('(b.client_id = 0 OR b.client_id IS NULL)');
// Get all menu types with optional home menu and language
$query = $db->getQuery(true)
->select('a.id, a.asset_id, a.menutype, a.title, a.description, a.client_id')
->select('c.home, c.language, c.image, c.sef, c.title_native')
->from('#__menu_types AS a')
->leftJoin('(' . $subQuery . ') c ON c.menutype = a.menutype')
->order('a.id');
$db->setQuery($query);
$menus = $db->loadObjectList();
if (!empty($menus)) {
$data = [
'name' => 'Default Sitemap',
'is_default' => 1,
'published' => 1,
];
// Create the sitemap
Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_osmap/tables');
$row = Table::getInstance('Sitemap', 'OSMapTable');
$row->save($data);
$i = 0;
foreach ($menus as $menu) {
$menuTypeId = $this->getMenuTypeId($menu->menutype);
$query = $db->getQuery(true)
->set('sitemap_id = ' . $db->quote($row->id))
->set('menutype_id = ' . $db->quote($menuTypeId))
->set('priority = ' . $db->quote('0.5'))
->set('changefreq = ' . $db->quote('weekly'))
->set('ordering = ' . $db->quote($i++))
->insert('#__osmap_sitemap_menus');
$db->setQuery($query)->execute();
}
}
}
}
/**
* Check if there are sitemaps in the old table. After migrate, remove
* the table.
*
* On updates, verify current schema
*
* @return void
*/
protected function checkDatabase(): void
{
$this->sendDebugMessage(__METHOD__);
if ($this->findTable('#__osmap_sitemap')) {
$this->migrateLegacyDatabase();
} else {
$this->checkDatabaseSchema();
}
}
/**
* @return void
*/
protected function migrateLegacyDatabase(): void
{
$this->sendDebugMessage('Migrating legacy database');
try {
$db = $this->dbo;
$db->transactionStart();
// For the migration, as we only have new tables, make sure to have a clean start
$db->setQuery('DELETE FROM ' . $db->quoteName('#__osmap_items_settings'))->execute();
$db->setQuery('DELETE FROM ' . $db->quoteName('#__osmap_sitemap_menus'))->execute();
$db->setQuery('DELETE FROM ' . $db->quoteName('#__osmap_sitemaps'))->execute();
// Get legacy sitemaps
$query = $db->getQuery(true)
->select([
'id',
'title',
'is_default',
'state',
'created',
'selections',
'excluded_items',
])
->from('#__osmap_sitemap');
$sitemaps = $db->setQuery($query)->loadObjectList();
// Move the legacy sitemaps to the new table
if ($sitemaps) {
foreach ($sitemaps as $sitemap) {
if ($sitemap->created === $db->getNullDate()) {
$sitemap->created = Factory::getDate()->toSql();
}
$insertObject = (object)[
'id' => $sitemap->id,
'name' => $sitemap->title,
'is_default' => $sitemap->is_default,
'published' => $sitemap->state,
'created_on' => $sitemap->created,
];
$db->insertObject('#__osmap_sitemaps', $insertObject);
// Add the selected menus
$menus = json_decode($sitemap->selections, true);
if (is_array($menus)) {
foreach ($menus as $menuType => $menu) {
$menuTypeId = $this->getMenuTypeId($menuType);
if ($menuTypeId) {
// Menu type still exists
$insertObject = (object)[
'sitemap_id' => $sitemap->id,
'menutype_id' => $menuTypeId,
'priority' => $menu['priority'],
'changefreq' => $menu['changefreq'],
'ordering' => $menu['ordering'],
];
$db->insertObject('#__osmap_sitemap_menus', $insertObject);
}
}
}
$excludedItems = json_decode($sitemap->excluded_items ?? '', true);
if (is_array($excludedItems)) {
// Convert settings for excluded items
foreach ($excludedItems as $item) {
$uid = $this->convertItemUID($item[0]);
// Check if the item was already registered
$query = $db->getQuery(true)
->select('COUNT(*)')
->from('#__osmap_items_settings')
->where([
'sitemap_id = ' . $db->quote($sitemap->id),
'uid = ' . $db->quote($uid),
]);
$count = $db->setQuery($query)->loadResult();
if ($count == 0) {
$insertObject = (object)[
'sitemap_id' => $sitemap->id,
'uid' => $uid,
'published' => 0,
'changefreq' => 'weekly',
'priority' => '0.5',
];
$db->insertObject('#__osmap_items_settings', $insertObject);
} else {
// Update the setting
$query = $db->getQuery(true)
->update('#__osmap_items_settings')
->set('published = 0')
->where([
'sitemap_id = ' . $db->quote($sitemap->id),
'uid = ' . $db->quote($uid),
]);
$db->setQuery($query)->execute();
}
}
}
// Convert custom settings for items
if ($this->findTable('#__osmap_items')) {
$query = $db->getQuery(true)
->select([
'uid',
'properties',
])
->from('#__osmap_items')
->where('sitemap_id = ' . $db->quote($sitemap->id))
->where('view = ' . $db->quote('xml'));
$modifiedItems = $db->setQuery($query)->loadObjectList();
if ($modifiedItems) {
foreach ($modifiedItems as $item) {
$item->properties = str_replace(';', '&', $item->properties);
parse_str($item->properties, $properties);
$item->uid = $this->convertItemUID($item->uid);
// Check if the item already exists to update, or insert
$query = $db->getQuery(true)
->select('COUNT(*)')
->from('#__osmap_items_settings')
->where([
'sitemap_id = ' . $db->quote($sitemap->id),
'uid = ' . $db->quote($item->uid),
]);
$exists = (bool)$db->setQuery($query)->loadResult();
if ($exists) {
$fields = [];
// Check if the changefreq is set and set to update
if (isset($properties['changefreq'])) {
$fields = 'changefreq = ' . $db->quote($properties['changefreq']);
}
// Check if the priority is set and set to update
if (isset($properties['priority'])) {
$fields = 'priority = ' . $db->quote($properties['priority']);
}
// Update the item
$query = $db->getQuery(true)
->update('#__osmap_items_settings')
->set($fields)
->where([
'sitemap_id = ' . $db->quote($sitemap->id),
'uid = ' . $db->quote($item->uid),
]);
$db->setQuery($query)->execute();
} else {
$insertObject = (object)[
'sitemap_id' => $sitemap->id,
'uid' => $item->uid,
'published' => 1,
'changefreq' => $properties['changefreq'] ?? 'weekly',
'priority' => $properties['priority'] ?? '0.5',
];
$db->insertObject('#__osmap_items_settings', $insertObject);
}
}
}
}
}
}
// Remove the old table
$query = 'DROP TABLE IF EXISTS ' . $db->quoteName('#__osmap_items');
$db->setQuery($query)->execute();
// Remove the old table
$query = 'DROP TABLE IF EXISTS ' . $db->quoteName('#__osmap_sitemap');
$db->setQuery($query)->execute();
$db->transactionCommit();
} catch (\Throwable $error) {
$this->sendErrorMessage($error);
$db->transactionRollback();
}
}
/**
* Returns the id of the menutype.
*
* @param string $menuType
*
* @return int
*/
protected function getMenuTypeId(string $menuType): int
{
$db = $this->dbo;
$query = $db->getQuery(true)
->select('id')
->from('#__menu_types')
->where('menutype = ' . $db->quote($menuType));
return (int)$db->setQuery($query)->loadResult();
}
/**
* Converts a legacy UID to the new pattern. Instead of "com_contenta25",
* "joomla.article.25". Returns the new UID
*
* @param string $uid
*
* @return string
*/
protected function convertItemUID(string $uid): string
{
// Joomla articles in categories
if (preg_match('#com_contentc[0-9]+a([0-9]+)#', $uid, $matches)) {
return 'joomla.article.' . $matches[1];
}
// Joomla categories
if (preg_match('#com_contentc([0-9]+)#', $uid, $matches)) {
return 'joomla.category.' . $matches[1];
}
// Joomla articles
if (preg_match('#com_contenta([0-9]+)#', $uid, $matches)) {
return 'joomla.article.' . $matches[1];
}
// Joomla featured
if (preg_match('#com_contentfeatureda([0-9]+)#', $uid, $matches)) {
return 'joomla.featured.' . $matches[1];
}
// Menu items
if (preg_match('#itemid([0-9]*)#', $uid, $matches)) {
return 'menuitem.' . $matches[1];
}
return $uid;
}
/**
* @return void
*/
public function checkDatabaseSchema(): void
{
if (version_compare($this->schemaVersion, '5', 'ge')) {
$this->sendDebugMessage('No DB Schema updates');
return;
}
$db = $this->dbo;
$this->sendDebugMessage('Checking database schema updates: v' . $this->schemaVersion);
if ($this->findColumn('#__osmap_items_settings.format')) {
$this->sendDebugMessage('UPDATE: items_settings.format');
$db->setQuery(join(' ', [
'ALTER TABLE `#__osmap_items_settings`',
"MODIFY `format` TINYINT(1) UNSIGNED NULL DEFAULT '2'",
"COMMENT 'Format of the setting: 1) Legacy Mode - UID Only; 2) Based on menu ID and UID'",
]))
->execute();
}
if ($this->findColumn('#__osmap_items_settings.url_hash')) {
$this->sendDebugMessage('UPDATE: items_settings.url_hash');
$db->setQuery(
sprintf(
'ALTER TABLE %s CHANGE %s %s CHAR(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT %s',
$db->quoteName('#__osmap_items_settings'),
$db->quoteName('url_hash'),
$db->quoteName('settings_hash'),
$db->quote('')
)
)
->execute();
}
$this->dropConstraints([
'#__osmap_sitemap_menus.fk_sitemaps',
'#__osmap_sitemap_menus.fk_sitemaps_menus',
]);
$this->addIndexes([
'#__osmap_sitemaps.idx_default' => ['is_default ASC', 'id ASC'],
'#__osmap_items_settings.idx_sitemap_id' => ['sitemap_id ASC'],
'#__osmap_sitemap_menus.idx_menutype_id' => ['menutype_id ASC'],
'#__osmap_sitemap_menus.idx_sitemaps_id' => ['sitemap_id ASC'],
]);
$this->dropIndexes([
'#__osmap_sitemaps.default_idx',
'#__osmap_sitemap_menus.idx_sitemap_menus',
'#__osmap_sitemap_menus.fk_sitemaps_idx',
'#__osmap_sitemap_menus.ordering_idx',
'#__osmap_sitemap_menus.idx_ordering',
]);
}
/**
* Adds new format=xml to existing xml menus
*
* @since v4.2.25
*/
protected function fixXMLMenus()
{
$db = $this->dbo;
$siteApp = SiteApplication::getInstance('site');
$query = $db->getQuery(true)
->select('id, link')
->from('#__menu')
->where([
'client_id = ' . $siteApp->getClientId(),
sprintf('link LIKE %s', $db->quote('%com_osmap%')),
sprintf('link LIKE %s', $db->quote('%view=xml%')),
sprintf('link NOT LIKE %s', $db->quote('%format=xml%')),
]);
$menus = $db->setQuery($query)->loadObjectList();
foreach ($menus as $menu) {
$menu->link .= '&format=xml';
$db->updateObject('#__menu', $menu, ['id']);
}
}
/**
* Clear localized language files from core folders
*
* @return void
*/
protected function clearLanguageFiles()
{
$files = array_merge(
Folder::files(JPATH_ADMINISTRATOR . '/language', '_osmap', true, true),
Folder::files(JPATH_SITE . '/language', '_osmap', true, true)
);
foreach ($files as $file) {
if (is_file($file)) {
File::delete($file);
}
}
}
}

View File

@ -0,0 +1,513 @@
<?php
/**
* @package OSMap
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2007-2014 XMap - Joomla! Vargas - Guillermo Vargas. All rights reserved.
* @copyright 2016-2024 Joomlashack.com. All rights reserved.
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of OSMap.
*
* OSMap is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* OSMap is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OSMap. If not, see <https://www.gnu.org/licenses/>.
*/
namespace Alledia\Installer\OSMap;
use Alledia\Framework\Joomla\Extension\Generic;
use Joomla\CMS\Factory;
defined('_JEXEC') or die();
class XmapConverter
{
/**
* @var array
*/
protected $xmapPluginsParams = [];
/**
* List of refactored Xmap plugins to migrate the settings
*
* @var array
*/
protected $refactoredXmapPlugins = ['com_content' => 'joomla'];
/**
* Look for the Xmap data to suggest a data migration
*
* @return bool True if Xmap data was found
*/
public function checkXmapDataExists(): bool
{
$db = Factory::getDbo();
// Do we have any Xmap sitemap?
$query = $db->getQuery(true)
->select('COUNT(*)')
->from('#__xmap_sitemap');
$total = (int)$db->setQuery($query)->loadResult();
return $total > 0;
}
/**
* Save the Xmap plugins params into the new plugins. Receives a list of
* plugin names to look for params.
*
* @return void
*/
public function saveXmapPluginParamsIfExists()
{
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select([
'element',
'params'
])
->from('#__extensions')
->where([
'type = "plugin"',
'folder = "xmap"',
'element IN ("' . implode('","', array_keys($this->refactoredXmapPlugins)) . '")'
]);
$legacyPlugins = $db->setQuery($query)->loadObjectList();
// Check if the respective OSMap plugin is already installed. If so, do not save its params to not override.
if ($legacyPlugins) {
foreach ($legacyPlugins as $plugin) {
$query = $db->getQuery(true)
->select('extension_id')
->from('#__extensions')
->where([
'type = "plugin"',
'folder = "osmap"',
'element = "' . $plugin->element . '"'
]);
$osmapPluginID = $db->setQuery($query)->loadResult();
if (empty($osmapPluginID)) {
$this->xmapPluginsParams[] = $plugin;
}
}
}
}
/**
* This method move the Xmap plugins' params to the OSMap plugins.
*
* @return void
*/
public function moveXmapPluginsParamsToOSMapPlugins()
{
$db = Factory::getDbo();
if (!empty($this->xmapPluginsParams)) {
foreach ($this->xmapPluginsParams as $plugin) {
// Look for the OSMap plugin
$query = $db->getQuery(true)
->select('extension_id')
->from('#__extensions')
->where([
'type = "plugin"',
'folder = "osmap"',
'element = ' . $db->quote($this->refactoredXmapPlugins[$plugin->element] ?? '')
]);
$osmapPluginID = $db->setQuery($query)->loadResult();
if (!empty($osmapPluginID)) {
$query = $db->getQuery(true)
->update('#__extensions')
->set('params = ' . $db->quote(addslashes($plugin->params)))
->where('extension_id = ' . $osmapPluginID);
$db->setQuery($query)->execute();
}
}
}
}
/**
* Migrates data from Xmap to OSMap.
*
* @return void
*/
public function migrateData()
{
$result = (object)[
'success' => false
];
$db = Factory::getDbo();
$db->transactionStart();
try {
// Do we have any Xmap sitemap?
$sitemapFailedIds = [];
$itemFailedIds = [];
$query = $db->getQuery(true)
->select('*')
->from('#__xmap_sitemap');
$sitemaps = $db->setQuery($query)->loadObjectList();
if ($sitemaps) {
// Cleanup the db tables
$tables = [
'#__osmap_items_settings',
'#__osmap_sitemap_menus',
'#__osmap_sitemaps'
];
foreach ($tables as $table) {
$db->setQuery(
$db->getQuery(true)->delete($db->quoteName($table))
)->execute();
}
// Import the sitemaps
foreach ($sitemaps as $sitemap) {
$query = $db->getQuery(true)
->set([
$db->quoteName('id') . '=' . $db->quote($sitemap->id),
$db->quoteName('name') . '=' . $db->quote($sitemap->title),
$db->quoteName('is_default') . '=' . $db->quote($sitemap->is_default),
$db->quoteName('published') . '=' . $db->quote($sitemap->state),
$db->quoteName('created_on') . '=' . $db->quote($sitemap->created)
])
->insert('#__osmap_sitemaps');
if ($db->setQuery($query)->execute()) {
// Add the selected menus to the correct table
if ($menus = json_decode($sitemap->selections, true)) {
foreach ($menus as $menuType => $menu) {
if ($menuTypeId = $this->getMenuTypeId($menuType)) {
// Convert the selection of menus into a row
$query = $db->getQuery(true)
->insert('#__osmap_sitemap_menus')
->columns([
'sitemap_id',
'menutype_id',
'priority',
'changefreq',
'ordering'
])
->values(
implode(
',',
[
$db->quote($sitemap->id),
$db->quote($menuTypeId),
$db->quote($menu['priority']),
$db->quote($menu['changefreq']),
$db->quote($menu['ordering'])
]
)
);
$db->setQuery($query)->execute();
}
}
}
// Convert settings about excluded items
if ($sitemap->excluded_items ?? null) {
if ($excludedItems = json_decode($sitemap->excluded_items, true)) {
foreach ($excludedItems as $item) {
$uid = $this->convertItemUID($item[0]);
// Check if the item was already registered
$query = $db->getQuery(true)
->select('COUNT(*)')
->from('#__osmap_items_settings')
->where([
'sitemap_id = ' . $db->quote($sitemap->id),
'uid = ' . $db->quote($uid)
]);
$count = $db->setQuery($query)->loadResult();
if ($count == 0) {
// Insert the settings
$query = $db->getQuery(true)
->insert('#__osmap_items_settings')
->columns([
'sitemap_id',
'uid',
'published',
'changefreq',
'priority'
])
->values(
implode(
',',
[
$sitemap->id,
$db->quote($uid),
0,
$db->quote('weekly'),
$db->quote('0.5')
]
)
);
} else {
// Update the setting
$query = $db->getQuery(true)
->update('#__osmap_items_settings')
->set('published = 0')
->where([
'sitemap_id = ' . $db->quote($sitemap->id),
'uid = ' . $db->quote($uid)
]);
}
$db->setQuery($query)->execute();
}
}
}
// Convert custom settings for items
$query = $db->getQuery(true)
->select([
'uid',
'properties'
])
->from('#__xmap_items')
->where('sitemap_id = ' . $db->quote($sitemap->id))
->where('view = ' . $db->quote('xml'));
if ($modifiedItems = $db->setQuery($query)->loadObjectList()) {
foreach ($modifiedItems as $item) {
$item->properties = str_replace(';', '&', $item->properties);
parse_str($item->properties, $properties);
$item->uid = $this->convertItemUID($item->uid);
// Check if the item already exists to update, or insert
$query = $db->getQuery(true)
->select('COUNT(*)')
->from('#__osmap_items_settings')
->where([
'sitemap_id = ' . $db->quote($sitemap->id),
'uid = ' . $db->quote($item->uid)
]);
$exists = (bool)$db->setQuery($query)->loadResult();
if ($exists) {
$fields = [];
// Check if the changefreq is set and set to update
if (isset($properties['changefreq'])) {
$fields = 'changefreq = ' . $db->quote($properties['changefreq']);
}
// Check if the priority is set and set to update
if (isset($properties['priority'])) {
$fields = 'priority = ' . $db->quote($properties['priority']);
}
// Update the item
$query = $db->getQuery(true)
->update('#__osmap_items_settings')
->set($fields)
->where([
'sitemap_id = ' . $db->quote($sitemap->id),
'uid = ' . $db->quote($item->uid)
]);
} else {
$columns = [
'sitemap_id',
'uid',
'published'
];
$values = [
$db->quote($sitemap->id),
$db->quote($item->uid),
1
];
// Check if the changefreq is set and set to update
if (isset($properties['changefreq'])) {
$columns[] = 'changefreq';
$values[] = 'changefreq = ' . $db->quote($properties['changefreq']);
}
// Check if the priority is set and set to update
if (isset($properties['priority'])) {
$columns[] = 'priority';
$values[] = 'priority = ' . $db->quote($properties['priority']);
}
// Insert a new item
$query = $db->getQuery(true)
->insert('#__osmap_items_settings')
->columns($columns)
->values(implode(',', $values));
}
$db->setQuery($query)->execute();
}
}
} else {
$sitemapFailedIds = $sitemap->id;
}
}
}
if ($sitemapFailedIds || $itemFailedIds) {
throw new \Exception('Failed the sitemap or item migration');
}
/*
* Menu Migration
*/
$xmap = new Generic('Xmap', 'component');
$osmap = new Generic('OSMap', 'component');
// Remove OSMap menus
$query = $db->getQuery(true)
->delete('#__menu')
->where('type = ' . $db->quote('component'))
->where('component_id = ' . $db->quote($osmap->getId()));
$db->setQuery($query);
$db->execute();
// Get the Xmap menus
$query = $db->getQuery(true)
->select('*')
->from('#__menu')
->where('type = ' . $db->quote('component'))
->where('component_id = ' . $db->quote($xmap->getId()));
$db->setQuery($query);
$xmapMenus = $db->loadObjectList();
if (!empty($xmapMenus)) {
// Convert each menu to OSMap
foreach ($xmapMenus as $menu) {
$query = $db->getQuery(true)
->set('title = ' . $db->quote($this->replaceXmapByOSMap($menu->title)))
->set('alias = ' . $db->quote($this->replaceXmapByOSMap($menu->alias)))
->set('path = ' . $db->quote($this->replaceXmapByOSMap($menu->path)))
->set('link = ' . $db->quote($this->replaceXmapByOSMap($menu->link)))
->set('img = ' . $db->quote($this->replaceXmapByOSMap($menu->img)))
->set('component_id = ' . $db->quote($osmap->getId()))
->update('#__menu')
->where('id = ' . $db->quote($menu->id));
$db->setQuery($query);
$db->execute();
}
}
// Disable Xmap
$query = $db->getQuery(true)
->set('enabled = 0')
->update('#__extensions')
->where('extension_id = ' . $db->quote($xmap->getId()));
$db->setQuery($query);
$db->execute();
// Clean up Xmap db tables
$db->setQuery('DELETE FROM ' . $db->quoteName('#__xmap_sitemap'));
$db->execute();
$db->setQuery('DELETE FROM ' . $db->quoteName('#__xmap_items'));
$db->execute();
$db->transactionCommit();
$result->success = true;
} catch (\Exception $e) {
$db->transactionRollback();
var_dump($e);
}
echo json_encode($result);
}
/**
* Replaces the Xmap strings in multiple formats, changing to OSMap.
*
* @param string $str
*
* @return string
*/
protected function replaceXmapByOSMap(string $str): string
{
$replacements = [
'XMAP' => 'OSMAP',
'XMap' => 'OSMap',
'xMap' => 'OSMap',
'xmap' => 'osmap',
];
return str_replace(
array_keys($replacements),
$replacements,
$str
);
}
/**
* Returns the id of the menutype.
*
* @param string $menuType
*
* @return int
*/
protected function getMenuTypeId(string $menuType): int
{
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select('id')
->from('#__menu_types')
->where('menutype = ' . $db->quote($menuType));
return (int)$db->setQuery($query)->loadResult();
}
/**
* Converts a legacy UID to the new pattern. Instead of "com_contenta25",
* "joomla.article.25". Returns the new UID
*
* @param string $uid
*
* @return string
*/
protected function convertItemUID(string $uid): string
{
// Joomla articles in categories
if (preg_match('#com_contentc[0-9]+a([0-9]+)#', $uid, $matches)) {
return 'joomla.article.' . $matches[1];
}
// Joomla categories
if (preg_match('#com_contentc([0-9]+)#', $uid, $matches)) {
return 'joomla.category.' . $matches[1];
}
// Joomla articles
if (preg_match('#com_contenta([0-9]+)#', $uid, $matches)) {
return 'joomla.article.' . $matches[1];
}
// Joomla featured
if (preg_match('#com_contentfeatureda([0-9]+)#', $uid, $matches)) {
return 'joomla.featured.' . $matches[1];
}
// Menu items
if (preg_match('#itemid([0-9]*)#', $uid, $matches)) {
return 'menuitem.' . $matches[1];
}
return $uid;
}
}

View File

@ -0,0 +1,53 @@
<?php
/**
* @package ShackInstaller
* @contact www.joomlashack.com, help@joomlashack.com
* @copyright 2016-2023 Joomlashack.com. All rights reserved
* @license https://www.gnu.org/licenses/gpl.html GNU/GPL
*
* This file is part of ShackInstaller.
*
* ShackInstaller is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* ShackInstaller is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ShackInstaller. If not, see <https://www.gnu.org/licenses/>.
*/
use Alledia\Installer\AutoLoader;
use Joomla\CMS\Version;
// phpcs:disable PSR1.Files.SideEffects
defined('_JEXEC') or die();
if (defined('SHACK_INSTALLER_BASE') == false) {
define('SHACK_INSTALLER_BASE', __DIR__);
require_once SHACK_INSTALLER_BASE . '/AutoLoader.php';
}
AutoLoader::register('Alledia\\Installer', __DIR__, true);
if (defined('SHACK_INSTALLER_VERSION') == false) {
define('SHACK_INSTALLER_VERSION', '2.4.4');
define('SHACK_INSTALLER_COMPATIBLE', '2.4.0');
if (isset($reportErrors) == false) {
$reportErrors = E_ALL ^ E_DEPRECATED ^ E_USER_DEPRECATED;
if (Version::MAJOR_VERSION == 4) {
// There is a bad line of code in Joomla 4 that runs during extension install/update
$reportErrors = $reportErrors ^ E_NOTICE;
}
}
if ($reportErrors) {
set_error_handler('\\Alledia\\Installer\\AbstractScript::errorHandler', $reportErrors);
}
}