diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 938956a87..bed5e2e3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,11 +2,11 @@ name: PHP Lint & Codeception on: [ push, pull_request ] jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: - php: [ '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3' ] + php: [ '7.4', '8.0', '8.1', '8.2', '8.3' ] name: PHP ${{ matrix.php }} steps: diff --git a/README.md b/README.md index b85c5ca17..b892e26e1 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Rhymix는 개발자와 사용자가 서로의 권리와 책임을 존중하는 ### 설치 환경 -Rhymix를 사용하려면 PHP 7.2.5 이상, MySQL 또는 MariaDB가 필요합니다. +Rhymix를 사용하려면 PHP 7.4 이상, MySQL 또는 MariaDB가 필요합니다. 자세한 설치 환경은 [매뉴얼](https://rhymix.org/manual/introduction/requirements)을 참고하십시오. ### 개발 참여 @@ -108,7 +108,7 @@ This requires the most convenience for the average user over any other CMS. ### Installation Environment -Rhymix requires PHP 7.2.5 or higher, and MySQL or MariaDB. +Rhymix requires PHP 7.4 or higher, and MySQL or MariaDB. Please see the [online manual](https://rhymix.org/manual/introduction/requirements) for more information on server requirements. ### Participation in Development diff --git a/classes/context/Context.class.php b/classes/context/Context.class.php index 1d5dcc37c..e1edbf63c 100644 --- a/classes/context/Context.class.php +++ b/classes/context/Context.class.php @@ -2879,14 +2879,10 @@ class Context { return isset(self::$_instance->meta_tags[$name]) ? self::$_instance->meta_tags[$name]['content'] : null; } - - $ret = array(); - foreach(self::$_instance->meta_tags as $name => $content) + else { - $ret[] = array('name' => $name, 'is_http_equiv' => $content['is_http_equiv'], 'content' => escape($content['content'], false)); + return array_values(self::$_instance->meta_tags); } - - return $ret; } /** @@ -2894,14 +2890,17 @@ class Context * * @param string $name name of meta tag * @param string $content content of meta tag - * @param mixed $is_http_equiv value of http_equiv + * @param bool $is_http_equiv + * @param bool $is_before_title * @return void */ - public static function addMetaTag($name, $content, $is_http_equiv = false) + public static function addMetaTag($name, $content, $is_http_equiv = false, $is_before_title = true) { self::$_instance->meta_tags[$name] = array( + 'name' => $name, + 'content' => escape(self::replaceUserLang($content, true), false), 'is_http_equiv' => (bool)$is_http_equiv, - 'content' => self::replaceUserLang($content, true), + 'is_before_title' => (bool)$is_before_title, ); } diff --git a/classes/display/HTMLDisplayHandler.php b/classes/display/HTMLDisplayHandler.php index 52e5f697d..c851b09c7 100644 --- a/classes/display/HTMLDisplayHandler.php +++ b/classes/display/HTMLDisplayHandler.php @@ -138,7 +138,7 @@ class HTMLDisplayHandler { // handle separately if the layout is faceoff - if($layout_info && $layout_info->type == 'faceoff') + if($layout_info && isset($layout_info->type) && $layout_info->type == 'faceoff') { $oLayoutModel->doActivateFaceOff($layout_info); Context::set('layout_info', $layout_info); @@ -196,6 +196,12 @@ class HTMLDisplayHandler Context::set('favicon_url', $favicon_url); Context::set('mobicon_url', $mobicon_url); + // Only print the X-UA-Compatible meta tag if somebody is still using IE + if (preg_match('!Trident/7\.0!', $_SERVER['HTTP_USER_AGENT'] ?? '')) + { + Context::addMetaTag('X-UA-Compatible', 'IE=edge', true); + } + return $output; } @@ -641,7 +647,7 @@ class HTMLDisplayHandler { if ($tag !== '') { - Context::addOpenGraphData('og:article:tag', $tag, false); + Context::addOpenGraphData('og:article:tag', $tag); } } @@ -683,21 +689,21 @@ class HTMLDisplayHandler function _addTwitterMetadata() { $card_type = $this->_image_type === 'document' ? 'summary_large_image' : 'summary'; - Context::addMetaTag('twitter:card', $card_type); + Context::addMetaTag('twitter:card', $card_type, false, false); foreach(Context::getOpenGraphData() as $val) { if ($val['property'] === 'og:title') { - Context::addMetaTag('twitter:title', $val['content']); + Context::addMetaTag('twitter:title', $val['content'], false, false); } if ($val['property'] === 'og:description') { - Context::addMetaTag('twitter:description', $val['content']); + Context::addMetaTag('twitter:description', $val['content'], false, false); } if ($val['property'] === 'og:image' && $this->_image_type === 'document') { - Context::addMetaTag('twitter:image', $val['content']); + Context::addMetaTag('twitter:image', $val['content'], false, false); } } } diff --git a/classes/module/ModuleHandler.class.php b/classes/module/ModuleHandler.class.php index 7d518befa..95c535c3d 100644 --- a/classes/module/ModuleHandler.class.php +++ b/classes/module/ModuleHandler.class.php @@ -693,7 +693,8 @@ class ModuleHandler extends Handler } } - if ($kind === 'admin') { + if ($kind === 'admin') + { Context::addMetaTag('robots', 'noindex'); } diff --git a/classes/module/ModuleObject.class.php b/classes/module/ModuleObject.class.php index a203bf9aa..c235289bf 100644 --- a/classes/module/ModuleObject.class.php +++ b/classes/module/ModuleObject.class.php @@ -370,28 +370,36 @@ class ModuleObject extends BaseObject } } // If permission is 'manager', check 'is user have manager privilege(granted)' - else if(preg_match('/^(manager|([a-z0-9\_]+)-managers)$/', $permission, $type)) + else if(preg_match('/^(manager(?::(.+))?|([a-z0-9\_]+)-managers)$/', $permission, $type)) { - if($grant->manager) + // If permission is manager(:scope), check manager privilege and scope + if ($grant->manager) { - return true; + if (empty($type[2])) + { + return true; + } + elseif ($grant->can($type[2])) + { + return true; + } } // If permission is '*-managers', search modules to find manager privilege of the member - if(Context::get('is_logged') && isset($type[2])) + if(Context::get('is_logged') && isset($type[3])) { // Manager privilege of the member is found by search all modules, Pass - if($type[2] == 'all' && ModuleModel::findManagerPrivilege($member_info) !== false) + if($type[3] == 'all' && ModuleModel::findManagerPrivilege($member_info) !== false) { return true; } // Manager privilege of the member is found by search same module as this module, Pass - elseif($type[2] == 'same' && ModuleModel::findManagerPrivilege($member_info, $this->module) !== false) + elseif($type[3] == 'same' && ModuleModel::findManagerPrivilege($member_info, $this->module) !== false) { return true; } // Manager privilege of the member is found by search same module as the module, Pass - elseif(ModuleModel::findManagerPrivilege($member_info, $type[2]) !== false) + elseif(ModuleModel::findManagerPrivilege($member_info, $type[3]) !== false) { return true; } diff --git a/common/autoload.php b/common/autoload.php index 8d7f98b3d..233ce1b83 100644 --- a/common/autoload.php +++ b/common/autoload.php @@ -11,10 +11,10 @@ if (defined('RX_VERSION')) /** * Check PHP version. */ -if (PHP_VERSION_ID < 70205) +if (PHP_VERSION_ID < 70400) { header('HTTP/1.1 500 Internal Server Error'); - echo 'Rhymix requires PHP 7.2.5 or higher.'; + echo 'Rhymix requires PHP 7.4 or higher.'; exit(1); } diff --git a/common/constants.php b/common/constants.php index da9ab15e6..6cce49864 100644 --- a/common/constants.php +++ b/common/constants.php @@ -3,7 +3,7 @@ /** * RX_VERSION is the version number of the Rhymix CMS. */ -define('RX_VERSION', '2.1.18'); +define('RX_VERSION', '2.1.19'); /** * RX_MICROTIME is the startup time of the current script, in microseconds since the Unix epoch. @@ -125,7 +125,7 @@ else /** * RX_WINDOWS is true if the operating system is Windows. */ -define('RX_WINDOWS', strncasecmp(PHP_OS, 'WIN', 3) === 0); +define('RX_WINDOWS', PHP_OS_FAMILY === 'Windows'); /** * XE core compatibility constants (may be used by XE-compatible plugins and themes). @@ -143,7 +143,7 @@ define('__XE_VERSION_ALPHA__', false); define('__XE_VERSION_BETA__', false); define('__XE_VERSION_RC__', false); define('__XE_VERSION_STABLE__', true); -define('__XE_MIN_PHP_VERSION__', '7.2.5'); +define('__XE_MIN_PHP_VERSION__', '7.4.0'); define('__XE_RECOMMEND_PHP_VERSION__', '7.4.0'); define('__ZBXE_VERSION__', RX_VERSION); define('_XE_LOCATION_', 'ko'); diff --git a/common/defaults/config.php b/common/defaults/config.php index f28768d7c..8351d0056 100644 --- a/common/defaults/config.php +++ b/common/defaults/config.php @@ -151,5 +151,7 @@ return array( ], 'use_rewrite' => true, 'use_sso' => false, - 'other' => array(), + 'other' => [ + 'proxy' => null, + ], ); diff --git a/common/framework/Cookie.php b/common/framework/Cookie.php index 997b39410..2374be6c8 100644 --- a/common/framework/Cookie.php +++ b/common/framework/Cookie.php @@ -79,24 +79,7 @@ class Cookie $options['samesite'] = config('cookie.samesite') ?? 'Lax'; } - // PHP 7.3+ supports the samesite attribute natively. PHP 7.2 requires a hack. - if (\PHP_VERSION_ID >= 70300) - { - $result = setcookie($name, $value, $options); - } - else - { - $expires = $options['expires']; - $path = $options['path'] ?? '/'; - $domain = $options['domain'] ?? null; - $secure = $options['secure'] ?? false; - $httponly = $options['httponly'] ?? false; - if (!empty($options['samesite'])) - { - $path = ($path ?: '/') . '; SameSite=' . $options['samesite']; - } - $result = setcookie($name, $value, $expires, $path, $domain, $secure, $httponly); - } + $result = setcookie($name, $value, $options); // Make the cookie immediately available server-side. if ($result && $options['expires'] >= 0) diff --git a/common/framework/HTTP.php b/common/framework/HTTP.php index 51e0b79ce..1948dee49 100644 --- a/common/framework/HTTP.php +++ b/common/framework/HTTP.php @@ -209,9 +209,10 @@ class HTTP ]; // Add proxy settings. - if (defined('__PROXY_SERVER__')) + $proxy = config('other.proxy') ?: (defined('__PROXY_SERVER__') ? constant('__PROXY_SERVER__') : ''); + if ($proxy !== '') { - $proxy = parse_url(constant('__PROXY_SERVER__')); + $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']); diff --git a/common/framework/Session.php b/common/framework/Session.php index c1e92a067..e88de76d3 100644 --- a/common/framework/Session.php +++ b/common/framework/Session.php @@ -79,17 +79,7 @@ class Session ini_set('session.use_cookies', 1); ini_set('session.use_only_cookies', 1); ini_set('session.use_strict_mode', 1); - if ($samesite) - { - if (PHP_VERSION_ID >= 70300) - { - ini_set('session.cookie_samesite', $samesite); - } - else - { - $path = ($path ?: '/') . '; SameSite=' . $samesite; - } - } + ini_set('session.cookie_samesite', $samesite ? 1 : 0); session_set_cookie_params($lifetime, $path, $domain, $secure, $httponly); session_name($session_name = Config::get('session.name') ?: session_name()); diff --git a/common/framework/Template.php b/common/framework/Template.php index 90302bab9..0049e68d9 100644 --- a/common/framework/Template.php +++ b/common/framework/Template.php @@ -899,19 +899,23 @@ class Template protected function _v2_checkCapability(int $check_type, $capability): bool { $grant = \Context::get('grant'); - if ($check_type === 1) + if (!($grant instanceof \Rhymix\Modules\Module\Models\Permission)) { - return isset($grant->$capability) ? boolval($grant->$capability) : false; + return false; + } + elseif ($check_type === 1) + { + return $grant->can($capability); } elseif ($check_type === 2) { - return isset($grant->$capability) ? !boolval($grant->$capability) : true; + return !$grant->can($capability); } elseif (is_array($capability)) { foreach ($capability as $cap) { - if (isset($grant->$cap) && $grant->$cap) + if ($grant->can($cap)) { return true; } diff --git a/common/framework/drivers/mail/mailfunction.php b/common/framework/drivers/mail/mailfunction.php index 6e11d03c2..545031106 100644 --- a/common/framework/drivers/mail/mailfunction.php +++ b/common/framework/drivers/mail/mailfunction.php @@ -7,15 +7,6 @@ namespace Rhymix\Framework\Drivers\Mail; */ class MailFunction extends Base implements \Rhymix\Framework\Drivers\MailInterface { - /** - * Direct invocation of the constructor is not permitted. - */ - protected function __construct() - { - include_once \RX_BASEDIR . 'common/libraries/swift_mail.php'; - $this->_mailer = new \Swift_Mailer(new \Swift_MailTransport); - } - /** * Get the human-readable name of this mail driver. * @@ -58,6 +49,12 @@ class MailFunction extends Base implements \Rhymix\Framework\Drivers\MailInterfa */ public function send(\Rhymix\Framework\Mail $message) { + if ($this->_mailer === null) + { + include_once \RX_BASEDIR . 'common/libraries/swift_mail.php'; + $this->_mailer = new \Swift_Mailer(new \Swift_MailTransport); + } + try { $errors = []; diff --git a/common/framework/drivers/mail/mailgun.php b/common/framework/drivers/mail/mailgun.php index cc1f339e9..118b6345e 100644 --- a/common/framework/drivers/mail/mailgun.php +++ b/common/framework/drivers/mail/mailgun.php @@ -116,12 +116,13 @@ class Mailgun extends Base implements \Rhymix\Framework\Drivers\MailInterface // Send the API request. $url = self::$_url . '/' . $this->_config['api_domain'] . '/messages.mime'; $request = \Rhymix\Framework\HTTP::post($url, $data, $headers, [], $settings); - $result = @json_decode($request->getBody()->getContents()); + $result_text = $request->getBody()->getContents(); + $result = @json_decode($result_text); // Parse the result. if (!$result) { - $message->errors[] = 'Mailgun: API error: ' . $request->getBody()->getContents(); + $message->errors[] = 'Mailgun: API error: ' . $result_text; return false; } elseif (!$result->id) diff --git a/common/framework/drivers/mail/smtp.php b/common/framework/drivers/mail/smtp.php index a15f05f68..7a762b2a9 100644 --- a/common/framework/drivers/mail/smtp.php +++ b/common/framework/drivers/mail/smtp.php @@ -7,23 +7,6 @@ namespace Rhymix\Framework\Drivers\Mail; */ class SMTP extends Base implements \Rhymix\Framework\Drivers\MailInterface { - /** - * Direct invocation of the constructor is not permitted. - */ - protected function __construct(array $config) - { - $security = in_array($config['smtp_security'], ['ssl', 'tls']) ? $config['smtp_security'] : null; - $transport = new \Swift_SmtpTransport($config['smtp_host'], $config['smtp_port'], $security); - $transport->setUsername($config['smtp_user']); - $transport->setPassword($config['smtp_pass']); - $local_domain = $transport->getLocalDomain(); - if (preg_match('/^\*\.(.+)$/', $local_domain, $matches)) - { - $transport->setLocalDomain($matches[1]); - } - $this->mailer = new \Swift_Mailer($transport); - } - /** * Get the list of configuration fields required by this mail driver. * @@ -56,9 +39,32 @@ class SMTP extends Base implements \Rhymix\Framework\Drivers\MailInterface */ public function send(\Rhymix\Framework\Mail $message) { + if ($this->_mailer === null) + { + if (isset($this->_config['smtp_security']) && in_array($this->_config['smtp_security'], ['ssl', 'tls'])) + { + $security = $this->_config['smtp_security']; + } + else + { + $security = null; + } + + $transport = new \Swift_SmtpTransport($this->_config['smtp_host'], $this->_config['smtp_port'], $security); + $transport->setUsername($this->_config['smtp_user']); + $transport->setPassword($this->_config['smtp_pass']); + $local_domain = $transport->getLocalDomain(); + if (preg_match('/^\*\.(.+)$/', $local_domain, $matches)) + { + $transport->setLocalDomain($matches[1]); + } + $this->_mailer = new \Swift_Mailer($transport); + } + try { - $result = $this->mailer->send($message->message, $errors); + $errors = []; + $result = $this->_mailer->send($message->message, $errors); } catch(\Exception $e) { diff --git a/common/framework/drivers/push/fcmv1.php b/common/framework/drivers/push/fcmv1.php index 6cd947749..390161af0 100644 --- a/common/framework/drivers/push/fcmv1.php +++ b/common/framework/drivers/push/fcmv1.php @@ -151,7 +151,8 @@ class FCMv1 extends Base implements PushInterface foreach ($responses as $i => $response) { $status_code = $response->getStatusCode(); - $result = @json_decode($response->getBody()->getContents()); + $result_text = $response->getBody()->getContents(); + $result = @json_decode($result_text); if ($status_code === 200) { $output->success[$tokens[$i]] = $result->name ?? ''; @@ -164,6 +165,10 @@ class FCMv1 extends Base implements PushInterface { $output->invalid[$tokens[$i]] = $tokens[$i]; } + elseif (str_contains($error_message, 'Requested entity was not found')) + { + $output->invalid[$tokens[$i]] = $tokens[$i]; + } } else { @@ -196,7 +201,8 @@ class FCMv1 extends Base implements PushInterface foreach ($responses as $i => $response) { $status_code = $response->getStatusCode(); - $result = @json_decode($response->getBody()->getContents()); + $result_text = $response->getBody()->getContents(); + $result = @json_decode($result_text); if ($status_code === 200) { $output->success[$topics[$i]] = $result->name ?? ''; diff --git a/common/framework/drivers/queue/db.php b/common/framework/drivers/queue/db.php index c45eedc5d..a55bd71ad 100644 --- a/common/framework/drivers/queue/db.php +++ b/common/framework/drivers/queue/db.php @@ -93,8 +93,8 @@ class DB implements QueueInterface 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)]); + $stmt = $oDB->prepare('INSERT INTO task_queue (handler, args, options, regdate) VALUES (?, ?, ?, ?)'); + $result = $stmt->execute([$handler, serialize($args), serialize($options), date('Y-m-d H:i:s')]); return $result ? $oDB->getInsertID() : 0; } diff --git a/common/framework/drivers/sms/apistore.php b/common/framework/drivers/sms/apistore.php deleted file mode 100644 index 2b294a0a5..000000000 --- a/common/framework/drivers/sms/apistore.php +++ /dev/null @@ -1,141 +0,0 @@ - 500, - 'sms_max_length' => 90, - 'sms_max_length_in_charset' => 'CP949', - 'lms_supported' => true, - 'lms_supported_country_codes' => array(82), - 'lms_max_length' => 2000, - 'lms_max_length_in_charset' => 'CP949', - 'lms_subject_supported' => true, - 'lms_subject_max_length' => 60, - 'mms_supported' => false, - 'delay_supported' => true, - ); - - /** - * Config keys used by this driver are stored here. - */ - protected static $_required_config = array('api_user', 'api_key'); - - /** - * Check if the current SMS driver is supported on this server. - * - * This method returns true on success and false on failure. - * - * @return bool - */ - public static function isSupported() - { - return true; - } - - /** - * Store the last response. - */ - protected $_last_response = ''; - - /** - * Send a message. - * - * This method returns true on success and false on failure. - * - * @param array $messages - * @param object $original - * @return bool - */ - public function send(array $messages, \Rhymix\Framework\SMS $original) - { - $status = true; - - foreach ($messages as $i => $message) - { - $data = array(); - $data['send_phone'] = $message->from; - $data['dest_phone'] = implode(',', $message->to); - $data['msg_body'] = strval($message->content); - if ($message->type !== 'SMS' && $message->subject) - { - $data['subject'] = $message->subject; - } - - $result = $this->_apiCall(sprintf('message/%s', strtolower($message->type)), $data); - if (!$result) - { - $message->errors[] = 'ApiStore API returned invalid response: ' . $this->_getLastResponse(); - $status = false; - } - if ($result->result_message !== 'OK') - { - $message->errors[] = 'ApiStore API error: ' . $result->result_code . ' ' . $result->result_message; - } - } - - return $status; - } - - /** - * API call. - * - * @param string $url - * @param array $data - * @param string $method (optional) - * @return object|false - */ - protected function _apiCall(string $url, array $data, string $method = 'POST') - { - // Build the request URL. - if ($data['version']) - { - $version = $data['version']; - unset($data['version']); - } - else - { - $version = 1; - } - $url = sprintf('http://api.apistore.co.kr/ppurio/%d/%s/%s', $version, trim($url, '/'), $this->_config['api_user']); - - // Set the API key in the header. - $headers = array( - 'x-waple-authorization' => $this->_config['api_key'], - ); - - // Send the API reqeust. - if ($method === 'GET') - { - if ($data) - { - $url .= '?' . http_build_query($data); - } - $this->_last_response = \FileHandler::getRemoteResource($url, null, 5, $method, null, $headers) ?: ''; - } - else - { - $this->_last_response = \FileHandler::getRemoteResource($url, $data, 5, $method, null, $headers) ?: ''; - } - $result = @json_decode($this->_last_response); - return $result ?: false; - } - - /** - * Fetch the last API response. - * - * @return string - */ - protected function _getLastResponse() - { - return $this->_last_response; - } -} diff --git a/common/functions.php b/common/functions.php index ae2616abe..d2b2d0d23 100644 --- a/common/functions.php +++ b/common/functions.php @@ -740,20 +740,6 @@ function is_empty_html_content($str): bool return $str === ''; } -/** - * Polyfill for is_countable() in PHP < 7.3 - * - * @param mixed $var - * @return bool -**/ -if (!function_exists('is_countable')) -{ - function is_countable($var) - { - return is_array($var) || $var instanceof Countable; - } -} - /** * Polyfill for str_starts_with() in PHP < 8.0 * diff --git a/common/scripts/cron.php b/common/scripts/cron.php index 563f71c70..19ea7b1c9 100644 --- a/common/scripts/cron.php +++ b/common/scripts/cron.php @@ -33,6 +33,7 @@ else } // Get queue configuration set by the administrator. +$display_errors = config('queue.display_errors') === false ? false : true; $timeout = (config('queue.interval') ?? 1) * 60; $process_count = config('queue.process_count') ?? 1; @@ -41,6 +42,14 @@ if (PHP_SAPI !== 'cli') { ignore_user_abort(true); set_time_limit(max(60, $timeout)); + if ($display_errors) + { + ini_set('display_errors', true); + } + if (Rhymix\Framework\Session::checkStart()) + { + Rhymix\Framework\Session::close(); + } } // Create multiple processes if configured. @@ -89,3 +98,9 @@ else { Rhymix\Framework\Queue::process($timeout); } + +// If called over the network, display a simple OK message to indicate success. +if (PHP_SAPI !== 'cli') +{ + echo "OK\n"; +} diff --git a/common/tpl/common_layout.html b/common/tpl/common_layout.html index 04a7e326d..b4d64176c 100644 --- a/common/tpl/common_layout.html +++ b/common/tpl/common_layout.html @@ -7,11 +7,12 @@ - -@foreach (Context::getMetaTag() as $val) - -@endforeach +@foreach (Context::getMetaTag() as $val) +@if ($val['is_before_title']) + +@endif +@endforeach
%s account and run crontab -e to paste the following content into your crontab. (DO NOT run it as root!)%s directory in the example should be replaced with a path where logs can be recorded.';
-$lang->msg_queue_instructions['crontab2'] = 'If you change the calling interval below, the crontab interval must be adjusted accordingly.';
+$lang->msg_queue_instructions['crontab1'] = 'Log into the server as the %s account and run crontab -e to paste the following content into your crontab. (DO NOT run it as root!)apache or www-data, try running sudo crontab -e -u %s from a different account.';
+$lang->msg_queue_instructions['crontab2'] = 'The %s directory in the example should be replaced with a path where logs can be recorded./etc/systemd/system/rhymix-queue.service';
$lang->msg_queue_instructions['systemd2'] = 'Put the following content in /etc/systemd/system/rhymix-queue.timer';
diff --git a/modules/admin/lang/ko.php b/modules/admin/lang/ko.php
index 7fd335612..3cb78e0e1 100644
--- a/modules/admin/lang/ko.php
+++ b/modules/admin/lang/ko.php
@@ -204,6 +204,9 @@ $lang->cache_truncate_method_empty = '캐시 내용만 삭제';
$lang->about_cache_truncate_method = '캐시 폴더를 삭제하는 방법이 더 빠르고 안정적입니다.crontab -e 명령을 실행한 후, 아래의 내용을 붙여넣으십시오. (root 권한으로 실행하지 마십시오.)%s 디렉토리는 로그를 기록할 수 있는 경로로 변경하여 사용하십시오.';
-$lang->msg_queue_instructions['crontab2'] = '스크립트 호출 간격을 변경할 경우, 설정에 맞추어 crontab 실행 간격도 조절하여야 합니다.';
+$lang->msg_queue_instructions['crontab1'] = '%s 계정으로 서버에 로그인하여 crontab -e 명령을 실행한 후, 아래의 내용을 붙여넣으십시오. (root 권한으로 실행하지 마십시오!)apache나 www-data처럼 로그인할 수 없는 계정이라면, 다른 계정에서 sudo crontab -e -u %s 명령을 실행해 볼 수 있습니다.';
+$lang->msg_queue_instructions['crontab2'] = '예제의 %s 디렉토리는 로그를 기록할 권한이 있는 경로로 변경하여 사용하십시오./etc/systemd/system/rhymix-queue.service 파일에 아래와 같은 내용을 넣습니다.';
$lang->msg_queue_instructions['systemd2'] = '/etc/systemd/system/rhymix-queue.timer 파일에 아래와 같은 내용을 넣습니다.';
diff --git a/modules/admin/tpl/config_advanced.html b/modules/admin/tpl/config_advanced.html
index 3378ea501..6373337fe 100644
--- a/modules/admin/tpl/config_advanced.html
+++ b/modules/admin/tpl/config_advanced.html
@@ -201,6 +201,13 @@
{$lang->about_cache_control_header}
+{$lang->about_outgoing_proxy}
+- {sprintf($lang->msg_queue_instructions['crontab1'], $user_info['name'] ?? 'PHP', $user_info['dir'] . 'logs')|noescape} + {sprintf($lang->msg_queue_instructions['crontab1'], $user_info['name'] ?? 'PHP', $user_info['name'] ?? 'PHP')|noescape}
-* * * * * php {\RX_BASEDIR}index.php common.cron >> {$user_info['dir']}logs{\DIRECTORY_SEPARATOR}queue.log 2>&1
+ * * * * * /usr/bin/php {\RX_BASEDIR}index.php common.cron >> {$user_info['dir']}logs{\DIRECTORY_SEPARATOR}queue.log 2>&1
- {$lang->msg_queue_instructions['crontab2']|noescape} + {sprintf($lang->msg_queue_instructions['crontab2'], $user_info['dir'] . 'logs')|noescape}
{$lang->msg_queue_instructions['systemd2']|noescape} @@ -167,6 +167,21 @@ systemctl enable rhymix-queue.timer
{$lang->cmd_queue_webcron_display_errors_help}
+