Skip to content

Commit

Permalink
Merge pull request #44068 from Hackwar/44-52-upmerge-20240913
Browse files Browse the repository at this point in the history
  • Loading branch information
Hackwar authored Sep 13, 2024
2 parents 686b63b + ae26c9e commit 4054d94
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
namespace Joomla\Component\Media\Administrator\Controller;

use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Helper\MediaHelper;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\Controller\BaseController;
Expand Down Expand Up @@ -199,7 +198,7 @@ public function postFiles()
$override = $content->get('override', false);

if ($mediaContent) {
$this->checkContent();
$this->checkFileSize(\strlen($mediaContent));

// A file needs to be created
$name = $this->getModel()->createFile($adapter, $name, $path, $mediaContent, $override);
Expand Down Expand Up @@ -334,29 +333,21 @@ public function getModel($name = 'Api', $prefix = 'Administrator', $config = [])
}

/**
* Performs various checks if it is allowed to save the content.
* Performs file size checks if it is allowed to be saved.
*
* @param integer $fileSize The size of submitted file
*
* @return void
*
* @since 4.0.0
* @since 4.4.9
* @throws \Exception
*/
private function checkContent()
private function checkFileSize(int $fileSize)
{
$helper = new MediaHelper();
$contentLength = $this->input->server->getInt('CONTENT_LENGTH');
$params = ComponentHelper::getParams('com_media');
$paramsUploadMaxsize = $params->get('upload_maxsize', 0) * 1024 * 1024;
$uploadMaxFilesize = $helper->toBytes(\ini_get('upload_max_filesize'));
$postMaxSize = $helper->toBytes(\ini_get('post_max_size'));
$memoryLimit = $helper->toBytes(\ini_get('memory_limit'));

if (
($paramsUploadMaxsize > 0 && $contentLength > $paramsUploadMaxsize)
|| ($uploadMaxFilesize > 0 && $contentLength > $uploadMaxFilesize)
|| ($postMaxSize > 0 && $contentLength > $postMaxSize)
|| ($memoryLimit > -1 && $contentLength > $memoryLimit)
) {

if ($paramsUploadMaxsize > 0 && $fileSize > $paramsUploadMaxsize) {
$link = 'index.php?option=com_config&view=component&component=com_media';
$output = HTMLHelper::_('link', Route::_($link), Text::_('JOPTIONS'));
throw new \Exception(Text::sprintf('COM_MEDIA_ERROR_WARNFILETOOLARGE', $output), 403);
Expand Down
220 changes: 139 additions & 81 deletions administrator/components/com_scheduler/src/Model/TaskModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -366,59 +366,103 @@ public function getTask(array $options = []): ?\stdClass

try {
$options = $resolver->resolve($options);
} catch (\Exception $e) {
if ($e instanceof UndefinedOptionsException || $e instanceof InvalidOptionsException) {
throw $e;
}
} catch (UndefinedOptionsException | InvalidOptionsException $e) {
throw $e;
}

$db = $this->getDatabase();
$now = Factory::getDate()->toSql();

// Get lock on the table to help with concurrency issues
$db->lockTable(self::TASK_TABLE);
$db = $this->getDatabase();
$now = Factory::getDate()->toSql();
$affectedRows = 0;

// If concurrency is not allowed, we only get a task if another one does not have a "lock"
if (!$options['allowConcurrent']) {
// Get count of locked (presumed running) tasks
$lockCountQuery = $db->getQuery(true)
->from($db->quoteName(self::TASK_TABLE))
->select('COUNT(id)')
->where($db->quoteName('locked') . ' IS NOT NULL');

try {
$runningCount = $db->setQuery($lockCountQuery)->loadResult();
} catch (\RuntimeException $e) {
$db->unlockTables();
try {
$db->lockTable(self::TASK_TABLE);

if (!$options['allowConcurrent'] && $this->hasRunningTasks($db)) {
return null;
}

if ($runningCount !== 0) {
$db->unlockTables();
$lockQuery = $this->buildLockQuery($db, $now, $options);

return null;
if ($options['id'] > 0) {
$lockQuery->where($db->quoteName('id') . ' = :taskId')
->bind(':taskId', $options['id'], ParameterType::INTEGER);
} else {
$id = $this->getNextTaskId($db, $now, $options);
if (\count($id) === 0) {
return null;
}
$lockQuery->where($db->quoteName('id') . ' = :taskId')
->bind(':taskId', $id, ParameterType::INTEGER);
}

$db->setQuery($lockQuery)->execute();
$affectedRows = $db->getAffectedRows();
} catch (\RuntimeException $e) {
return null;
} finally {
$db->unlockTables();
}

if ($affectedRows != 1) {
return null;
}

return $this->fetchTask($db, $now);
}

/**
* Checks if there are any running tasks in the database.
*
* @param \JDatabaseDriver $db The database driver to use.
* @return bool True if there are running tasks, false otherwise.
* @since __DEPLOY_VERSION__
*/
private function hasRunningTasks($db): bool
{
$lockCountQuery = $db->getQuery(true)
->select('COUNT(id)')
->from($db->quoteName(self::TASK_TABLE))
->where($db->quoteName('locked') . ' IS NOT NULL')
->where($db->quoteName('state') . ' = 1');

try {
$runningCount = $db->setQuery($lockCountQuery)->loadResult();
} catch (\RuntimeException $e) {
return false;
}

$lockQuery = $db->getQuery(true);
return $runningCount != 0;
}

$lockQuery->update($db->quoteName(self::TASK_TABLE))
/**
* Builds a query to lock a task.
*
* @param Database $db The database object.
* @param string $now The current time.
* @param array $options The options for building the query.
* - includeCliExclusive: Whether to include CLI exclusive tasks.
* - bypassScheduling: Whether to bypass scheduling.
* - allowDisabled: Whether to allow disabled tasks.
* - id: The ID of the task.
* @return Query The lock query.
* @since __DEPLOY_VERSION__
*/
private function buildLockQuery($db, $now, $options)
{
$lockQuery = $db->getQuery(true)
->update($db->quoteName(self::TASK_TABLE))
->set($db->quoteName('locked') . ' = :now1')
->bind(':now1', $now);

// Array of all active routine ids
$activeRoutines = array_map(
static function (TaskOption $taskOption): string {
return $taskOption->id;
},
SchedulerHelper::getTaskOptions()->options
);

// "Orphaned" tasks are not a part of the task queue!
$lockQuery->whereIn($db->quoteName('type'), $activeRoutines, ParameterType::STRING);

// If directed, exclude CLI exclusive tasks
if (!$options['includeCliExclusive']) {
$lockQuery->where($db->quoteName('cli_exclusive') . ' = 0');
}
Expand All @@ -428,77 +472,91 @@ static function (TaskOption $taskOption): string {
->bind(':now2', $now);
}

if ($options['allowDisabled']) {
$lockQuery->whereIn($db->quoteName('state'), [0, 1]);
} else {
$lockQuery->where($db->quoteName('state') . ' = 1');
}
$stateCondition = $options['allowDisabled'] ? [0, 1] : [1];
$lockQuery->whereIn($db->quoteName('state'), $stateCondition);

if ($options['id'] > 0) {
$lockQuery->where($db->quoteName('id') . ' = :taskId')
->bind(':taskId', $options['id'], ParameterType::INTEGER);
} else {
// Pick from the front of the task queue if no 'id' is specified
// Get the id of the next task in the task queue
$idQuery = $db->getQuery(true)
->from($db->quoteName(self::TASK_TABLE))
->select($db->quoteName('id'))
->where($db->quoteName('state') . ' = 1')
->order($db->quoteName('priority') . ' DESC')
->order($db->quoteName('next_execution') . ' ASC')
->setLimit(1);

try {
$ids = $db->setQuery($idQuery)->loadColumn();
} catch (\RuntimeException $e) {
$db->unlockTables();
return $lockQuery;
}

return null;
}
/**
* Retrieves the ID of the next task based on the given criteria.
*
* @param \JDatabaseDriver $db The database object.
* @param string $now The current time.
* @param array $options The options for retrieving the next task.
* - includeCliExclusive: Whether to include CLI exclusive tasks.
* - bypassScheduling: Whether to bypass scheduling.
* - allowDisabled: Whether to allow disabled tasks.
* @return array The ID of the next task, or an empty array if no task is found.
*
* @since __DEPLOY_VERSION__
* @throws \RuntimeException If there is an error executing the query.
*/
private function getNextTaskId($db, $now, $options)
{
$idQuery = $db->getQuery(true)
->from($db->quoteName(self::TASK_TABLE))
->select($db->quoteName('id'));

if (\count($ids) === 0) {
$db->unlockTables();
$activeRoutines = array_map(
static function (TaskOption $taskOption): string {
return $taskOption->id;
},
SchedulerHelper::getTaskOptions()->options
);

return null;
}
$idQuery->whereIn($db->quoteName('type'), $activeRoutines, ParameterType::STRING);

$lockQuery->whereIn($db->quoteName('id'), $ids);
if (!$options['includeCliExclusive']) {
$idQuery->where($db->quoteName('cli_exclusive') . ' = 0');
}

try {
$db->setQuery($lockQuery)->execute();
} catch (\RuntimeException $e) {
} finally {
$affectedRows = $db->getAffectedRows();

$db->unlockTables();
if (!$options['bypassScheduling']) {
$idQuery->where($db->quoteName('next_execution') . ' <= :now2')
->bind(':now2', $now);
}

if ($affectedRows != 1) {
/*
// @todo
// ? Fatal failure handling here?
// ! Question is, how? If we check for tasks running beyond there time here, we have no way of
// ! what's already been notified (since we're not auto-unlocking/recovering tasks anymore).
// The solution __may__ be in a "last_successful_finish" (or something) column.
*/
$stateCondition = $options['allowDisabled'] ? [0, 1] : [1];
$idQuery->whereIn($db->quoteName('state'), $stateCondition);

return null;
}
$idQuery->where($db->quoteName('next_execution') . ' IS NOT NULL')
->order($db->quoteName('priority') . ' DESC')
->order($db->quoteName('next_execution') . ' ASC')
->setLimit(1);

$getQuery = $db->getQuery(true);
try {
return $db->setQuery($idQuery)->loadColumn();
} catch (\RuntimeException $e) {
return [];
}
}

$getQuery->select('*')
/**
* Fetches a task from the database based on the current time.
*
* @param \JDatabaseDriver $db The database driver to use.
* @param string $now The current time in the database's time format.
* @return \stdClass|null The fetched task object, or null if no task was found.
* @since __DEPLOY_VERSION__
* @throws \RuntimeException If there was an error executing the query.
*/
private function fetchTask($db, $now): ?\stdClass
{
$getQuery = $db->getQuery(true)
->select('*')
->from($db->quoteName(self::TASK_TABLE))
->where($db->quoteName('locked') . ' = :now')
->bind(':now', $now);

$task = $db->setQuery($getQuery)->loadObject();
try {
$task = $db->setQuery($getQuery)->loadObject();
} catch (\RuntimeException $e) {
return null;
}

$task->execution_rules = json_decode($task->execution_rules);
$task->cron_rules = json_decode($task->cron_rules);

$task->taskOption = SchedulerHelper::getTaskOptions()->findOption($task->type);
$task->taskOption = SchedulerHelper::getTaskOptions()->findOption($task->type);

return $task;
}
Expand Down
5 changes: 4 additions & 1 deletion components/com_content/src/Model/ArticlesModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,10 @@ protected function getListQuery()
}

// Filter by start and end dates.
if ((!$user->authorise('core.edit.state', 'com_content')) && (!$user->authorise('core.edit', 'com_content'))) {
if (
!(is_numeric($condition) && $condition == ContentComponent::CONDITION_UNPUBLISHED)
&& !(\is_array($condition) && \in_array(ContentComponent::CONDITION_UNPUBLISHED, $condition))
) {
$query->where(
[
'(' . $db->quoteName('a.publish_up') . ' IS NULL OR ' . $db->quoteName('a.publish_up') . ' <= :publishUp)',
Expand Down
4 changes: 0 additions & 4 deletions components/com_tags/src/Model/TagModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -303,10 +303,6 @@ public function getItem($pk = null)
return false;
}
}

if (\count($this->item) != \count($idsArray)) {
throw new \Exception(Text::_('COM_TAGS_TAG_NOT_FOUND'), 404);
}
}

return $this->item;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ describe('Test in backend that the action logs', () => {
cy.visit('/administrator/index.php?option=com_actionlogs&view=actionlogs');
});

it('have a title', () => {
it('has a title', () => {
cy.get('h1.page-title').should('contain.text', 'User Actions Log');
});

Expand All @@ -23,7 +23,7 @@ describe('Test in backend that the action logs', () => {
cy.task('queryDB', 'TRUNCATE #__action_logs');
});

it('have an export button', () => {
it('has an export button', () => {
cy.get('#toolbar-download1').click();
cy.get('#system-message-container').contains('There are no User Action logs to export').should('exist');
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ describe('Test in backend that the cache', () => {
cy.visit('/administrator/index.php?option=com_cache&view=cache');
});

it('have a title', () => {
it('has a title', () => {
cy.get('h1.page-title').should('contain.text', 'Maintenance: Clear Cache');
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe('Test in backend that the checkin', () => {
cy.get('p.lead').should('contain.text', 'There are no tables with checked out items');
});

it('can display a list of checked out', () => {
it('can display a list of checked out items', () => {
cy.db_createArticle({ title: 'Test article', checked_out: '1', checked_out_time: '2024-01-01 20:00:00' }).then(() => {
cy.visit('/administrator/index.php?option=com_checkin');
cy.get('tr.row0').should('contain.text', 'content');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ describe('Test in backend that the login component', () => {
cy.get('#system-message-container').contains('Username and password do not match or you do not have an account yet.').should('exist');
});

it('can not log in with not existing user', () => {
it('can not log in with non-existing user', () => {
cy.visit('administrator/index.php');
cy.get('#mod-login-username').type('invalid');
cy.get('#mod-login-password').type('invalid');
Expand Down
Loading

0 comments on commit 4054d94

Please sign in to comment.