Merge branch 'rhymix:develop' into develop

This commit is contained in:
Lastorder 2024-10-12 16:08:02 +09:00 committed by GitHub
commit 88b5281094
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
111 changed files with 2542 additions and 405 deletions

View file

@ -33,6 +33,7 @@
"ezyang/htmlpurifier": "4.16.*",
"google/auth": "1.26.*",
"guzzlehttp/guzzle": "7.8.*",
"guzzlehttp/psr7": "2.6.*",
"jbbcode/jbbcode": "1.4.*",
"leafo/lessphp": "dev-master",
"league/html-to-markdown": "5.1.*",

View file

@ -463,7 +463,9 @@ class DB
if ($this->isError())
{
return $this->getError();
$output = $this->getError();
$output->add('_count', $query_string);
return $output;
}
else
{
@ -474,11 +476,13 @@ class DB
catch (Exceptions\DBError $e)
{
$output = $this->setError(-1, $e->getMessage());
$output->add('_count', $query_string);
return $output;
}
catch (\PDOException $e)
{
$output = $this->setError(-1, $e->getMessage());
$output->add('_count', $query_string);
return $output;
}

View file

@ -560,6 +560,13 @@ class Mail
*/
public function send(): bool
{
// If queue is enabled, send asynchronously.
if (config('queue.enabled') && !defined('RXQUEUE_CRON'))
{
Queue::addTask(self::class . '::' . 'sendAsync', $this);
return true;
}
// Get caller information.
$backtrace = debug_backtrace(0);
if(count($backtrace) && isset($backtrace[0]['file']))
@ -600,6 +607,17 @@ class Mail
return $this->sent;
}
/**
* Send an email asynchronously (for Queue integration).
*
* @param self $mail
* @return void
*/
public static function sendAsync(self $mail): void
{
$mail->send();
}
/**
* Check if the message was sent.
*

View file

@ -402,6 +402,13 @@ class Push
*/
public function send(): bool
{
// If queue is enabled, send asynchronously.
if (config('queue.enabled') && !defined('RXQUEUE_CRON'))
{
Queue::addTask(self::class . '::' . 'sendAsync', $this);
return true;
}
// Get caller information.
$backtrace = debug_backtrace(0);
if(count($backtrace) && isset($backtrace[0]['file']))
@ -464,6 +471,17 @@ class Push
return $this->sent > 0 ? true : false;
}
/**
* Send asynchronously (for Queue integration).
*
* @param self $sms
* @return void
*/
public static function sendAsync(self $push): void
{
$push->send();
}
/**
* Get the device token
*

302
common/framework/Queue.php Normal file
View file

@ -0,0 +1,302 @@
<?php
namespace Rhymix\Framework;
/**
* The Queue class.
*/
class Queue
{
/**
* Static properties.
*/
protected static $_drivers = [];
/**
* Add a custom Queue driver.
*
* @param string $name
* @param object $driver
* @return void
*/
public static function addDriver(string $name, Drivers\QueueInterface $driver): void
{
self::$_drivers[$name] = $driver;
}
/**
* Get the default driver.
*
* @param string $name
* @return ?Drivers\QueueInterface
*/
public static function getDriver(string $name): ?Drivers\QueueInterface
{
if (isset(self::$_drivers[$name]))
{
return self::$_drivers[$name];
}
$driver_class = '\Rhymix\Framework\Drivers\Queue\\' . $name;
if (class_exists($driver_class))
{
$driver_config = config('queue.' . $name) ?: [];
return self::$_drivers[$name] = $driver_class::getInstance($driver_config);
}
else
{
return null;
}
}
/**
* Get the list of supported Queue drivers.
*
* @return array
*/
public static function getSupportedDrivers(): array
{
$result = [];
foreach (Storage::readDirectory(__DIR__ . '/drivers/queue', false) as $filename)
{
$driver_name = substr($filename, 0, -4);
$class_name = '\Rhymix\Framework\Drivers\Queue\\' . $driver_name;
if ($class_name::isSupported())
{
$result[$driver_name] = [
'name' => $class_name::getName(),
'required' => $class_name::getRequiredConfig(),
'optional' => $class_name::getOptionalConfig(),
];
}
}
foreach (self::$_drivers as $driver_name => $driver)
{
if ($driver->isSupported())
{
$result[$driver_name] = [
'name' => $driver->getName(),
'required' => $driver->getRequiredConfig(),
'optional' => $driver->getOptionalConfig(),
];
}
}
ksort($result);
return $result;
}
/**
* Add a task.
*
* The handler can be in one of the following formats:
* - Global function, e.g. myHandler
* - ClassName::staticMethodName
* - ClassName::getInstance()->methodName
* - new ClassName()->methodName
*
* Once identified and/or instantiated, the handler will be passed $args
* and $options, in that order. Each of them must be a single object.
*
* It is strongly recommended that you write a dedicated method to handle
* queued tasks, rather than reusing an existing method with a potentially
* incompatible structure. If you must to call an existing method,
* you should consider writing a wrapper.
*
* Any value returned by the handler will be discarded. If you throw an
* exception, it may be logged, but it will not cause a fatal error.
*
* @param string $handler
* @param ?object $args
* @param ?object $options
* @return int
*/
public static function addTask(string $handler, ?object $args = null, ?object $options = null): int
{
$driver_name = config('queue.driver');
if (!$driver_name)
{
throw new Exceptions\FeatureDisabled('Queue not configured');
}
$driver = self::getDriver($driver_name);
if (!$driver)
{
throw new Exceptions\FeatureDisabled('Queue not configured');
}
return $driver->addTask($handler, $args, $options);
}
/**
* Get the first task to execute immediately.
*
* If no tasks are pending, this method will return null.
* Detailed scheduling of tasks will be handled by each driver.
*
* @param int $blocking
* @return ?object
*/
public static function getTask(int $blocking = 0): ?object
{
$driver_name = config('queue.driver');
if (!$driver_name)
{
throw new Exceptions\FeatureDisabled('Queue not configured');
}
$driver = self::getDriver($driver_name);
if (!$driver)
{
throw new Exceptions\FeatureDisabled('Queue not configured');
}
return $driver->getTask($blocking);
}
/**
* Check the process key.
*
* @param string $key
* @return bool
*/
public static function checkKey(string $key): bool
{
return config('queue.key') === $key;
}
/**
* Process the queue.
*
* This will usually be called by a separate script, run every minute
* through an external scheduler such as crontab or systemd.
*
* If you are on a shared hosting service, you may also call a URL
* using a "web cron" service provider.
*
* @param int $timeout
* @return void
*/
public static function process(int $timeout): void
{
// This part will run in a loop until timeout.
$process_start_time = microtime(true);
while (true)
{
// Get a task from the driver.
$loop_start_time = microtime(true);
$task = self::getTask(1);
// Wait 1 second and loop back.
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(),
]));
}
}
// If the timeout is imminent, break the loop.
$process_elapsed_time = microtime(true) - $process_start_time;
if ($process_elapsed_time > $timeout - 2)
{
break;
}
// If there was no task, wait 1 second to make sure that the loop isn't too tight.
$loop_elapsed_time = microtime(true) - $loop_start_time;
if (!$task && $loop_elapsed_time < 1)
{
usleep(intval((1 - $loop_elapsed_time) * 1000000));
}
}
}
}

