Merge branch 'rhymix:master' into master

This commit is contained in:
Lastorder 2025-03-03 10:47:47 +09:00 committed by GitHub
commit e5b729f8e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
152 changed files with 2348 additions and 792 deletions

View file

@ -133,6 +133,11 @@ require_once RX_BASEDIR . 'common/framework/DateTime.php';
require_once RX_BASEDIR . 'common/framework/Debug.php';
require_once RX_BASEDIR . 'common/framework/Lang.php';
/**
* Load system configuration.
*/
Rhymix\Framework\Config::init();
/**
* Load user configuration.
*/
@ -141,11 +146,6 @@ if(file_exists(RX_BASEDIR . 'config/config.user.inc.php'))
require_once RX_BASEDIR . 'config/config.user.inc.php';
}
/**
* Load system configuration.
*/
Rhymix\Framework\Config::init();
/**
* Install the debugger.
*/

View file

@ -3,7 +3,7 @@
/**
* RX_VERSION is the version number of the Rhymix CMS.
*/
define('RX_VERSION', '2.1.19');
define('RX_VERSION', '2.1.21');
/**
* RX_MICROTIME is the startup time of the current script, in microseconds since the Unix epoch.

View file

@ -6,7 +6,6 @@
* Copyright (c) Rhymix Developers and Contributors
*/
return array(
// YouTube
'www.youtube.com/',
'www.youtube-nocookie.com/',
@ -19,43 +18,17 @@ return array(
// Google Maps
'www.google.com/maps/embed',
'maps.google.com/',
'maps.google.co.kr/',
// Daum
'flvs.daum.net/',
'videofarm.daum.net/',
'api.v.daum.net/',
'videofarm.daum.net/',
// Giphy
'giphy.com/',
// Sooplive
'vod.sooplive.co.kr/',
// Kakao TV
'tv.kakao.com/',
'play-tv.kakao.com/',
// Naver
'serviceapi.rmcnmv.naver.com/',
'serviceapi.nmv.naver.com/',
'scrap.ad.naver.com/',
'static.campaign.naver.com/',
'musicplayer.naver.com/naverPlayer/posting/',
'player.music.naver.com/naverPlayer/posting/',
// Pandora TV
'www.pandora.tv/view/',
'flvr.pandora.tv/flv2pan/',
// Cyworld
'dbi.video.cyworld.com/v.sk/',
// Egloos
'v.egloos.com/v.sk/',
// Nate
'v.nate.com/v.sk/',
'w.blogdoc.nate.com/',
// SBS
'netv.sbs.co.kr/sbox/',
'news.sbs.co.kr/',
'wizard2.sbs.co.kr/',
'sbsplayer.sbs.co.kr/',
// Afreeca
'afree.ca/',
'chzzk.naver.com/',
);

View file

@ -487,9 +487,9 @@ class DB
}
// Collect various counts used in the page calculation.
list($is_expression, $list_count) = $query->navigation->list_count->getValue($args);
list($is_expression, $page_count) = $query->navigation->page_count->getValue($args);
list($is_expression, $page) = $query->navigation->page->getValue($args);
$list_count = $query->navigation->list_count->getValue($args)[0];
$page_count = $query->navigation->page_count->getValue($args)[0];
$page = $query->navigation->page->getValue($args)[0];
$total_count = intval($count);
$total_page = max(1, intval(ceil($total_count / $list_count)));
$last_index = $total_count - (($page - 1) * $list_count);
@ -531,6 +531,7 @@ class DB
$result = array();
$index = $last_index;
$step = $last_index !== 0 ? -1 : 1;
$count = 0;
$result_class = ($result_class && $result_class !== 'master') ? $result_class : 'stdClass';
if (!class_exists($result_class))
{
@ -540,6 +541,11 @@ class DB
{
$result[$index] = $row;
$index += $step;
$count++;
if ($count === 10000 && $this->_query_id !== '')
{
trigger_error('XML query ' . $this->_query_id . ' returned 10000 rows or more', E_USER_WARNING);
}
}
$stmt->closeCursor();

View file

@ -968,7 +968,7 @@ class Debug
case \E_USER_ERROR: return 'User Error';
case \E_USER_WARNING: return 'Warning';
case \E_USER_NOTICE: return 'Notice';
case \E_STRICT: return 'Strict Standards';
case 2048: return 'Strict Standards'; /* E_STRICT is deprecated */
case \E_PARSE: return 'Parse Error';
case \E_DEPRECATED: return 'Deprecated';
case \E_USER_DEPRECATED: return 'User Deprecated';

View file

@ -215,7 +215,10 @@ class HTTP
$proxy = parse_url($proxy);
$proxy_scheme = preg_match('/^(https|socks)/', $proxy['scheme'] ?? '') ? ($proxy['scheme'] . '://') : 'http://';
$proxy_auth = (!empty($proxy['user']) && !empty($proxy['pass'])) ? ($proxy['user'] . ':' . $proxy['pass'] . '@') : '';
$settings['proxy'] = sprintf('%s%s%s:%s', $proxy_scheme, $proxy_auth, $proxy['host'], $proxy['port']);
if (!empty($proxy['host']) && !empty($proxy['port']))
{
$settings['proxy'] = sprintf('%s%s%s:%s', $proxy_scheme, $proxy_auth, $proxy['host'], $proxy['port']);
}
}
self::$_client = new \GuzzleHttp\Client($settings);

View file

@ -25,7 +25,7 @@ class Queue
}
/**
* Get the default driver.
* Get a Queue driver instance.
*
* @param string $name
* @return ?Drivers\QueueInterface
@ -49,6 +49,16 @@ class Queue
}
}
/**
* Get the DB driver instance, for managing scheduled tasks.
*
* @return Drivers\Queue\DB
*/
public static function getDbDriver(): Drivers\Queue\DB
{
return self::getDriver('db');
}
/**
* Get the list of supported Queue drivers.
*
@ -86,7 +96,9 @@ class Queue
}
/**
* Add a task.
* Add a task to the queue.
*
* The queued task will be executed as soon as possible.
*
* The handler can be in one of the following formats:
* - Global function, e.g. myHandler
@ -128,29 +140,82 @@ class Queue
}
/**
* Get the first task to execute immediately.
* Add a task to be executed at a specific time.
*
* If no tasks are pending, this method will return null.
* Detailed scheduling of tasks will be handled by each driver.
* The queued task will be executed once at the configured time.
* The rest is identical to addTask().
*
* @param int $blocking
* @param int $time
* @param string $handler
* @param ?object $args
* @param ?object $options
* @return int
*/
public static function addTaskAt(int $time, string $handler, ?object $args = null, ?object $options = null): int
{
if (!config('queue.enabled'))
{
throw new Exceptions\FeatureDisabled('Queue not configured');
}
// This feature always uses the DB driver.
$driver = self::getDbDriver();
return $driver->addTaskAt($time, $handler, $args, $options);
}
/**
* Add a task to be executed at an interval.
*
* The queued task will be executed repeatedly at the scheduled interval.
* The synax for specifying the interval is the same as crontab.
* The rest is identical to addTask().
*
* @param string $interval
* @param string $handler
* @param ?object $args
* @param ?object $options
* @return int
*/
public static function addTaskAtInterval(string $interval, string $handler, ?object $args = null, ?object $options = null): int
{
if (!config('queue.enabled'))
{
throw new Exceptions\FeatureDisabled('Queue not configured');
}
// Validate the interval syntax.
if (!self::checkIntervalSyntax($interval))
{
throw new Exceptions\InvalidRequest('Invalid interval syntax: ' . $interval);
}
// This feature always uses the DB driver.
$driver = self::getDbDriver();
return $driver->addTaskAtInterval($interval, $handler, $args, $options);
}
/**
* Get information about a scheduled task if it exists.
*
* @param int $task_srl
* @return ?object
*/
public static function getTask(int $blocking = 0): ?object
public static function getScheduledTask(int $task_srl): ?object
{
$driver_name = config('queue.driver');
if (!$driver_name)
{
throw new Exceptions\FeatureDisabled('Queue not configured');
}
$driver = self::getDbDriver();
return $driver->getScheduledTask($task_srl);
}
$driver = self::getDriver($driver_name);
if (!$driver)
{
throw new Exceptions\FeatureDisabled('Queue not configured');
}
return $driver->getTask($blocking);
/**
* Cancel a scheduled task.
*
* @param int $task_srl
* @return bool
*/
public static function cancelScheduledTask(int $task_srl): bool
{
$driver = self::getDbDriver();
return $driver->cancelScheduledTask($task_srl);
}
/**
@ -165,7 +230,80 @@ class Queue
}
/**
* Process the queue.
* Check the interval syntax.
*
* This method returns true if the interval string is well-formed,
* and false otherwise. However, it does not check that all the numbers
* are in the correct range (e.g. 0-59 for minutes).
*
* @param string $interval
* @return bool
*/
public static function checkIntervalSyntax(string $interval): bool
{
$parts = preg_split('/\s+/', $interval);
if (!$parts || count($parts) !== 5)
{
return false;
}
foreach ($parts as $part)
{
if (!preg_match('!^(?:\\*(?:/\d+)?|\d+(?:-\d+)?(?:,\d+(?:-\d+)?)*)$!', $part))
{
return false;
}
}
return true;
}
/**
* Parse an interval string check it against a timestamp.
*
* This method returns true if the interval covers the given timestamp,
* and false otherwise.
*
* @param string $interval
* @param ?int $time
* @return bool
*/
public static function parseInterval(string $interval, ?int $time): bool
{
$parts = preg_split('/\s+/', $interval);
if (!$parts || count($parts) !== 5)
{
return false;
}
$current_time = explode(' ', date('i G j n w', $time ?? time()));
foreach ($parts as $i => $part)
{
$subparts = explode(',', $part);
foreach ($subparts as $subpart)
{
if ($subpart === '*' || ltrim($subpart, '0') === ltrim($current_time[$i], '0'))
{
continue 2;
}
if ($subpart === '7' && $i === 4 && intval($current_time[$i], 10) === 0)
{
continue 2;
}
if (preg_match('!^\\*/(\d+)?$!', $subpart, $matches) && ($div = intval($matches[1], 10)) && (intval($current_time[$i], 10) % $div === 0))
{
continue 2;
}
if (preg_match('!^(\d+)-(\d+)$!', $subpart, $matches) && intval($current_time[$i], 10) >= intval($matches[1], 10) && intval($current_time[$i], 10) <= intval($matches[2], 10))
{
continue 2;
}
}
return false;
}
return true;
}
/**
* Process queued and scheduled tasks.
*
* This will usually be called by a separate script, run every minute
* through an external scheduler such as crontab or systemd.
@ -173,115 +311,54 @@ class Queue
* If you are on a shared hosting service, you may also call a URL
* using a "web cron" service provider.
*
* @param int $index
* @param int $count
* @param int $timeout
* @return void
*/
public static function process(int $timeout): void
public static function process(int $index, int $count, int $timeout): void
{
// This part will run in a loop until timeout.
$process_start_time = microtime(true);
// Get default driver instance.
$driver_name = config('queue.driver');
$driver = self::getDriver($driver_name);
if (!$driver_name || !$driver)
{
throw new Exceptions\FeatureDisabled('Queue not configured');
}
// Process scheduled tasks.
if ($index === 0)
{
$db_driver = self::getDbDriver();
$tasks = $db_driver->getScheduledTasks('once');
foreach ($tasks as $task)
{
self::_executeTask($task);
}
}
if ($index === 1 || $count < 2)
{
$db_driver = self::getDbDriver();
$tasks = $db_driver->getScheduledTasks('interval');
foreach ($tasks as $task)
{
$db_driver->updateLastRunTimestamp($task);
self::_executeTask($task);
}
}
// Process queued tasks.
while (true)
{
// Get a task from the driver.
// Get a task from the driver, with a 1 second delay at maximum.
$loop_start_time = microtime(true);
$task = self::getTask(1);
// Wait 1 second and loop back.
$task = $driver->getNextTask(1);
if ($task)
{
// Find the handler for the task.
$task->handler = trim($task->handler, '\\()');
$handler = null;
try
{
if (preg_match('/^(?:\\\\)?([\\\\\\w]+)::(\\w+)$/', $task->handler, $matches))
{
$class_name = '\\' . $matches[1];
$method_name = $matches[2];
if (class_exists($class_name) && method_exists($class_name, $method_name))
{
$handler = [$class_name, $method_name];
}
else
{
error_log('RxQueue: task handler not found: ' . $task->handler);
}
}
elseif (preg_match('/^(?:\\\\)?([\\\\\\w]+)::(\\w+)(?:\(\))?->(\\w+)$/', $task->handler, $matches))
{
$class_name = '\\' . $matches[1];
$initializer_name = $matches[2];
$method_name = $matches[3];
if (class_exists($class_name) && method_exists($class_name, $initializer_name))
{
$obj = $class_name::$initializer_name();
if (method_exists($obj, $method_name))
{
$handler = [$obj, $method_name];
}
else
{
error_log('RxQueue: task handler not found: ' . $task->handler);
}
}
else
{
error_log('RxQueue: task handler not found: ' . $task->handler);
}
}
elseif (preg_match('/^new (?:\\\\)?([\\\\\\w]+)(?:\(\))?->(\\w+)$/', $task->handler, $matches))
{
$class_name = '\\' . $matches[1];
$method_name = $matches[2];
if (class_exists($class_name) && method_exists($class_name, $method_name))
{
$obj = new $class_name();
$handler = [$obj, $method_name];
}
else
{
error_log('RxQueue: task handler not found: ' . $task->handler);
}
}
else
{
if (function_exists('\\' . $task->handler))
{
$handler = '\\' . $task->handler;
}
else
{
error_log('RxQueue: task handler not found: ' . $task->handler);
}
}
}
catch (\Throwable $th)
{
error_log(vsprintf('RxQueue: task handler %s could not be accessed: %s in %s:%d', [
$task->handler,
get_class($th),
$th->getFile(),
$th->getLine(),
]));
}
// Call the handler.
try
{
if ($handler)
{
call_user_func($handler, $task->args, $task->options);
}
}
catch (\Throwable $th)
{
error_log(vsprintf('RxQueue: task handler %s threw %s in %s:%d', [
$task->handler,
get_class($th),
$th->getFile(),
$th->getLine(),
]));
}
self::_executeTask($task);
}
// If the timeout is imminent, break the loop.
@ -299,4 +376,107 @@ class Queue
}
}
}
/**
* Execute a task.
*
* @param object $task
* @return void
*/
protected static function _executeTask(object $task): void
{
// Find the handler for the task.
$task->handler = trim($task->handler, '\\()');
$handler = null;
try
{
if (preg_match('/^(?:\\\\)?([\\\\\\w]+)::(\\w+)$/', $task->handler, $matches))
{
$class_name = '\\' . $matches[1];
$method_name = $matches[2];
if (class_exists($class_name) && method_exists($class_name, $method_name))
{
$handler = [$class_name, $method_name];
}
else
{
error_log('RxQueue: task handler not found: ' . $task->handler);
}
}
elseif (preg_match('/^(?:\\\\)?([\\\\\\w]+)::(\\w+)(?:\(\))?->(\\w+)$/', $task->handler, $matches))
{
$class_name = '\\' . $matches[1];
$initializer_name = $matches[2];
$method_name = $matches[3];
if (class_exists($class_name) && method_exists($class_name, $initializer_name))
{
$obj = $class_name::$initializer_name();
if (method_exists($obj, $method_name))
{
$handler = [$obj, $method_name];
}
else
{
error_log('RxQueue: task handler not found: ' . $task->handler);
}
}
else
{
error_log('RxQueue: task handler not found: ' . $task->handler);
}
}
elseif (preg_match('/^new (?:\\\\)?([\\\\\\w]+)(?:\(\))?->(\\w+)$/', $task->handler, $matches))
{
$class_name = '\\' . $matches[1];
$method_name = $matches[2];
if (class_exists($class_name) && method_exists($class_name, $method_name))
{
$obj = new $class_name();
$handler = [$obj, $method_name];
}
else
{
error_log('RxQueue: task handler not found: ' . $task->handler);
}
}
else
{
if (function_exists('\\' . $task->handler))
{
$handler = '\\' . $task->handler;
}
else
{
error_log('RxQueue: task handler not found: ' . $task->handler);
}
}
}
catch (\Throwable $th)
{
error_log(vsprintf('RxQueue: task handler %s could not be accessed: %s in %s:%d', [
$task->handler,
get_class($th),
$th->getFile(),
$th->getLine(),
]));
}
// Call the handler.
try
{
if ($handler)
{
call_user_func($handler, $task->args, $task->options);
}
}
catch (\Throwable $th)
{
error_log(vsprintf('RxQueue: task handler %s threw %s in %s:%d', [
$task->handler,
get_class($th),
$th->getFile(),
$th->getLine(),
]));
}
}
}