View file

@ -511,6 +511,13 @@ class SMS
*/
public function send(): bool
{
// If queue is enabled, send asynchronously.
if (config('queue.enabled') && !defined('RXQUEUE_CRON'))
{
Queue::addTask(self::class . '::' . 'sendAsync', $this);
return true;
}
// Get caller information.
$backtrace = debug_backtrace(0);
if(count($backtrace) && isset($backtrace[0]['file']))
@ -566,6 +573,17 @@ class SMS
return $this->sent;
}
/**
* Send an SMS asynchronously (for Queue integration).
*
* @param self $sms
* @return void
*/
public static function sendAsync(self $sms): void
{
$sms->send();
}
/**
* Check if the message was sent.
*

View file

@ -171,7 +171,7 @@ class UA
}
// Look for common search engine names and the 'bot' keyword.
if (preg_match('/bot|spider|crawler|archiver|wget|curl|php|slurp|wordpress|facebook|teoma|yeti|daum|apachebench|mediapartners-google|[(<+]https?:|@/i', $ua))
if (preg_match('/bot|spider|crawler|archiver|wget|curl|php|slurp|wordpress|facebook|external(agent|fetcher)|teoma|yeti|daum|apachebench|googleother|mediapartners-google|[(<+]https?:|@/i', $ua))
{
return self::$_robot_cache[$ua] = true;
}

View file

@ -0,0 +1,71 @@
<?php
namespace Rhymix\Framework\Drivers;
/**
* The Queue driver interface.
*/
interface QueueInterface
{
/**
* Create a new instance of the current Queue driver, using the given settings.
*
* @param array $config
* @return QueueInterface
*/
public static function getInstance(array $config): QueueInterface;
/**
* Get the human-readable name of this Queue driver.
*
* @return string
*/
public static function getName(): string;
/**
* Get the list of configuration fields required by this Queue driver.
*
* @return array
*/
public static function getRequiredConfig(): array;
/**
* Get the list of configuration fields optionally used by this Queue driver.
*
* @return array
*/
public static function getOptionalConfig(): array;
/**
* Check if this driver is supported on this server.
*
* @return bool
*/
public static function isSupported(): bool;
/**
* Validate driver configuration.
*
* @param mixed $config
* @return bool
*/
public static function validateConfig($config): bool;
/**
* Add a task.
*
* @param string $handler
* @param ?object $args
* @param ?object $options
* @return int
*/
public function addTask(string $handler, ?object $args = null, ?object $options = null): int;
/**
* Get the first task.
*
* @param int $blocking
* @return ?object
*/
public function getTask(int $blocking = 0): ?object;
}

View file

@ -0,0 +1,131 @@
<?php
namespace Rhymix\Framework\Drivers\Queue;
use Rhymix\Framework\DB as RFDB;
use Rhymix\Framework\Drivers\QueueInterface;
/**
* The DB queue driver.
*/
class DB implements QueueInterface
{
/**
* Create a new instance of the current Queue driver, using the given settings.
*
* @param array $config
* @return QueueInterface
*/
public static function getInstance(array $config): QueueInterface
{
return new self($config);
}
/**
* Get the human-readable name of this Queue driver.
*
* @return string
*/
public static function getName(): string
{
return 'DB';
}
/**
* Get the list of configuration fields required by this Queue driver.
*
* @return array
*/
public static function getRequiredConfig(): array
{
return [];
}
/**
* Get the list of configuration fields optionally used by this Queue driver.
*
* @return array
*/
public static function getOptionalConfig(): array
{
return [];
}
/**
* Check if this driver is supported on this server.
*
* @return bool
*/
public static function isSupported(): bool
{
return true;
}
/**
* Validate driver configuration.
*
* @param mixed $config
* @return bool
*/
public static function validateConfig($config): bool
{
return true;
}
/**
* Constructor.
*
* @param array $config
*/
public function __construct(array $config)
{
}
/**
* Add a task.
*
* @param string $handler
* @param ?object $args
* @param ?object $options
* @return int
*/
public function addTask(string $handler, ?object $args = null, ?object $options = null): int
{
$oDB = RFDB::getInstance();
$stmt = $oDB->prepare('INSERT INTO task_queue (handler, args, options) VALUES (?, ?, ?)');
$result = $stmt->execute([$handler, serialize($args), serialize($options)]);
return $result ? $oDB->getInsertID() : 0;
}
/**
* Get the first task.
*
* @param int $blocking
* @return ?object
*/
public function getTask(int $blocking = 0): ?object
{
$oDB = RFDB::getInstance();
$oDB->beginTransaction();
$stmt = $oDB->query('SELECT * FROM task_queue ORDER BY id LIMIT 1 FOR UPDATE');
$result = $stmt->fetchObject();
$stmt->closeCursor();
if ($result)
{
$stmt = $oDB->prepare('DELETE FROM task_queue WHERE id = ?');
$stmt->execute([$result->id]);
$oDB->commit();
$result->args = unserialize($result->args);
$result->options = unserialize($result->options);
return $result;
}
else
{
$oDB->commit();
return null;
}
}
}

View file