View file

@ -293,7 +293,7 @@ class Session
\Context::displayErrorPage('SSO Error', 'ERR_INVALID_SSO_REQUEST', 400);
exit;
}
if (!URL::isInternalUrl($sso_request) || !URL::isInternalURL($_SERVER['HTTP_REFERER']))
if (!URL::isInternalUrl($sso_request) || !URL::isInternalURL($_SERVER['HTTP_REFERER'] ?? ''))
{
\Context::displayErrorPage('SSO Error', 'ERR_INVALID_SSO_REQUEST', 400);
exit;

View file

@ -260,10 +260,10 @@ class Storage
* @param string $filename
* @param string|resource $content
* @param string $mode (optional)
* @param int $perms (optional)
* @param ?int $perms (optional)
* @return string|false
*/
public static function write(string $filename, $content, string $mode = 'w', int $perms = null)
public static function write(string $filename, $content, string $mode = 'w', ?int $perms = null)
{
$filename = rtrim($filename, '/\\');
$destination_dir = dirname($filename);

View file

@ -694,12 +694,12 @@ class Template
}
// If any of the variables seems to be an array or object, it's $vars.
if (!is_scalar($media_type))
if (!is_scalar($media_type ?? ''))
{
$vars = $media_type;
$media_type = null;
}
if (!is_scalar($index))
if (!is_scalar($index ?? ''))
{
$vars = $index;
$index = null;

View file

@ -62,10 +62,10 @@ interface QueueInterface
public function addTask(string $handler, ?object $args = null, ?object $options = null): int;
/**
* Get the first task.
* Get the next task from the queue.
*
* @param int $blocking
* @return ?object
*/
public function getTask(int $blocking = 0): ?object;
public function getNextTask(int $blocking = 0): ?object;
}

View file

@ -4,6 +4,7 @@ namespace Rhymix\Framework\Drivers\Queue;
use Rhymix\Framework\DB as RFDB;
use Rhymix\Framework\Drivers\QueueInterface;
use Rhymix\Framework\Queue;
/**
* The DB queue driver.
@ -99,12 +100,72 @@ class DB implements QueueInterface
}
/**
* Get the first task.
* Add a task to be executed at a specific time.
*
* @param int $time
* @param string $handler
* @param ?object $args
* @param ?object $options
* @return int
*/
public function addTaskAt(int $time, string $handler, ?object $args = null, ?object $options = null): int
{
$oDB = RFDB::getInstance();
$task_srl = getNextSequence();
$stmt = $oDB->prepare(trim(<<<END
INSERT INTO task_schedule
(task_srl, task_type, first_run, handler, args, options, regdate)
VALUES (?, ?, ?, ?, ?, ?, ?)
END));
$result = $stmt->execute([
$task_srl,
'once',
date('Y-m-d H:i:s', $time),
$handler,
serialize($args),
serialize($options),
date('Y-m-d H:i:s'),
]);
return $result ? $task_srl : 0;
}
/**
* Add a task to be executed at an interval.
*
* @param string $interval
* @param string $handler
* @param ?object $args
* @param ?object $options
* @return int
*/
public function addTaskAtInterval(string $interval, string $handler, ?object $args = null, ?object $options = null): int
{
$oDB = RFDB::getInstance();
$task_srl = getNextSequence();
$stmt = $oDB->prepare(trim(<<<END
INSERT INTO task_schedule
(task_srl, task_type, run_interval, handler, args, options, regdate)
VALUES (?, ?, ?, ?, ?, ?, ?)
END));
$result = $stmt->execute([
$task_srl,
'interval',
$interval,
$handler,
serialize($args),
serialize($options),
date('Y-m-d H:i:s'),
]);
return $result ? $task_srl : 0;
}
/**
* Get the next task from the queue.
*
* @param int $blocking
* @return ?object
*/
public function getTask(int $blocking = 0): ?object
public function getNextTask(int $blocking = 0): ?object
{
$oDB = RFDB::getInstance();
$oDB->beginTransaction();
@ -128,4 +189,123 @@ class DB implements QueueInterface
return null;
}
}
/**
* Get a scheduled task by its task_srl.
*
* @param int $task_srl
* @return ?object
*/
public function getScheduledTask(int $task_srl): ?object
{
$oDB = RFDB::getInstance();
$stmt = $oDB->query('SELECT * FROM task_schedule WHERE task_srl = ?', [$task_srl]);
$task = $stmt->fetchObject();
$stmt->closeCursor();
if ($task)
{
$task->args = unserialize($task->args);
$task->options = unserialize($task->options);
return $task;
}
else
{
return null;
}
}
/**
* Get scheduled tasks.
*
* @param string $type
* @return array
*/
public function getScheduledTasks(string $type): array
{
$oDB = RFDB::getInstance();
$tasks = [];
$task_srls = [];
// Get tasks to be executed once at the current time.
if ($type === 'once')
{
$oDB->beginTransaction();
$timestamp = date('Y-m-d H:i:s');
$stmt = $oDB->query("SELECT * FROM task_schedule WHERE task_type = 'once' AND first_run <= ? ORDER BY first_run FOR UPDATE", [$timestamp]);
while ($task = $stmt->fetchObject())
{
$task->args = unserialize($task->args);
$task->options = unserialize($task->options);
$tasks[] = $task;
$task_srls[] = $task->task_srl;
}
if (count($task_srls))
{
$stmt = $oDB->prepare('DELETE FROM task_schedule WHERE task_srl IN (' . implode(', ', array_fill(0, count($task_srls), '?')) . ')');
$stmt->execute($task_srls);
}
$oDB->commit();
}
// Get tasks to be executed at an interval.
if ($type === 'interval')
{
$stmt = $oDB->query("SELECT task_srl, run_interval FROM task_schedule WHERE task_type = 'interval' ORDER BY task_srl");
while ($task = $stmt->fetchObject())
{
if (Queue::parseInterval($task->run_interval, time()))
{
$task_srls[] = $task->task_srl;
}
}
if (count($task_srls))
{
$stmt = $oDB->prepare('SELECT * FROM task_schedule WHERE task_srl IN (' . implode(', ', array_fill(0, count($task_srls), '?')) . ')');
$stmt->execute($task_srls);
while ($task = $stmt->fetchObject())
{
$task->args = unserialize($task->args);
$task->options = unserialize($task->options);
$tasks[] = $task;
}
}
}
return $tasks;
}
/**
* Update the last executed timestamp of a scheduled task.
*
* @param object $task
* @return void
*/
public function updateLastRunTimestamp(object $task): void
{
$oDB = RFDB::getInstance();
if ($task->first_run)
{
$stmt = $oDB->prepare('UPDATE task_schedule SET last_run = ?, run_count = run_count + 1 WHERE task_srl = ?');
$stmt->execute([date('Y-m-d H:i:s'), $task->task_srl]);
}
else
{
$stmt = $oDB->prepare('UPDATE task_schedule SET first_run = ?, last_run = ?, run_count = run_count + 1 WHERE task_srl = ?');
$stmt->execute([date('Y-m-d H:i:s'), date('Y-m-d H:i:s'), $task->task_srl]);
}
}
/**
* Cancel a scheduled task.
*
* @param int $task_srl
* @return bool
*/
public function cancelScheduledTask(int $task_srl): bool
{
$oDB = RFDB::getInstance();
$stmt = $oDB->query('DELETE FROM task_schedule WHERE task_srl = ?', [$task_srl]);
return ($stmt && $stmt->rowCount()) ? true : false;
}
}