@ -0,0 +1,119 @@
<?php
namespace Rhymix\Framework\Drivers\Queue;
use Rhymix\Framework\Drivers\QueueInterface;
/**
* The Dummy queue driver.
*/
class Dummy implements QueueInterface
{
/**
* Dummy queue for testing.
*/
protected $_dummy_queue;
/**
* Create a new instance of the current Queue driver, using the given settings.
*
* @param array $config
* @return QueueInterface
*/
public static function getInstance(array $config): QueueInterface
{
return new self($config);
}
/**
* Get the human-readable name of this Queue driver.
*
* @return string
*/
public static function getName(): string
{
return 'Dummy';
}
/**
* Get the list of configuration fields required by this Queue driver.
*
* @return array
*/
public static function getRequiredConfig(): array
{
return [];
}
/**
* Get the list of configuration fields optionally used by this Queue driver.
*
* @return array
*/
public static function getOptionalConfig(): array
{
return [];
}
/**
* Check if this driver is supported on this server.
*
* @return bool
*/
public static function isSupported(): bool
{
return true;
}
/**
* Validate driver configuration.
*
* @param mixed $config
* @return bool
*/
public static function validateConfig($config): bool
{
return true;
}
/**
* Constructor.
*
* @param array $config
*/
public function __construct(array $config)
{
}
/**
* Add a task.
*
* @param string $handler
* @param ?object $args
* @param ?object $options
* @return int
*/
public function addTask(string $handler, ?object $args = null, ?object $options = null): int
{
$this->_dummy_queue = (object)[
'handler' => $handler,
'args' => $args,
'options' => $options,
];
return 0;
}
/**
* Get the first task.
*
* @param int $blocking
* @return ?object
*/
public function getTask(int $blocking = 0): ?object
{
$result = $this->_dummy_queue;
$this->_dummy_queue = null;
return $result;
}
}

View file

@ -0,0 +1,197 @@
<?php
namespace Rhymix\Framework\Drivers\Queue;
use Rhymix\Framework\Drivers\QueueInterface;
/**
* The Redis queue driver.
*/
class Redis implements QueueInterface
{
/**
* The Redis connection is stored here.
*/
protected $_conn;
protected $_key;
/**
* Create a new instance of the current Queue driver, using the given settings.
*
* @param array $config
* @return QueueInterface
*/
public static function getInstance(array $config): QueueInterface
{
return new self($config);
}
/**
* Get the human-readable name of this Queue driver.
*
* @return string
*/
public static function getName(): string
{
return 'Redis';
}
/**
* Get the list of configuration fields required by this Queue driver.
*
* @return array
*/
public static function getRequiredConfig(): array
{
return ['host', 'port'];
}
/**
* Get the list of configuration fields optionally used by this Queue driver.
*
* @return array
*/
public static function getOptionalConfig(): array
{
return ['dbnum', 'user', 'pass'];
}
/**
* Check if this driver is supported on this server.
*
* @return bool
*/
public static function isSupported(): bool
{
return class_exists('\\Redis');
}
/**
* Validate driver configuration.
*
* @param mixed $config
* @return bool
*/
public static function validateConfig($config): bool
{
try
{
$test = new \Redis;
$test->connect($config['host'], $config['port'] ?? 6379);
if (isset($config['user']) || isset($config['pass']))
{
$auth = [];
if (isset($config['user']) && $config['user']) $auth[] = $config['user'];
if (isset($config['pass']) && $config['pass']) $auth[] = $config['pass'];
$test->auth(count($auth) > 1 ? $auth : $auth[0]);
}
if (isset($config['dbnum']))
{
$test->select(intval($config['dbnum']));
}
return true;
}
catch (\Throwable $th)
{
return false;
}
}
/**
* Constructor.
*
* @param array $config
*/
public function __construct(array $config)
{
try
{
$this->_conn = new \Redis;
$this->_conn->connect($config['host'], $config['port'] ?? 6379);
if (isset($config['user']) || isset($config['pass']))
{
$auth = [];
if (isset($config['user']) && $config['user']) $auth[] = $config['user'];
if (isset($config['pass']) && $config['pass']) $auth[] = $config['pass'];
$this->_conn->auth(count($auth) > 1 ? $auth : $auth[0]);
}
if (isset($config['dbnum']))
{
$this->_conn->select(intval($config['dbnum']));
}
$this->_key = 'rxQueue_' . substr(hash_hmac('sha1', 'rxQueue_', config('crypto.authentication_key')), 0, 24);
}
catch (\RedisException $e)
{
$this->_conn = null;
}
}
/**
* Add a task.
*
* @param string $handler
* @param ?object $args
* @param ?object $options
* @return int
*/
public function addTask(string $handler, ?object $args = null, ?object $options = null): int
{
$value = serialize((object)[
'handler' => $handler,
'args' => $args,
'options' => $options,
]);
if ($this->_conn)
{
$result = $this->_conn->rPush($this->_key, $value);
return intval($result);
}
else
{
return 0;
}
}
/**
* Get the first task.
*
* @param int $blocking
* @return ?object
*/
public function getTask(int $blocking = 0): ?object
{
if ($this->_conn)
{
if ($blocking > 0)
{
$result = $this->_conn->blpop($this->_key, $blocking);
if (is_array($result) && isset($result[1]))
{
return unserialize($result[1]);
}
else
{
return null;
}
}
else
{
$result = $this->_conn->lpop($this->_key);
if ($result)
{
return unserialize($result);
}
else
{
return null;
}
}
}
else
{
return null;
}
}
}