View file

@ -105,12 +105,12 @@ class Dummy implements QueueInterface
}
/**
* Get the first task.
* Get the next task from the queue.
*
* @param int $blocking
* @return ?object
*/
public function getTask(int $blocking = 0): ?object
public function getNextTask(int $blocking = 0): ?object
{
$result = $this->_dummy_queue;
$this->_dummy_queue = null;

View file

@ -155,12 +155,12 @@ class Redis implements QueueInterface
}
/**
* Get the first task.
* Get the next task from the queue.
*
* @param int $blocking
* @return ?object
*/
public function getTask(int $blocking = 0): ?object
public function getNextTask(int $blocking = 0): ?object
{
if ($this->_conn)
{

View file

@ -101,8 +101,8 @@ class FilenameFilter
{
$images = 'gif|jpe?g|jfif|png|webp';
$audios = 'mp3|wav|ogg|flac|aac';
$videos = 'mp4|webm|ogv';
$legacy = 'avi|as[fx]|flv|m4[av]|midi?|mkv|moo?v|mpe?g|qt|r[am]m?|wm[av]';
$videos = 'mp4|webm';
$legacy = 'avi|as[fx]|flv|m4[av]|midi?|mkv|moo?v|mpe?g|qt|r[am]m?|wm[av]|ogv';
if ($include_multimedia)
{

View file

@ -135,7 +135,7 @@ class IpFilter
*/
public static function getCloudFlareRealIP()
{
if (!isset($_SERVER['HTTP_CF_CONNECTING_IP']))
if (!isset($_SERVER['HTTP_CF_CONNECTING_IP']) || isset($_SERVER['HTTP_CF_WORKER']))
{
return false;
}

View file

@ -198,6 +198,7 @@ class DBQueryParser extends BaseParser
$orderby->default = ($attribs['default'] ?? null) ?: null;
$orderby->order_var = ($attribs['order'] ?? null) ?: null;
$orderby->order_default = strtoupper($attribs['orderdefault'] ?? '') === 'DESC' ? 'DESC' : 'ASC';
$orderby->ifvar = trim($attribs['if'] ?? '') ?: null;
$query->navigation->orderby[] = $orderby;
}
foreach (['list_count', 'page_count', 'page', 'offset'] as $key)
@ -207,6 +208,7 @@ class DBQueryParser extends BaseParser
$query->navigation->{$key} = new DBQuery\VariableBase;
$query->navigation->{$key}->var = trim($tag['var'] ?? '') ?: null;
$query->navigation->{$key}->default = trim($tag['default'] ?? '') ?: null;
$query->navigation->{$key}->ifvar = trim($tag['if'] ?? '') ?: null;
}
}
}

View file

@ -11,4 +11,5 @@ class OrderBy extends VariableBase
public $default;
public $order_var;
public $order_default = 'ASC';
public $ifvar;
}

View file

@ -245,7 +245,11 @@ class Query extends VariableBase
// Compose the ORDER BY clause.
if ($this->navigation && count($this->navigation->orderby) && !$count_only)
{
$result .= ' ORDER BY ' . $this->_arrangeOrderBy($this->navigation);
$order_by = $this->_arrangeOrderBy($this->navigation);
if ($order_by !== '')
{
$result .= ' ORDER BY ' . $order_by;
}
}
// Compose the LIMIT/OFFSET clause.
@ -607,9 +611,15 @@ class Query extends VariableBase
// Process each column definition.
foreach ($navigation->orderby as $orderby)
{
// Skip
if ($orderby->ifvar && empty($this->_args[$orderby->ifvar]))
{
continue;
}
// Get the name of the column or expression to order by.
$column_name = '';
list($is_expression, $column_name) = $orderby->getValue($this->_args);
list($column_name, $is_expression, $is_default_value) = $orderby->getValue($this->_args);
if (!$column_name)
{
continue;
@ -618,6 +628,10 @@ class Query extends VariableBase
{
$column_name = self::quoteName($column_name);
}
elseif (!$is_default_value)
{
continue;
}
// Get the ordering (ASC or DESC).
if (preg_match('/^(ASC|DESC)$/i', $orderby->order_var ?: '', $matches))
@ -648,23 +662,34 @@ class Query extends VariableBase
*/
protected function _arrangeLimitOffset(Navigation $navigation): string
{
$list_count = 0;
$page = 0;
$offset = 0;
// Get the list count.
list($is_expression, $list_count) = $navigation->list_count->getValue($this->_args);
if (!$navigation->list_count->ifvar || !empty($this->_args[$navigation->list_count->ifvar]))
{
$list_count = $navigation->list_count->getValue($this->_args)[0];
}
if ($list_count <= 0)
{
return '';
}
$page = 0;
$offset = 0;
// Get the offset from the page or offset variable.
if ($navigation->page)
{
list($is_expression, $page) = $navigation->page->getValue($this->_args);
if (!$navigation->page->ifvar || !empty($this->_args[$navigation->page->ifvar]))
{
$page = $navigation->page->getValue($this->_args)[0];
}
}
if ($navigation->offset)
{
list($is_expression, $offset) = $navigation->offset->getValue($this->_args);
if (!$navigation->offset->ifvar || !empty($this->_args[$navigation->offset->ifvar]))
{
$offset = $navigation->offset->getValue($this->_args)[0];
}
}
// If page is available, set the offset and require pagination for this query.

View file

@ -281,11 +281,17 @@ class VariableBase
/**
* Get the current value, falling back to the default value if necessary.
*
* Format of return value: [value, is_expression, is_default_value]
*
* @param array $args
* @return array
*/
public function getValue(array $args): array
{
$value = null;
$is_expression = false;
$is_default_value = false;
if ($this->var && Query::isValidVariable($args[$this->var] ?? null, $this instanceof ColumnWrite))
{
if ($args[$this->var] === '')
@ -293,35 +299,32 @@ class VariableBase
if ($this instanceof ColumnWrite)
{
$value = $args[$this->var];
$is_expression = false;
}
else
{
list($is_expression, $value) = $this->getDefaultValue();
$is_default_value = true;
}
}
else
{
$is_expression = false;
$value = $args[$this->var];
}
}
elseif ($this->default !== null)
{
list($is_expression, $value) = $this->getDefaultValue();
}
else
{
$is_expression = null;
$value = null;
$is_default_value = true;
}
return [$is_expression, $value];
return [$value, $is_expression, $is_default_value];
}
/**
* Get the default value of this variable.
*
* Format of return value: [is_expression, value]
*
* @return array
*/
public function getDefaultValue(): array

View file

@ -692,7 +692,7 @@ function utf8_normalize_spaces($str, bool $multiline = false): string
*/
function utf8_trim($str): string
{
return preg_replace('/^[\s\pZ\pC]+|[\s\pZ\pC]+$/u', '', (string)$str);
return preg_replace('/^[\s\pZ\pC]+|[\s\pZ\pC]+$/u', '', (string)$str) ?? '';
}
/**

View file

@ -181,7 +181,7 @@
else if (filename.match(/\.(mp3|wav|ogg|flac|aac)$/i) && opt.autoinsertTypes.audio) {
temp_code = self.generateHtml($container, result);
}
else if (filename.match(/\.(mp4|webm|ogv)$/i) && opt.autoinsertTypes.video) {
else if (filename.match(/\.(mp4|webm)$/i) && opt.autoinsertTypes.video) {
temp_code = self.generateHtml($container, result);
}
@ -365,7 +365,7 @@
html = '<audio src="' + file.download_url + '" controls' +
' data-file-srl="' + file.file_srl + '" />';
}
else if (filename.match(/\.(mp4|webm|ogv)$/i)) {
else if (filename.match(/\.(mp4|webm)$/i)) {
if (file.original_type === 'image/gif') {
html = '<video src="' + file.download_url + '"'
+ ' autoplay loop muted playsinline data-file-srl="' + file.file_srl + '" />';
@ -519,7 +519,7 @@
if(file.thumbnail_filename) {
file.thumbnail_url = file.thumbnail_filename;
if(/\.(mp4|webm|ogv)$/i.test(file.source_filename)) {
if(/\.(mp4|webm)$/i.test(file.source_filename)) {
result_image.push(template_fileimte_video(file));
} else {
result_image.push(template_fileimte_image(file));

View file

@ -31,6 +31,8 @@ $lang->cmd_vote = 'Upvote';
$lang->cmd_vote_down = 'Downvote';
$lang->cmd_declare = 'Report';
$lang->cmd_cancel_declare = 'Cancel Report';
$lang->cmd_cancel_vote = 'Cancel Upvote';
$lang->cmd_cancel_vote_down = 'Cancel Downvote';
$lang->cmd_vote_config = 'Voting Configuration';
$lang->cmd_declare_config = 'Reporting Configuration';
$lang->cmd_declared_list = 'Reported List';
@ -150,6 +152,7 @@ $lang->colorset = 'Colorset';
$lang->mobile_colorset = 'Mobile Colorset';
$lang->extra_vars = 'Extra Input Fields';
$lang->domain = 'Domain Name';
$lang->deleted_domain = 'Deleted Domain';
$lang->url = 'URL';
$lang->document_url = 'Article URL';
$lang->trackback_url = 'Trackback URL';

View file

@ -152,6 +152,7 @@ $lang->colorset = '컬러셋';
$lang->mobile_colorset = '모바일 컬러셋';
$lang->extra_vars = '확장 변수';
$lang->domain = '도메인';
$lang->deleted_domain = '삭제된 도메인';
$lang->url = 'URL';
$lang->document_url = '게시글 주소';
$lang->trackback_url = '엮인글 주소';

View file

@ -495,7 +495,8 @@ function cut_str($string, $cut_size = 0, $tail = '...'): string
if(isset($GLOBALS['use_mb_strimwidth']) || function_exists('mb_strimwidth'))
{
$GLOBALS['use_mb_strimwidth'] = TRUE;
return mb_strimwidth($string, 0, $cut_size + 4, $tail, 'utf-8');
$string = html_entity_decode($string);
return escape(mb_strimwidth($string, 0, $cut_size + 4, $tail, 'utf-8'));
}
$chars = array(12, 4, 3, 5, 7, 7, 11, 8, 4, 5, 5, 6, 6, 4, 6, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 8, 6, 8, 6, 10, 8, 8, 9, 8, 8, 7, 9, 8, 3, 6, 7, 7, 11, 8, 9, 8, 9, 8, 8, 7, 8, 8, 10, 8, 8, 8, 6, 11, 6, 6, 6, 4, 7, 7, 7, 7, 7, 3, 7, 7, 3, 3, 6, 3, 9, 7, 7, 7, 7, 4, 7, 3, 7, 6, 10, 6, 6, 7, 6, 6, 6, 9);

View file

@ -73,7 +73,7 @@ if (PHP_SAPI === 'cli' && $process_count > 1 && function_exists('pcntl_fork') &&
}
elseif ($pid == 0)
{
Rhymix\Framework\Queue::process($timeout);
Rhymix\Framework\Queue::process($i, $process_count, $timeout);
exit;
}
else
@ -96,7 +96,7 @@ if (PHP_SAPI === 'cli' && $process_count > 1 && function_exists('pcntl_fork') &&
}
else
{
Rhymix\Framework\Queue::process($timeout);
Rhymix\Framework\Queue::process(0, 1, $timeout);
}
// If called over the network, display a simple OK message to indicate success.

View file

@ -24,7 +24,7 @@
<!-- JS -->
@foreach (Context::getJsFile('head', true) as $js_file)
<script src="{!! $js_file['file'] !!}"></script>
<script src="{!! $js_file['file'] !!}"{!! $js_file['attrs'] !!}></script>
@endforeach
<!-- RSS -->