View file

@ -132,6 +132,11 @@ class SolAPI extends Base implements \Rhymix\Framework\Drivers\SMSInterface
}
}
foreach ($original->getExtraVars() as $key => $value)
{
$options->$key = $value;
}
$data['messages'][] = $options;
}
}

View file

@ -15,6 +15,9 @@ class FilenameFilter
*/
public static function clean(string $filename): string
{
// Clean up unnecessary encodings.
$filename = strtr($filename, ['&amp;' => '&', '&#039;' => "'"]);
// Replace dangerous characters with safe alternatives, maintaining meaning as much as possible.
$illegal = array('\\', '/', '<', '>', '{', '}', ':', ';', '|', '"', '~', '`', '$', '%', '^', '*', '?');
$replace = array('', '', '(', ')', '(', ')', '_', ',', '_', '', '_', '\'', '_', '_', '_', '', '');
@ -31,9 +34,6 @@ class FilenameFilter
$filename = preg_replace('/__+/', '_', $filename);
$filename = preg_replace('/\.\.+/', '.', $filename);
// Clean up unnecessary encodings.
$filename = strtr($filename, array('&amp;' => '&'));
// Change .php files to .phps to make them non-executable.
if (strtolower(substr($filename, strlen($filename) - 4)) === '.php')
{

View file

@ -353,12 +353,12 @@
unselectNonImageFiles: function() {},
generateHtml: function($container, file) {
var filename = String(file.source_filename);
var filename = String(file.source_filename).escape();
var html = '';
var data = $container.data();
if (filename.match(/\.(gif|jpe?g|png|webp)$/i)) {
html = '<img src="' + file.download_url + '" alt="' + file.source_filename + '"'
html = '<img src="' + file.download_url + '" alt="' + filename + '"'
+ ' editor_component="image_link" data-file-srl="' + file.file_srl + '" />';
}
else if (filename.match(/\.(mp3|wav|ogg|flac|aac)$/i)) {
@ -388,7 +388,7 @@
}
if (html === '') {
html += '<a href="' + file.download_url + '" data-file-srl="' + file.file_srl + '">' + file.source_filename + '</a>\n';
html += '<a href="' + file.download_url + '" data-file-srl="' + file.file_srl + '">' + filename + '</a>\n';
}
return html;
@ -405,7 +405,6 @@
try {
var textarea = _getCkeContainer(data.editorSequence).find('.cke_source');
var editor = _getCkeInstance(data.editorSequence);
console.log(textarea, editor.mode);
if (textarea.length && editor.mode == 'source') {
var caretPosition = textarea[0].selectionStart;
var currentText = textarea[0].value;
@ -516,7 +515,7 @@
$container.data(data);
file.thumbnail_url = file.download_url;
file.source_filename = file.source_filename.replace("&amp;", "&");
file.source_filename = file.source_filename.replace("&amp;", "&").replace("&#039;", "'");
if(file.thumbnail_filename) {
file.thumbnail_url = file.thumbnail_filename;

View file

@ -132,7 +132,6 @@ $.fn.simpleTree = function(opt){
}
});
}
}
};
TREE.setTreeNodes = function(obj, useParent){
@ -226,10 +225,7 @@ $.fn.simpleTree = function(opt){
trigger.click(function(event){
TREE.nodeToggle(node);
});
if(!$.browser.msie)
{
trigger.css('float','left');
}
trigger.css('float','left');
};
TREE.dragStart = function(event){
var LI = $(event.data.LI);

View file

@ -214,11 +214,13 @@
var url = XE.URI(request_uri).pathname();
var action_parts = action.split('.');
var request_info;
if (action === 'raw') {
if (params instanceof FormData) {
request_info = (params.get('module') || params.get('mid')) + '.' + params.get('act');
} else if (action === 'raw') {
request_info = 'RAW FORM SUBMISSION';
} else {
params = params ? ($.isArray(params) ? arr2obj(params) : params) : {};
//if (action_parts.length != 2) return;
params.module = action_parts[0];
params.act = action_parts[1];
request_info = params.module + "." + params.act;
@ -342,7 +344,7 @@
// Send the AJAX request.
try {
$.ajax({
var args = {
type: "POST",
dataType: "json",
url: url,
@ -351,7 +353,11 @@
headers : headers,
success : successHandler,
error : errorHandler
});
};
if (params instanceof FormData) {
args.contentType = false;
}
$.ajax(args);
} catch(e) {
alert(e);
return;
@ -376,7 +382,6 @@
* Function for AJAX submission of arbitrary forms.
*/
XE.ajaxForm = function(form, callback_success, callback_error) {
// Abort if the form already has a 'target' attribute.
form = $(form);
// Get success and error callback functions.
if (typeof callback_success === 'undefined') {
@ -402,43 +407,10 @@
callback_error = null;
}
}
// Set _rx_ajax_form flag
if (!form.find('input[name=_rx_ajax_form]').size()) {
form.append('<input type="hidden" name="_rx_ajax_form" value="json" />');
setTimeout(function() {
form.find('input[name=_rx_ajax_form]').remove();
}, 1000);
}
// If the form has file uploads, use a hidden iframe to submit. Otherwise use exec_json.
var has_files = form.find('input[type=file][name!=Filedata]').size();
if (has_files) {
var iframe_id = '_rx_temp_iframe_' + (new Date()).getTime();
$('<iframe id="' + iframe_id + '" name="' + iframe_id + '" style="display:none"></iframe>').appendTo($(document.body));
form.attr('method', 'POST').attr('enctype', 'multipart/form-data').attr('target', iframe_id);
form.find('input[name=_rx_ajax_form]').val(iframe_id);
window.XE.handleIframeResponse = function(iframe_id, data) {
if (data.error) {
if (callback_error) {
callback_error(data);
} else {
alert(data.message);
}
} else {
callback_success(data);
}
if (iframe_id.match(/^_rx_temp_iframe_[0-9]+$/)) {
$('iframe#' + iframe_id).remove();
}
};
setTimeout(function() {
form.removeAttr('target');
}, 1000);
form.submit();
} else {
window.exec_json('raw', form.serialize(), callback_success, callback_error);
}
window.exec_json('raw', new FormData(form[0]), callback_success, callback_error);
};
$(document).on('submit', 'form.rx_ajax', function(event) {
// Abort if the form already has a 'target' attribute.
if (!$(this).attr('target')) {
event.preventDefault();
XE.ajaxForm(this);

View file

@ -415,6 +415,7 @@ function legacy_filter(filter_name, form, module, act, callback, responses, conf
args[0] = filter_name;
args[1] = function(f) {
var hasFile = false;
var params = {}, res = [], elms = f.elements, data = $(f).serializeArray();
$.each(data, function(i, field) {
var v = $.trim(field.value), n = field.name;
@ -435,11 +436,20 @@ function legacy_filter(filter_name, form, module, act, callback, responses, conf
}
});
$(f).find('input[type=file][name^=extra_vars]').each(function() {
if (this.files && this.files[0]) {
params[this.name] = this.files[0];
hasFile = true;
}
});
if (confirm_msg && !confirm(confirm_msg)) return false;
//exec_xml(module, act, params, callback, responses, params, form);
params['module'] = module;
params['act'] = act;
params['_rx_ajax_compat'] = 'XMLRPC';
exec_json(module + '.' + act, params, function(result) {
var callback_wrapper = function(result) {
if (!result) {
result = {};
}
@ -454,7 +464,17 @@ function legacy_filter(filter_name, form, module, act, callback, responses, conf
});
callback(filtered_result, responses, params, form);
}
});
};
if (!hasFile) {
exec_json(module + '.' + act, params, callback_wrapper);
} else {
var fd = new FormData();
for (let key in params) {
fd.append(key, params[key]);
}
exec_json('raw', fd, callback_wrapper);
}
};
v.cast('ADD_CALLBACK', args);

View file

@ -328,6 +328,8 @@ $lang->column_type_list['language'] = 'Language';
$lang->column_type_list['date'] = 'Date';
$lang->column_type_list['time'] = 'Time';
$lang->column_type_list['timezone'] = 'Time zone';
$lang->column_type_list['number'] = 'Number';
$lang->column_type_list['file'] = 'File upload';
$lang->column_name = 'Column Name';
$lang->column_title = 'Column Title';
$lang->default_value = 'Default Value';

View file

@ -330,6 +330,8 @@ $lang->column_type_list['language'] = '언어';
$lang->column_type_list['date'] = '날짜';
$lang->column_type_list['time'] = '시간';
$lang->column_type_list['timezone'] = '표준 시간대';
$lang->column_type_list['number'] = '숫자';
$lang->column_type_list['file'] = '파일 업로드';
$lang->column_name = '입력항목 이름';
$lang->column_title = '입력항목 제목';
$lang->default_value = '기본값';

91
common/scripts/cron.php Normal file
View file

@ -0,0 +1,91 @@
<?php
/**
* This script runs the task queue.
*
* Unlike other scripts provided with Rhymix, it can be called
* both on the command line and over the network.
*/
define('RXQUEUE_CRON', true);
// If called on the CLI, run additional checks.
if (PHP_SAPI === 'cli')
{
require_once __DIR__ . '/common.php';
}
else
{
// If called over the network, load Rhymix directly.
chdir(dirname(dirname(__DIR__)));
require_once dirname(__DIR__) . '/autoload.php';
Context::init();
// On the other hand, we should check the key.
$key = (string)Context::get('key');
if (!Rhymix\Framework\Queue::checkKey($key))
{
Context::setCacheControl(0);
header('HTTP/1.1 403 Forbidden');
echo "Invalid key\n";
Context::close();
exit;
}
}
// Get queue configuration set by the administrator.
$timeout = (config('queue.interval') ?? 1) * 60;
$process_count = config('queue.process_count') ?? 1;
// If called over the network, try to increase the timeout.
if (PHP_SAPI !== 'cli')
{
ignore_user_abort(true);
set_time_limit(max(60, $timeout));
}
// Create multiple processes if configured.
if (PHP_SAPI === 'cli' && $process_count > 1 && function_exists('pcntl_fork') && function_exists('pcntl_waitpid'))
{
// This array will keep a dictionary of subprocesses.
$pids = [];
// The database connection must be closed before forking.
Rhymix\Framework\DB::getInstance()->disconnect();
Rhymix\Framework\Debug::disable();
// Create the required number of subprocesses.
for ($i = 0; $i < $process_count; $i++)
{
$pid = pcntl_fork();
if ($pid > 0)
{
$pids[$pid] = true;
usleep(200000);
}
elseif ($pid == 0)
{
Rhymix\Framework\Queue::process($timeout);
exit;
}
else
{
error_log('RxQueue: could not fork!');
exit;
}
}
// The parent process waits for its children to finish.
while (count($pids))
{
$pid = pcntl_waitpid(-1, $status, \WNOHANG);
if ($pid)
{
unset($pids[$pid]);
}
usleep(200000);
}
}
else
{
Rhymix\Framework\Queue::process($timeout);
}