diff --git a/.editorconfig b/.editorconfig index 719153d4b..3a0b56850 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,9 +1,23 @@ root = true [*] -end_of_line = lf charset = utf-8 -trim_trailing_whitespace = false +end_of_line = lf +trim_trailing_whitespace = true insert_final_newline = true indent_style = tab + +[*.md] +trim_trailing_whitespace = false + +[*.py] +indent_style = space indent_size = 4 + +[*.{json,yml,md}] +indent_style = space +indent_size = 2 + +[*.{jsx,tsx,svelte,vue}] +indent_style = space +indent_size = 2 diff --git a/.gitattributes b/.gitattributes index d6a255849..ef192bc48 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,6 @@ .gitattributes export-ignore +.github export-ignore .gitignore export-ignore -/tools/ export-ignore +codeception.dist.yml export-ignore /tests/ export-ignore -Gruntfile.js export-ignore -.travis.yml export-ignore +/common/vendor/bin/ export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..668f1e4c4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: PHP Lint & Codeception +on: [ push, pull_request ] +jobs: + build: + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + php: [ '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5' ] + + name: PHP ${{ matrix.php }} + steps: + + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Install PHP + run: chmod +x .github/workflows/setup-php.sh && .github/workflows/setup-php.sh ${{ matrix.php }} + + - name: Create test database + run: chmod +x .github/workflows/setup-mysql.sh && .github/workflows/setup-mysql.sh + + - name: PHP Lint + run: if find . -name "*.php" ! -path "./common/vendor/*" -print0 | xargs -0 -n 1 -P 8 php -l | grep -v "No syntax errors detected"; then exit 1; fi + + - name: Download codeception + run: wget https://res.rhymix.org/ci/php${{ matrix.php }}/codecept.phar + + - name: Run PHP development server + run: php -S localhost:8000 & + + - name: Build and run codeception + run: | + php codecept.phar build + php codecept.phar run --debug --fail-fast diff --git a/.github/workflows/setup-mysql.sh b/.github/workflows/setup-mysql.sh new file mode 100644 index 000000000..2f90347cd --- /dev/null +++ b/.github/workflows/setup-mysql.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +AUTH="-uroot -proot" + +# Start MySQL +sudo systemctl start mysql.service + +# Create default database +sudo mysql $AUTH -e "CREATE DATABASE rhymix CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci" +sudo mysql $AUTH -e "CREATE USER rhymix@localhost IDENTIFIED WITH mysql_native_password BY 'rhymix'" +sudo mysql $AUTH -e "GRANT ALL ON rhymix.* to rhymix@localhost; FLUSH PRIVILEGES" + +# Check MySQL version +sudo mysql $AUTH -e "SELECT VERSION()" diff --git a/.github/workflows/setup-php.sh b/.github/workflows/setup-php.sh new file mode 100644 index 000000000..ef9b1656c --- /dev/null +++ b/.github/workflows/setup-php.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# Based on https://github.com/nanasess/setup-php +sudo add-apt-repository -y ppa:ondrej/php + +# Install all required packages +sudo apt -y install \ + php$1-apcu \ + php$1-bcmath \ + php$1-cli \ + php$1-common \ + php$1-curl \ + php$1-gd \ + php$1-intl \ + php$1-mbstring \ + php$1-mysql \ + php$1-readline \ + php$1-sqlite3 \ + php$1-xml \ + php$1-zip + +# Adjust php.ini settings +sudo bash -c "echo 'register_argc_argv = On' >> /etc/php/$1/cli/php.ini" +sudo bash -c "echo 'opcache.enable = 1' >> /etc/php/$1/cli/conf.d/10-opcache.ini" +sudo bash -c "echo 'opcache.enable_cli = 1' >> /etc/php/$1/cli/conf.d/10-opcache.ini" +sudo bash -c "echo 'opcache.jit = tracing' >> /etc/php/$1/cli/conf.d/10-opcache.ini" +sudo bash -c "echo 'opcache.jit_buffer_size = 128M' >> /etc/php/$1/cli/conf.d/10-opcache.ini" + +# Enable APCu +if [ -f "/etc/php/$1/cli/conf.d/20-apcu.ini" ]; then + sudo bash -c "echo 'apc.enable_cli = 1' >> /etc/php/$1/cli/conf.d/20-apcu.ini" +fi + +# Disable xdebug +sudo phpdismod -v ALL -s ALL xdebug + +# Set and check default PHP version +sudo update-alternatives --set php /usr/bin/php$1 +php -v diff --git a/.gitignore b/.gitignore index f6d44c481..f69ccf345 100644 --- a/.gitignore +++ b/.gitignore @@ -17,10 +17,11 @@ codeception.yml /node_modules/ /bower_components/ composer.phar +codecept.phar .idea *.sublime-workspace *.sublime-project .codeintel +.vscode error_log - diff --git a/.htaccess b/.htaccess index c0ea1b7bb..a160d6cbd 100644 --- a/.htaccess +++ b/.htaccess @@ -2,8 +2,8 @@ RewriteEngine On # block direct access to templates, XML schema files, config files, dotfiles, environment, etc. RewriteCond %{REQUEST_URI} !/modules/editor/(skins|styles)/ -RewriteRule ^(addons|common/tpl|files/ruleset|(m\.)?layouts|modules|plugins|themes|widgets|widgetstyles)/.+\.(html|xml)$ - [L,F] -RewriteRule ^files/(attach|config|cache/store)/.+\.(ph(p|t|ar)?[0-9]?|p?html?|cgi|pl|exe|[aj]spx?|inc|bak)$ - [L,F] +RewriteRule ^(addons|common/tpl|files/(faceOff|ruleset)|(m\.)?layouts|modules|plugins|themes|widgets|widgetstyles)/.+\.(html|xml|blade\.php)$ - [L,F] +RewriteRule ^files/(attach|config|cache)/.+\.(ph(p|t|ar)?[0-9]?|p?html?|cgi|pl|exe|[aj]spx?|inc|bak)$ - [L,F] RewriteRule ^files/(env|member_extra_info/(new_message_flags|point))/ - [L,F] RewriteRule ^(\.git|\.ht|\.travis|codeception\.|composer\.|Gruntfile\.js|package\.json|CONTRIBUTING|COPYRIGHT|LICENSE|README) - [L,F] @@ -13,29 +13,7 @@ RewriteRule ^(.+)/(addons|files|layouts|m\.layouts|modules|widgets|widgetstyles) RewriteCond %{SCRIPT_FILENAME} !-f RewriteRule ^(.+)\.min\.(css|js)$ ./$1.$2 [L] -# rss, blogAPI -RewriteRule ^(rss|atom)$ ./index.php?module=rss&act=$1 [L] -RewriteRule ^([a-zA-Z0-9_]+)/(rss|atom|api)$ ./index.php?mid=$1&act=$2 [L] - -# trackback -RewriteRule ^([0-9]+)/(.+)/trackback$ ./index.php?document_srl=$1&key=$2&act=trackback [L] -RewriteRule ^([a-zA-Z0-9_]+)/([0-9]+)/(.+)/trackback$ ./index.php?mid=$1&document_srl=$2&key=$3&act=trackback [L] - -# document category -RewriteRule ^([a-zA-Z0-9_]+)/category/([0-9]+)$ ./index.php?mid=$1&category=$2 [L,QSA] - -# document permanent link -RewriteRule ^([0-9]+)$ ./index.php?document_srl=$1 [L,QSA] - -# admin module link -RewriteRule ^admin/?$ ./index.php?module=admin [L,QSA] - -# mid link +# all other short URLs +RewriteCond %{SCRIPT_FILENAME} !-f RewriteCond %{SCRIPT_FILENAME} !-d -RewriteRule ^([a-zA-Z0-9_]+)/?$ ./index.php?mid=$1 [L,QSA] - -# mid + document link -RewriteRule ^([a-zA-Z0-9_]+)/([0-9]+)$ ./index.php?mid=$1&document_srl=$2 [L,QSA] - -# mid + entry title -RewriteRule ^([a-zA-Z0-9_]+)/entry/(.+)$ ./index.php?mid=$1&entry=$2 [L,QSA] +RewriteRule . index.php [L] diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f9eb67b7e..000000000 --- a/.travis.yml +++ /dev/null @@ -1,24 +0,0 @@ -distro: trusty -sudo: false -language: php -php: - - 7.0 - - 7.1 - - 7.2 - - 7.3 -services: - - mysql -before_script: - - npm install grunt grunt-cli grunt-contrib-jshint grunt-contrib-csslint grunt-phplint --save-dev - - mysql -u root -e "CREATE DATABASE rhymix CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci" - - mysql -u root -e "GRANT ALL PRIVILEGES ON rhymix.* TO travis@localhost" - - mysql -u root -e "UPDATE mysql.user SET Password = PASSWORD('travis') WHERE User = 'travis'; FLUSH PRIVILEGES" - - if [[ $TRAVIS_PHP_VERSION != "7.3" ]]; then phpenv config-rm xdebug.ini; fi - - wget https://codeception.com/releases/2.3.9/codecept.phar - - php -S localhost:8000 & -script: - - php codecept.phar build - - php codecept.phar run --debug --fail-fast --env travis - - grunt lint -notifications: - email: false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 88849c01d..2951a7f63 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,230 +1,7 @@ # 개발에 참여하고 싶으신 분들께 드리는 안내문 -## 이슈 작성 - -- **구글에서 답을 찾을 수 있는지 먼저 확인해 주십시오.** -- 관련된 이슈가 이미 있는지 검색하고, 같은 내용이라면 댓글로 덧붙여 주십시오. - 오래된 이슈라도 메일로 알림이 전달되므로 묻힐 염려가 없습니다. -- 무관한 이슈에 댓글을 달지 말아 주십시오. 엉뚱한 사람에게 메일 알림이 전달됩니다. -- 2가지 이상 서로 다른 문제가 있는 경우, 각각 이슈를 등록해 주십시오. -- 보안 취약점은 공개적으로 언급하지 말고 devops@rhymix.org로 알려 주시면 감사하겠습니다. -- **버그 신고 전 자신의 서버가 Rhymix의 실행 환경을 충족하는지 확인해 주십시오.** - - PHP 5.5 미만, EUC-KR 환경, 퍼미션 오류 등은 호스팅 업체에 문의하셔야 합니다. - - 자신의 서버 환경은 `phpinfo`를 사용하여 확인할 수 있습니다. -- **버그 신고에는 아래의 내용을 반드시 포함시켜 주십시오.** - - 실행 환경 - - 호스팅 환경에 대한 간단한 설명 (예: ○○24 리눅스 웹호스팅) - - Rhymix 버전 (예: 1.8.15) - - PHP 버전 (예: 5.6.16) - - 브라우저 종류 및 버전 (예: IE 11) - - 에러가 발생하는 경우 에러 메시지 전체 - - 화면상에 에러가 표시되거나 디자인이 깨져 보이는 경우, 해당 스크린샷 - - 브라우저의 개발자도구(F12)에 에러가 표시되는 경우, 콘솔 및 네트워크 탭의 스크린샷 - - 증상을 확인해 볼 수 있는 웹사이트 주소 - - 내부망이나 로컬 개발환경 등 외부인의 접속이 원천적으로 불가능한 경우가 아니라면 반드시 주소를 남겨 주시기 바랍니다. - - 공개적인 개발을 추구하는 오픈소스 소프트웨어의 특성상, 이슈 해결에 필요한 정보를 공개하지 않는 경우 - 처리가 지연되거나 제3자에게 비용을 지불하고 해결해야 하는 등 불이익이 발생할 수 있습니다. - -## 풀 리퀘스트(PR) 작성 - -- 자신의 저장소에서 별도의 브랜치를 만들어 작업하신 후, `develop` 브랜치로 풀 리퀘스트를 넣어주시면 됩니다. - - 예: 썸네일 관련 버그를 수정하는 경우 자신의 저장소에서 `fix/thumbnail` 브랜치를 만들어 작업하십시오. - 작성 후에 수정할 것이 있으면 이 브랜치에서 계속 작업하고 커밋하시면 됩니다. PR 페이지에 자동으로 반영됩니다. -- 개발 진행 및 안정화에 따라 브랜치별 운영 정책이 변경될 수 있으니 유의하십시오. -- 아래의 코딩 규칙을 지키려고 노력해 주시기 바랍니다. -- **코딩 규칙에 맞지 않는 소스를 발견하더라도 PR의 주제와 관계없는 부분은 함부로 고치지 마세요! - 코딩 규칙에 맞도록 소스를 수정하는 작업은 모두 별도의 PR로 처리하여야 합니다.** -- **단, PR을 검토하는 개발자들은 괄호의 위치와 같은 사소한 문제를 지적하느라고 - 실제 기능에 관심을 주지 못하는 오류를 범하지 않도록 노력해야 합니다.** -- PR의 제목은 커밋 메시지에 적용되는 규칙을 참고하되, 가능하면 한글로 작성해 주십시오. -- 유닛 테스트를 통과하지 못하거나, 통과하기 위해 테스트를 삭제할 경우 PR이 거부될 수 있습니다. - 단, 테스트 자체에 문제가 있거나 테스트 내용을 변경해야 한다고 생각되는 경우 개발팀과 의논해 주십시오. - -## 저작권 및 라이선스 - -- 모든 소스 코드의 저작권은 해당 작성자가 가집니다. -- 모든 소스 코드에는 GPL v2 또는 그 이후 버전의 라이선스가 적용됩니다. - - Rhymix 개발팀을 비롯한 전세계 어느 누구라도 어떤 목적으로든지 자유롭게 사용, 수정, 재배포할 수 있습니다. - - 타인에게 저작권이 있는 코드를 가져온 경우, 원본의 라이선스를 GPL로 전환할 수 있어야 합니다. - - 한 번 적용한 라이선스는 철회할 수 없습니다. -- **풀 리퀘스트를 작성하실 경우 위의 두 가지에 동의하시는 것으로 간주합니다.** - -## 코딩 규칙 - -### 일반 - -PHP, HTML, XML, CSS, JS 등 모든 텍스트 파일의 문자셋은 BOM이 없는 UTF-8입니다. - -줄바꿈 문자는 유닉스 방식(`LF`)을 따릅니다. - -윈도우 메모장에서 편집한 파일은 위의 두 가지 규칙에 어긋납니다. -윈도우 사용자는 [Notepad++](https://notepad-plus-plus.org/) 등의 개발자용 에디터를 사용하여 편집하시기 바랍니다. - -들여쓰기는 1개의 탭으로 합니다. -단, 탭 대신 공백을 사용하는 파일에서는 일관성 유지를 위해 4칸의 공백을 사용할 수 있습니다. - -들여쓴 줄들 사이의 빈 줄도 들여씁니다. (에디터에서 후행 공백을 제거하지 않도록 설정하십시오.) - -PHP 코드만으로 이루어진 파일은 맨 끝에 `?>` 태그를 사용하지 않습니다. - -### 공백 및 줄바꿈 규칙 - -클래스 및 함수 선언과 `if`, `for`, `foreach`, `while` 등의 중괄호는 다음 줄에 씁니다. - - class Foo - { // RIGHT - public function bar() { // WRONG - - } - } - -조건문이나 순환문 내에 하나의 명령만 있는 경우에도 반드시 중괄호를 사용합니다. -그래야 나중에 명령이 추가될 경우 수정하기 편리합니다. - - if (!$foo) return false; // WRONG - if (!$bar) // RIGHT - { - return true; - } - -단, 클로져는 같은 줄에 중괄호를 쓸 수 있으며, -이 경우 중괄호 앞뒤에 한 칸씩 공백을 두어 클로져가 시작되고 끝나는 지점을 찾기 쉽도록 합니다. -닫는 중괄호와 그 뒤의 기호 사이에는 공백을 두지 않습니다. - - $foo = function($bar) { return $bar + 1; }; // RIGHT - $foo = function($bar) { // RIGHT - return $bar + 1; - }; - $foo = function($bar){return $bar + 1;}; // WRONG - -자바스크립트에서는 거의 모든 함수가 클로져이며, 잘못 줄바꿈할 경우 세미콜론이 삽입될 수 있으므로 -중괄호를 다음 줄에 쓰지 않아도 됩니다. - - $("#foo").on("click", function() { // OK - if ($(this).val() === "bar") { // OK - $(this).val("baz"); - } - }); - -함수 호출시 함수명과 괄호 사이, 괄호와 인자 사이에 공백을 두지 않습니다. -인자를 구분하는 쉼표는 뒤쪽에만 한 칸의 공백을 둡니다. - - function foobar($baz, $param) // RIGHT - function foobar ( $baz , $param ) // WRONG - -`if`, `for`, `foreach`, `while` 등의 키워드 뒤에는 한 칸의 공백을 둘 수 있으며, -`==`, `!=`, `>` 등의 연산자는 앞뒤에 한 칸씩 공백을 둡니다. - - if ($foo === $bar) // RECOMMENDED - if($foo === $bar) // OK for XE Compatibility - if($foo==$bar){ // WRONG - -여러 줄에 걸쳐 배열을 선언할 경우, 마지막 구성요소 뒤에 쉼표를 남깁니다. -그래야 나중에 구성요소를 추가할 때 편리합니다. - - $animals = array( - 'bear', - 'cat', - 'dog', // COMMA - ); - -단, 자바스크립트 및 JSON에서는 마지막 쉼표를 반드시 삭제해야 합니다. -쉼표를 남겨둘 경우 일부 브라우저에서 오류가 발생할 수 있기 때문입니다. - - var animals = [ - 'bear', - 'cat', - 'dog' // NO COMMA - ]; - -### 주석 - -주석은 관련 코드 윗줄에 써야 합니다. 조건문이나 루프의 경우에도 마찬가지입니다. - - // If foo is bar, do something. - if ($foo->isBar()) - { - // Note: this will do X, Y, and Z. - $foo->doSomething(); - } - // Otherwise, do something else. - else - { - // TODO: Refactor this later. - $foo->doSomethingElse(); - } - -모든 클래스와 함수에는 `/**`으로 시작하는 PHPDoc 방식의 주석을 붙여야 합니다. -PHPDoc 주석 작성에 어려움이 있는 경우, 다른 클래스와 함수의 주석을 참고하십시오. - - /** - * This is the Foo class. - */ - class Foo - { - /** - * Constructor. - * - * @param string $member_srl - */ - public function __construct($member_srl) - { - // 생략 - } - - /** - * Check if this Foo is bar. - * - * @return bool - */ - public function isBar() - { - return true; - } - } - -불가피한 경우를 제외하면 주석은 영문으로 쓰는 것을 원칙으로 하며, -대문자로 시작하는 완전한 문장으로 이루어져야 합니다. - -### 커밋 메시지 - -커밋 메시지는 가능하면 영문으로 작성하며, **동사원형**으로 시작하는 **현재형**, **명령형** 문장 사용을 원칙으로 합니다. - - Delete unnecessary condition // RIGHT - Fix #1234 // RIGHT - Deletes unnecessary condition // WRONG (불필요한 동사변화) - Fixed #1234 // WRONG (불필요한 과거형) - -이 규칙에 맞추어 영문으로 커밋 메시지를 작성하기 어려운 경우, 한글로 작성해도 무방합니다. -한글 커밋 메시지는 **어디서** **무엇을** **어떻게** 했는지 간결하고 명확하고 격식있게 표현하며, -가능하면 현재형 동사로 마치도록 합니다. - - 크롬 최신 버전에서 스크립트 오류 해결 // RIGHT - Foo 클래스에 bar() 메소드 추가 // RIGHT - 파일첨부 에러나는거 고쳤쩌염~^^ // WRONG (격식없는 표현) - 함수 개선 // WRONG (두리뭉실한 표현) - -### 기타 - -Rhymix의 기본 `error_reporting` 설정 하에서 어떤 에러도 발생하지 않도록 하는 것을 목표로 하지만, -선언하지 않은 변수 등 `E_NOTICE`를 일으키는 문제도 가능하면 새로 만들어내지는 않도록 합니다. - -문자열과 문자열, 정수와 정수를 비교할 때는 가능하면 `==` 대신 `===`을 사용합니다. -실제 자료형이 다를 가능성이 있는 경우 `intval()`, `strval()` 등의 함수와 함께 사용합니다. -정수는 항상 64비트 범위를 가지는 것으로 가정합니다. - -PHP 5.4 이상에서 지원하는 간단한 배열 문법(`[1, 2, 3]`)을 사용할 수 있으나, -복잡한 구조의 배열을 선언할 때는 이 문법이 오히려 가독성을 해칠 수 있으니 주의하시기 바랍니다. - -전역 상수를 참조할 때는 `\RX_CLIENT_IP`와 같이 `\`를 앞에 붙여서, -추후 다른 네임스페이스로 코드를 이동 또는 복사하더라도 문제가 생기지 않도록 합니다. - -여기에서 규정하지 않은 내용은 [PSR-1](http://www.php-fig.org/psr/psr-1/)과 -[PSR-2](http://www.php-fig.org/psr/psr-2/)를 따릅니다. - -composer 라이브러리를 업데이트할 때는 라이믹스에서 공식적으로 지원하는 환경(PHP 5.5.9 이상)에서 -정상 작동하는 버전으로 고정시켜야 합니다. PHP 5.6 또는 7.x를 필요로 하는 라이브러리를 가져와서는 안됩니다. -또한 불필요한 라이브러리가 들어오는 것을 막기 위해 반드시 아래의 명령으로 업데이트합니다. - - composer update --no-dev --optimize-autoloader +이 파일의 내용은 공식 매뉴얼로 옮겨졌습니다. +- [이슈 및 PR 작성 방법](https://rhymix.org/manual/contrib/github) +- [코딩 규칙](https://rhymix.org/manual/contrib/coding-standards) +- [GPL: 개발자, 디자이너, 사용자 등의 권리와 의무](https://rhymix.org/manual/contrib/license) diff --git a/COPYRIGHT b/COPYRIGHT deleted file mode 100644 index 6c9ed950e..000000000 --- a/COPYRIGHT +++ /dev/null @@ -1,2 +0,0 @@ -Copyright (c) Rhymix Developers and Contributors -Copyright (c) NAVER diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 8144ce46e..000000000 --- a/Gruntfile.js +++ /dev/null @@ -1,87 +0,0 @@ -module.exports = function(grunt) { - "use strict"; - - grunt.file.defaultEncoding = 'utf8'; - - grunt.initConfig({ - jshint: { - files: [ - 'Gruntfile.js', - 'common/js/*.js', - 'modules/admin/tpl/js/*.js', - 'modules/board/tpl/js/*.js', - 'modules/board/skins/*/*.js', - 'modules/editor/tpl/js/*.js', - 'modules/menu/tpl/js/*.js', - 'modules/widget/tpl/js/*.js', - ], - options : { - ignores : [ - '**/jquery*.js', - '**/swfupload.js', - '**/**.min.js', - '**/*-packed.js', - '**/*.compressed.js', - '**/jquery-*.js', - '**/jquery.*.js', - 'common/js/html5.js', - 'common/js/x.js', - 'common/js/xe.js', - 'common/js/xml2json.js', - 'common/js/modernizr.js', - 'vendor/**', - 'tests/**', - ] - } - }, - csslint: { - 'common-css': { - options: { - import : 2, - 'adjoining-classes' : false, - 'box-model' : false, - 'box-sizing' : false, - 'font-sizes' : false, - 'duplicate-background-images' : false, - 'order-alphabetical' : false, - 'ids' : false, - 'important' : false, - 'overqualified-elements' : false, - 'qualified-headings' : false, - 'star-property-hack' : false, - 'underscore-property-hack' : false, - }, - src: [ - 'common/css/*.css', - '!common/css/bootstrap.css', - '!common/css/bootstrap-responsive.css', - '!**/*.min.css', - '!vendor/**', - '!tests/**', - ] - } - }, - phplint: { - default : { - options: { - phpCmd: "php", - }, - src: [ - "**/*.php", - "!files/**", - "!tests/**", - "!tools/**", - "!common/libraries/**", - "!vendor/**", - "!tests/_output/**" - ], - }, - } - }); - - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-contrib-csslint'); - grunt.loadNpmTasks('grunt-phplint'); - - grunt.registerTask('lint', ['jshint', 'csslint', 'phplint']); -}; diff --git a/README.md b/README.md index 9f043be7f..b892e26e1 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,13 @@ +[![Rhymix](./common/img/logo.png)](https://rhymix.org) + +![PHP Lint & Codeception](https://github.com/rhymix/rhymix/workflows/PHP%20Lint%20&%20Codeception/badge.svg) + # 한국어 -[![Rhymix](https://cloud.githubusercontent.com/assets/8565457/12881857/7c3e69d6-ce90-11e5-94dc-8a592cf9ab7d.png)](https://www.rhymix.org) Rhymix(라이믹스)는 누구든지 쉽고 자유롭게 독립적인 홈페이지를 만들어 자신을 표현하고 커뮤니티를 키워나갈 수 있도록 돕기 위한 CMS(content management system)입니다. -[XpressEngine](https://www.xpressengine.com) 1.8 버전을 fork(가지치기)하여 진행하는 프로젝트로, +[XpressEngine](https://xe1.xpressengine.com) 1.8 버전을 fork(가지치기)하여 진행하는 프로젝트로, 누구나 무료로 사용할 수 있고 개발에 참여할 수도 있는 자유 소프트웨어(free software)입니다. Rhymix는 "시를 짓다, 운을 맞추다"라는 의미의 "rhyme"과 @@ -22,7 +25,7 @@ Rhymix는 개발자와 사용자가 서로의 권리와 책임을 존중하는 개발자 위주, 서비스 제공자 위주로 나아가는 현대의 IT 동향을 무차별적으로 받아들이기보다는 사용자의 주권과 열린 인터넷 환경을 보호하는 기술을 집중적으로 발굴하며, 우리나라 인터넷 커뮤니티의 성장을 이끌었던 90년대 제로보드와 2000년대 XE의 정신을 이어받아 -2010년대 후반 현재 위기에 처한 오픈 웹을 지키고 회복시키는 일에 앞장서고자 합니다. +2020년대 현재 위기에 처한 오픈 웹을 지키고 회복시키는 일에 앞장서고자 합니다. 그러기 위해서는 다른 어떤 CMS보다도 일반 사용자를 위한 편리성이 가장 뛰어나야 합니다. @@ -33,14 +36,15 @@ Rhymix는 개발자와 사용자가 서로의 권리와 책임을 존중하는 ### 설치 환경 -Rhymix를 사용하려면 PHP 7.0 이상, MySQL 5.0.7 이상 버전이 필요합니다. -자세한 설치 환경은 [매뉴얼](https://github.com/rhymix/rhymix-docs/blob/master/ko/introduction/requirements.md)을 참고하십시오. +Rhymix를 사용하려면 PHP 7.4 이상, MySQL 또는 MariaDB가 필요합니다. +자세한 설치 환경은 [매뉴얼](https://rhymix.org/manual/introduction/requirements)을 참고하십시오. ### 개발 참여 Rhymix는 개발자, 디자이너, 번역가 등의 도움과 일반 사용자들의 버그 신고를 환영합니다. 참여를 원하시는 분은 질서있고 효율적인 프로젝트 운영을 위해 -[CONTRIBUTING.md](./CONTRIBUTING.md)를 먼저 읽어 주시기 바랍니다. +[이슈 및 PR 작성 방법](https://rhymix.org/manual/contrib/github)과 +[코딩 규칙](https://rhymix.org/manual/contrib/coding-standards)을 먼저 읽어 주시기 바랍니다. 보안 취약점을 발견하셨다면 해커들에게 알려지기 전에 먼저 패치를 작성할 수 있도록 devops@rhymix.org로 알려 주시면 감사하겠습니다. @@ -62,7 +66,7 @@ Rhymix는 [GNU GPL v2](http://korea.gnu.org/documents/copyleft/gpl.ko.html) 누구나 무료로 사용할 수 있고 개발에 참여할 수도 있습니다. Rhymix는 [NAVER](https://www.navercorp.com/)가 일부 저작권을 가진 -[XpressEngine](https://www.xpressengine.com)의 소스코드에 바탕을 두고 있습니다. +[XpressEngine](https://xe1.xpressengine.com)의 소스코드에 바탕을 두고 있습니다. Rhymix 개발자들이 추가 및 변경한 부분의 저작권은 해당 개발자들에게 있습니다. XpressEngine은 초창기에 GPL을 사용하다가 버전 1.4.0부터 LGPL로 전환했지만, @@ -78,11 +82,10 @@ Rhymix의 소스코드를 수정하거나 확장 기능을 직접 개발하여 이러한 소스코드는 모두 GPL 라이선스의 적용을 받습니다. # English -[![Rhymix](https://cloud.githubusercontent.com/assets/8565457/12881857/7c3e69d6-ce90-11e5-94dc-8a592cf9ab7d.png)](https://www.rhymix.org) Rhymix is a content management system (CMS) for everyone who wants to create independent homepages to express themselves and build their communities easily and freely. -Rhymix is a fork of [XpressEngine](https://www.xpressengine.com) version 1.8 and is free software that anyone can use for free and participate. +Rhymix is a fork of [XpressEngine](https://xe1.xpressengine.com) version 1.8 and is free software that anyone can use for free and participate. Rhymix is a combination of "rhyme" in the sense of "making a poem, adjusting the sound", and "remix" in the sense of "combining, changing". Rhymix cheers everyone who freely creates and/or assembles new things in the Internet space by combining various software and contents. @@ -94,8 +97,7 @@ a world where everyone can speak their voice without relying on a centralized SN a future where ordinary bloggers, students, the disabled and others as well as developers of startups can build a home in cyberspace and communicate with each other. Rather than indiscriminately accepting modern IT trends that are centered on developers and service providers, -we focused on technologies that protect users' sovereignty and open Internet environment. Taking the spirit of ZeroBoard and XpressEngine, which led the growth of the Korean Internet communities in the 1990s and 2000s respectively, Rhymix wants to take the lead in recovering and restoring the open web in the crisis of late 2010s. - +we focused on technologies that protect users' sovereignty and open Internet environment. Taking the spirit of ZeroBoard and XpressEngine, which led the growth of the Korean Internet communities in the 1990s and 2000s respectively, Rhymix wants to take the lead in recovering and restoring the open web in the crisis of the 2020s. This requires the most convenience for the average user over any other CMS. @@ -106,13 +108,14 @@ This requires the most convenience for the average user over any other CMS. ### Installation Environment -Rhymix requires PHP 7.0 or higher, and MySQL 5.0.7 or higher. -Please see the online manual for more information on server requirements. +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 Rhymix welcomes developers, designers, translators, and bugs to the public. -If you would like to participate, please read [CONTRIBUTING.md](./CONTRIBUTING.md) first to ensure efficient and orderly project management. +If you would like to participate, please read [Issue and PR Submission Guide](https://rhymix.org/manual/contrib/github) +and [Coding Standards](https://rhymix.org/manual/contrib/coding-standards) first to ensure efficient and orderly project management. If you have found a security vulnerability, please let us know at devops@rhymix.org so that we can make a patch before it is exploited. @@ -129,7 +132,7 @@ If you have found a security vulnerability, please let us know at devops@rhymix. Rhymix is a free software licensed under the [GNU GPL v2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.html) or later. Free software is a program that emphasizes the rights and responsibilities of developers and users to participate or use in freedom. -Rhymix is based on the source code of [XpressEngine](https://www.xpressengine.com) ([Github](https://github.com/xpressengine/xe-core/)), which [NAVER](https://www.navercorp.com/) is partly copyrighted free software. +Rhymix is based on the source code of [XpressEngine](https://xe1.xpressengine.com) ([Github](https://github.com/xpressengine/xe-core/)), which is free software partly owned by [NAVER](https://www.navercorp.com/). Rhymix developers have copyrights on the added and modified code. XpressEngine has been licensed under the GPL in its early days and switched to LGPL from version 1.4.0, but Rhymix has reverted its licenses to the GPL for further protection of the rights and spirit of free software. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..065605150 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,17 @@ +Security Policy +--------------- + +### Supported Versions + +Only the latest version is actively supported. + +## Reporting a Vulnerability + +Please report possible vulnerabilities by email to devops@rhymix.org. +Please DO NOT use GitHub issues or pull requests for this purpose. + +We do not consider it a vulnerability if the superuser (is_admin=Y) account +can insert scripts or delete information. That's what the superuser account is for! +It will, however, be considered a serious vulnerability if someone else can +trick a superuser to perform such actions inadvertently, +for example through a CSRF attack. diff --git a/addons/adminlogging/conf/info.xml b/addons/adminlogging/conf/info.xml index 0cb06b920..928157628 100644 --- a/addons/adminlogging/conf/info.xml +++ b/addons/adminlogging/conf/info.xml @@ -1,7 +1,7 @@ 어드민 메뉴 접근 로깅 - admin menu access logging + Logging Access to the Administrator Menu 后台访问日志 管理選單訪問日誌 @@ -13,8 +13,11 @@ 管理選單訪問紀錄及登入日誌。 - 1.7 - 2013-11-27 + + This addon will record Rhymix administrators' access to the menu. + + RX_VERSION + RX_CORE NAVER diff --git a/addons/autolink/autolink.js b/addons/autolink/autolink.js index c118be337..b2c5534c5 100644 --- a/addons/autolink/autolink.js +++ b/addons/autolink/autolink.js @@ -24,7 +24,7 @@ var thisPlugin = this; // extract target text nodes - this.extractTargets($('.xe_content')); + this.extractTargets($('.rhymix_content, .xe_content')); $(this.targets).each(function(){ thisPlugin.cast('AUTOLINK', [this]); @@ -40,6 +40,7 @@ content = content.replace(url_regex, function(match, p1, offset, string) { var match; var suffix = ''; + var attribute = ''; if (p1.indexOf('(') < 0 && p1.match(/\)$/)) { p1 = p1.replace(/\)$/, ''); suffix = ')'; @@ -53,7 +54,10 @@ p1 = match[1]; suffix = match[2]; } - return '' + p1 + '' + suffix; + if(!isSameOrigin(location.href, p1)) { + attribute = ' target="_blank"'; + } + return '' + p1 + '' + suffix; }); $(textNode).before(dummy); @@ -63,7 +67,7 @@ }, extractTargets : function(obj) { var thisPlugin = this; - var wrap = $('.xe_content', obj); + var wrap = $('.rhymix_content, .xe_content', obj); if(wrap.length) { this.extractTargets(wrap); return; @@ -92,10 +96,15 @@ }); xe.registerPlugin(new AutoLink()); - - $(document).on('click', '.xe_content a', function() { - if (!$(this).attr("target")) { - $(this).attr("target", "_blank"); + + $(document).on('click', '.rhymix_content a, .xe_content a', function() { + var $this = $(this); + var href = $this.attr('href'); + if(!href || /^(?:javascript|mailto):|#/.test(href)) { + return; + } + if (!$this.attr("target") && !isSameOrigin(location.href, href)) { + $this.attr("target", "_blank"); } }); diff --git a/addons/autolink/conf/info.xml b/addons/autolink/conf/info.xml index fb22d052d..efea921a5 100644 --- a/addons/autolink/conf/info.xml +++ b/addons/autolink/conf/info.xml @@ -36,8 +36,8 @@ 是種可將文章及回覆內容中的 URL 網址字串自動轉換成連結的附加元件。 - 1.7 - 2013-11-27 + RX_VERSION + RX_CORE NAVER diff --git a/addons/counter/conf/info.xml b/addons/counter/conf/info.xml index d92ea06a8..ad1c983b0 100644 --- a/addons/counter/conf/info.xml +++ b/addons/counter/conf/info.xml @@ -45,8 +45,8 @@ 使用XE的網站訪問統計模組記錄網站訪問資料。 將狀態設置成"使用"時,才會紀錄網站訪問資料。 - 1.7 - 2013-11-27 + RX_VERSION + RX_CORE NAVER diff --git a/addons/counter/counter.addon.php b/addons/counter/counter.addon.php index 65d665bd5..d1276eacc 100644 --- a/addons/counter/counter.addon.php +++ b/addons/counter/counter.addon.php @@ -10,7 +10,7 @@ if(!defined('__XE__')) * @brief Counter add-on */ // Execute if called_position is before_display_content -if($called_position == 'before_module_init' && Context::get('module') != 'admin' && Context::getResponseMethod() == 'HTML' && Context::isInstalled() && !isCrawler()) +if($called_position == 'before_display_content' && Context::get('module') != 'admin' && Context::getResponseMethod() == 'HTML' && Context::isInstalled() && !isCrawler()) { $oCounterController = getController('counter'); $oCounterController->counterExecute(); diff --git a/addons/member_extra_info/conf/info.xml b/addons/member_extra_info/conf/info.xml index ef2fb2e13..3a1005c23 100644 --- a/addons/member_extra_info/conf/info.xml +++ b/addons/member_extra_info/conf/info.xml @@ -36,8 +36,8 @@ 可將用戶資料中的暱稱圖片、用戶圖示、簽名檔等資料顯示到頁面當中。 - 1.7 - 2013-11-27 + RX_VERSION + RX_CORE NAVER diff --git a/addons/member_extra_info/member_extra_info.addon.php b/addons/member_extra_info/member_extra_info.addon.php index 57fbc4fe8..1d8d89e37 100644 --- a/addons/member_extra_info/member_extra_info.addon.php +++ b/addons/member_extra_info/member_extra_info.addon.php @@ -22,7 +22,8 @@ if($called_position != "before_display_content" || Context::get('act') == 'dispP return; } // Include a file having functions to replace member image name/mark -require_once('./addons/member_extra_info/member_extra_info.lib.php'); +require_once __DIR__ . '/member_extra_info.lib.php'; + // 1. Find a part
content
in the output document, change it to image name/mark by using MemberController::transImageName() $temp_output = preg_replace_callback('!<(div|span|a)([^\>]*)member_([0-9]+)([^\>]*)>(.*?)\<\/(div|span|a)\>!is', 'memberTransImageName', $output); if($temp_output) diff --git a/addons/member_extra_info/member_extra_info.lib.php b/addons/member_extra_info/member_extra_info.lib.php index 64b0015a4..38fbe3a7f 100644 --- a/addons/member_extra_info/member_extra_info.lib.php +++ b/addons/member_extra_info/member_extra_info.lib.php @@ -29,29 +29,31 @@ function memberTransImageName($matches) $_tmp = &$GLOBALS['_transImageNameList'][$member_srl]; // If pre-defined data in the global variables, return it - if(!$_tmp->cached) + if(!isset($_tmp->cached) || !$_tmp->cached) { $_tmp->cached = true; $image_name_file = sprintf('files/member_extra_info/image_name/%s%d.gif', getNumberingPath($member_srl), $member_srl); $image_mark_file = sprintf('files/member_extra_info/image_mark/%s%d.gif', getNumberingPath($member_srl), $member_srl); - if(file_exists(_XE_PATH_ . $image_name_file)) + if(file_exists(RX_BASEDIR . $image_name_file)) { - $_tmp->image_name_file = $image_name_file . '?' . date('YmdHis', filemtime(_XE_PATH_ . $image_name_file)); + $_tmp->image_name_file = $image_name_file . '?' . date('YmdHis', filemtime(RX_BASEDIR . $image_name_file)); $image_name_file = $_tmp->image_name_file; } else { + $_tmp->image_name_file = ''; $image_name_file = ''; } - if(file_exists(_XE_PATH_ . $image_mark_file)) + if(file_exists(RX_BASEDIR . $image_mark_file)) { - $_tmp->image_mark_file = $image_mark_file . '?' . date('YmdHis', filemtime(_XE_PATH_ . $image_mark_file)); + $_tmp->image_mark_file = $image_mark_file . '?' . date('YmdHis', filemtime(RX_BASEDIR . $image_mark_file)); $image_mark_file = $_tmp->image_mark_file; } else { + $_tmp->image_mark_file = ''; $image_mark_file = ''; } diff --git a/addons/photoswipe/conf/info.xml b/addons/photoswipe/conf/info.xml index 82d6b2a96..6c8b3a203 100644 --- a/addons/photoswipe/conf/info.xml +++ b/addons/photoswipe/conf/info.xml @@ -6,11 +6,11 @@ 본문 이미지를 하나의 갤러리 처럼 볼 수 있도록 하는 애드온입니다. - Swipe your images of an document on your screens. + Swipe your images of a post on your screen. MIT License (codes from http://photoswipe.com/), GPLv2 (other codes by Rhymix contributors) - 1.0.1 - 2016-04-27 + RX_VERSION + RX_CORE misol @@ -23,12 +23,16 @@ 파일이름 출력 설정 + Display Filenames 넘겨보기 실행시 하단에 파일이름을 출력할 것인지 여부를 선택합니다. 기본값은 사용하도록 되어있습니다. + Whether the PhotoSwipe displays the filenames or not. The default is to display. 사용 + Display 사용 안함 + Hide diff --git a/addons/photoswipe/rx_photoswipe.js b/addons/photoswipe/rx_photoswipe.js index cc4891c9d..d21fbabce 100644 --- a/addons/photoswipe/rx_photoswipe.js +++ b/addons/photoswipe/rx_photoswipe.js @@ -1,12 +1,14 @@ /* Modified version of a http://photoswipe.com/documentation/getting-started.html example. Modified by misol for rhymix */ -var getPSImageSize = function(src) { - var testImg = new Image(); - testImg.src = src; - +var getPSImageSize = function(el) { var size = new Array(); - size[0] = testImg.width; - size[1] = testImg.height; - + size[0] = el.naturalWidth ? el.naturalWidth : (el.width ? el.width : 0); + size[1] = el.naturalHeight ? el.naturalHeight : (el.height ? el.height : 0); + if (!size[0] || !size[1]) { + var test = new Image(); + test.src = el.src; + size[0] = test.naturalWidth ? test.naturalWidth : (test.width ? test.width : 1000); + size[1] = test.naturalHeight ? test.naturalHeight : (test.height ? test.height : 1000); + } return size; } @@ -23,7 +25,7 @@ var initPhotoSwipeFromDOM = function(gallerySelector) { // CSS selector for photoswipe items. var ps_find_selector = 'img:not(' + ps_skip_elements + ps_skip_class + '), img' + ps_enroll_class; - // parse slide data (url, title, size ...) from DOM elements + // parse slide data (url, title, size ...) from DOM elements // (children of gallerySelector) var parseThumbnailElements = function(el) { var imgElements = $(el).find(ps_find_selector), @@ -37,12 +39,12 @@ var initPhotoSwipeFromDOM = function(gallerySelector) { imgEl = imgElements.get(i); // element - // include only element nodes - if(imgEl.nodeType !== 1 || !$(imgEl).attr('data-pswp-pid')) { + // include only element nodes + if (imgEl.nodeType !== 1 || !imgEl.src || !$(imgEl).attr('data-pswp-pid')) { continue; } - size = getPSImageSize($(imgEl).attr('src')); + size = getPSImageSize(imgEl); // create slide object item = { @@ -54,11 +56,11 @@ var initPhotoSwipeFromDOM = function(gallerySelector) { var ps_skip_alt_class = '.photoswipe-no-caption'; if(imgEl.alt && !$(imgEl).is(ps_skip_alt_class)) { - item.title = imgEl.alt; + item.title = imgEl.alt; } if(imgEl.title && !$(imgEl).is(ps_skip_alt_class)) { - item.title = imgEl.title; + item.title = imgEl.title; } item.el = imgEl; // save link to element for getThumbBoundsFn @@ -85,7 +87,7 @@ var initPhotoSwipeFromDOM = function(gallerySelector) { if(!clickedListItem) { return; } - + e = e || window.event; e.preventDefault ? e.preventDefault() : e.returnValue = false; @@ -106,8 +108,8 @@ var initPhotoSwipeFromDOM = function(gallerySelector) { }*/ for (var i = 0; i < numChildNodes; i++) { - if(childNodes[i].nodeType !== 1 || !$(childNodes[i]).attr('data-pswp-pid')) { - continue; + if(childNodes[i].nodeType !== 1 || !$(childNodes[i]).attr('data-pswp-pid')) { + continue; } if(childNodes[i] === clickedListItem) { @@ -138,7 +140,7 @@ var initPhotoSwipeFromDOM = function(gallerySelector) { if(!vars[i]) { continue; } - var pair = vars[i].split('='); + var pair = vars[i].split('='); if(pair.length < 2) { continue; } @@ -170,7 +172,7 @@ var initPhotoSwipeFromDOM = function(gallerySelector) { // See Options -> getThumbBoundsFn section of documentation for more info var thumbnail = items[index].el, pageYScroll = window.pageYOffset || document.documentElement.scrollTop, - rect = thumbnail.getBoundingClientRect(); + rect = thumbnail.getBoundingClientRect(); return {x:rect.left, y:rect.top + pageYScroll, w:rect.width}; }, @@ -189,7 +191,7 @@ var initPhotoSwipeFromDOM = function(gallerySelector) { // PhotoSwipe opened from URL if(fromURL) { if(options.galleryPIDs) { - // parse real index when custom PIDs are used + // parse real index when custom PIDs are used // http://photoswipe.com/documentation/faq.html#custom-pid-in-url for(var j = 0; j < items.length; j++) { if(items[j].pid == index) { @@ -230,6 +232,7 @@ var initPhotoSwipeFromDOM = function(gallerySelector) { var regx_skip = /(?:(modules|addons|classes|common|layouts|libs|widgets|widgetstyles)\/)/i; var regx_allow_i6pngfix = /(?:common\/tpl\/images\/blank\.gif$)/i; + var isMobile = String(navigator.userAgent).match(/mobile/i); var galleryImgEls = $(galleryElements[i]).find(ps_find_selector); for(var j = 0, jl = galleryImgEls.length; j < jl; j++) { // skip components @@ -238,6 +241,11 @@ var initPhotoSwipeFromDOM = function(gallerySelector) { //$(galleryImgEls[j]).attr('data-pswp-uid', i+1); $(galleryImgEls[j]).attr('data-pswp-pid', j+1); + // Fix stretching of image on mobile + if (isMobile) { + galleryImgEls[j].style.height = 'auto'; + galleryImgEls[j].height = null; + } } } @@ -256,4 +264,4 @@ var initPhotoSwipeFromDOM = function(gallerySelector) { // execute above function -initPhotoSwipeFromDOM('.xe_content'); \ No newline at end of file +initPhotoSwipeFromDOM('.rhymix_content, .xe_content'); diff --git a/addons/point_level_icon/conf/info.xml b/addons/point_level_icon/conf/info.xml index 3597262b9..c9eeda6f7 100644 --- a/addons/point_level_icon/conf/info.xml +++ b/addons/point_level_icon/conf/info.xml @@ -45,8 +45,8 @@ 使用點數系統時,可以在用戶名前顯示等級圖案。 等級圖案可以在模組 > 點數系統中進行選擇。 - 1.7 - 2013-11-27 + RX_VERSION + RX_CORE NAVER diff --git a/addons/point_level_icon/point_level_icon.addon.php b/addons/point_level_icon/point_level_icon.addon.php index fd3b2919e..0543da966 100644 --- a/addons/point_level_icon/point_level_icon.addon.php +++ b/addons/point_level_icon/point_level_icon.addon.php @@ -17,7 +17,7 @@ if($called_position != "before_display_content" || Context::get('act') == 'dispP return; } -require_once(_XE_PATH_ . 'addons/point_level_icon/point_level_icon.lib.php'); +require_once __DIR__ . '/point_level_icon.lib.php'; $temp_output = preg_replace_callback('!<(div|span|a)([^\>]*)member_([0-9\-]+)([^\>]*)>(.*?)\<\/(div|span|a)\>!is', function($matches) use($addon_info) { return pointLevelIconTrans($matches, $addon_info); diff --git a/addons/point_level_icon/point_level_icon.lib.php b/addons/point_level_icon/point_level_icon.lib.php index c7679f2b2..96457dacc 100644 --- a/addons/point_level_icon/point_level_icon.lib.php +++ b/addons/point_level_icon/point_level_icon.lib.php @@ -13,7 +13,7 @@ function pointLevelIconTrans($matches, $addon_info) return $matches[0]; } - if($addon_info->icon_duplication != 'N') + if(!isset($addon_info->icon_duplication) || $addon_info->icon_duplication !== 'N') { // Check Group Image Mark $oMemberModel = getModel('member'); @@ -28,21 +28,21 @@ function pointLevelIconTrans($matches, $addon_info) if(!isset($GLOBALS['_pointLevelIcon'][$member_srl])) { // Get point configuration - if(!$GLOBALS['_pointConfig']) + if(!isset($GLOBALS['_pointConfig'])) { - $oModuleModel = getModel('module'); - $GLOBALS['_pointConfig'] = $oModuleModel->getModuleConfig('point'); + $GLOBALS['_pointConfig'] = getModel('module')->getModuleConfig('point') ?? new stdClass; } $config = $GLOBALS['_pointConfig']; // Get point model - if(!$GLOBALS['_pointModel']) + if(!isset($GLOBALS['_pointModel'])) { $GLOBALS['_pointModel'] = getModel('point'); } - $oPointModel = &$GLOBALS['_pointModel']; + $oPointModel = $GLOBALS['_pointModel']; // Get points + $exists = false; $point = $oPointModel->getPoint($member_srl, false, $exists); if(!$exists) { @@ -54,14 +54,15 @@ function pointLevelIconTrans($matches, $addon_info) $text = $matches[5]; // Get a path where level icon is - $level_icon = sprintf('%smodules/point/icons/%s/%d.gif', Context::getRequestUri(), $config->level_icon, $level); + $level_icon_type = $config->level_icon_type ?? 'gif'; + $level_icon = sprintf('%smodules/point/icons/%s/%d.%s', Context::getRequestUri(), $config->level_icon, $level, $level_icon_type); // Get per to go to the next level if not a top level $per = NULL; if($level < $config->max_level) { $next_point = $config->level_step[$level + 1]; - $present_point = $config->level_step[$level]; + $present_point = $config->level_step[$level] ?? 0; if($next_point > 0) { $per = (int) (($point - $present_point) / ($next_point - $present_point) * 100); diff --git a/addons/recaptcha/conf/info.xml b/addons/recaptcha/conf/info.xml deleted file mode 100644 index 5908ba095..000000000 --- a/addons/recaptcha/conf/info.xml +++ /dev/null @@ -1,119 +0,0 @@ - - - reCAPTCHA - reCAPTCHA - 구글 reCAPTCHA 서비스를 사용하여 자동 가입 스팸을 방지합니다. - Prevent automated signups and spam with Google's reCAPTCHA service. - 1.0.0 - 2016-05-27 - - Kijin Sung - Kijin Sung - - - - Site Key - Site Key - - - Secret Key - Secret Key - - - 회원가입에 사용 - Use on Signup Form - - - Yes - - - 아니오 - No - - - - ID/PW찾기에 사용 - Use on Account Recovery Form - - - Yes - - - 아니오 - No - - - - 글쓰기에 사용 - Use on New Document - - 아니오 - No - - - - Yes - - - - 댓글쓰기에 사용 - Use on New Comment - - 아니오 - No - - - - Yes - - - - 사용 대상 - Target Users - - 비회원만 사용 - Non-members Only - - - 모든 방문자에게 사용 - Everyone - - - - 사용 빈도 - Target Frequency - - 매번 사용 - Every Time - - - 방문자당 최초 1회만 사용 - First Time Only - - - - 테마 - Theme - - 밝은 테마 - Light - - - 어두운 테마 - Dark - - - - 크기 - Size - - 보통 - Normal - - - 작게 - Compact - - - - diff --git a/addons/recaptcha/lang/en.php b/addons/recaptcha/lang/en.php deleted file mode 100644 index 02baffd5a..000000000 --- a/addons/recaptcha/lang/en.php +++ /dev/null @@ -1,4 +0,0 @@ -msg_recaptcha_connection_error = 'An error occurred while connecting to the reCAPTCHA verification server.'; -$lang->msg_recaptcha_server_error = 'An error occurred while verifying your reCAPTCHA response.'; -$lang->msg_recaptcha_invalid_response = 'Please check reCAPTCHA.'; diff --git a/addons/recaptcha/lang/ko.php b/addons/recaptcha/lang/ko.php deleted file mode 100644 index e26e413f3..000000000 --- a/addons/recaptcha/lang/ko.php +++ /dev/null @@ -1,4 +0,0 @@ -msg_recaptcha_connection_error = 'reCAPTCHA 스팸방지 서버에 접속하는 도중 오류가 발생했습니다.'; -$lang->msg_recaptcha_server_error = 'reCAPTCHA 스팸방지 서버와 통신하는 도중 오류가 발생했습니다.'; -$lang->msg_recaptcha_invalid_response = 'reCAPTCHA 스팸방지 기능을 체크해 주십시오.'; diff --git a/addons/recaptcha/recaptcha.addon.php b/addons/recaptcha/recaptcha.addon.php deleted file mode 100644 index 6a75c1e49..000000000 --- a/addons/recaptcha/recaptcha.addon.php +++ /dev/null @@ -1,57 +0,0 @@ -site_key || !$addon_info->secret_key || $called_position !== 'before_module_init') -{ - return; -} - -$current_action = Context::get('act'); -$current_member = Context::get('logged_info'); - -if ($current_member->is_admin === 'Y') -{ - $enable_captcha = false; -} -elseif ($addon_info->target_users !== 'everyone' && $current_member->member_srl) -{ - $enable_captcha = false; -} -elseif ($addon_info->target_frequency !== 'every_time' && isset($_SESSION['recaptcha_authenticated']) && $_SESSION['recaptcha_authenticated']) -{ - $enable_captcha = false; -} -elseif ($addon_info->use_signup === 'Y' && preg_match('/^(?:disp|proc)Member(?:SignUp|Insert)/i', $current_action)) -{ - $enable_captcha = true; -} -elseif ($addon_info->use_recovery === 'Y' && preg_match('/^(?:disp|proc)Member(?:FindAccount|ResendAuthMail)/i', $current_action)) -{ - $enable_captcha = true; -} -elseif ($addon_info->use_document === 'Y' && preg_match('/^(?:disp|proc)Board(Write|InsertDocument)/i', $current_action)) -{ - $enable_captcha = true; -} -elseif ($addon_info->use_comment === 'Y' && (preg_match('/^(?:disp|proc)Board(Content|InsertComment)/i', $current_action) || (!$current_action && Context::get('document_srl')))) -{ - $enable_captcha = true; -} -else -{ - $enable_captcha = false; -} - -if ($enable_captcha) -{ - include_once __DIR__ . '/recaptcha.class.php'; - reCAPTCHA::init($addon_info); - - if (strncasecmp('proc', $current_action, 4) === 0) - { - getController('module')->addTriggerFunction('moduleObject.proc', 'before', 'reCAPTCHA::check'); - } - else - { - Context::set('captcha', new reCAPTCHA()); - } -} diff --git a/addons/recaptcha/recaptcha.class.php b/addons/recaptcha/recaptcha.class.php deleted file mode 100644 index 11177627b..000000000 --- a/addons/recaptcha/recaptcha.class.php +++ /dev/null @@ -1,70 +0,0 @@ - self::$config->secret_key, - 'response' => $response, - 'remoteip' => \RX_CLIENT_IP, - )); - } - catch (\Requests_Exception $e) - { - throw new Rhymix\Framework\Exception('recaptcha.msg_recaptcha_connection_error'); - } - - $verify = @json_decode($verify_request->body, true); - if ($verify && isset($verify['error-codes']) && in_array('invalid-input-response', $verify['error-codes'])) - { - throw new Rhymix\Framework\Exception('recaptcha.msg_recaptcha_invalid_response'); - } - elseif (!$verify || !$verify['success'] || (isset($verify['error-codes']) && $verify['error-codes'])) - { - throw new Rhymix\Framework\Exception('recaptcha.msg_recaptcha_server_error'); - } - else - { - $_SESSION['recaptcha_authenticated'] = true; - return true; - } - } - - public function __construct() - { - if (!self::$scripts_added) - { - self::$scripts_added = true; - Context::loadFile(array('./addons/recaptcha/recaptcha.js', 'body')); - Context::addHtmlFooter(''); - $html = '
'; - $html = sprintf($html, escape(self::$config->site_key), self::$config->theme ?: 'light', self::$config->size ?: 'normal'); - Context::addHtmlFooter($html); - } - } - - public function __toString() - { - return sprintf('
', self::$instances_inserted++); - } -} diff --git a/addons/recaptcha/recaptcha.js b/addons/recaptcha/recaptcha.js deleted file mode 100644 index 4cfd3fb0d..000000000 --- a/addons/recaptcha/recaptcha.js +++ /dev/null @@ -1,40 +0,0 @@ - -function reCaptchaCallback() { - var recaptcha_config = $("#recaptcha-config"); - var recaptcha_instances = $(".g-recaptcha"); - var recaptcha_instance_id = 1; - - if (recaptcha_instances.size() === 0) { - var autoinsert_candidates = $("form").filter(function() { - var actinput = $("input[name='act']", this); - if (actinput.size() && actinput.val() && actinput.val().match(/^proc.+(Insert(Document|Comment|)|FindAccount|ResendAuthMail)/i)) { - return true; - } - var procfilter = $(this).attr("onsubmit"); - if (procfilter && procfilter.match(/procFilter\b.+\binsert/i)) { - return true; - } - return false; - }); - autoinsert_candidates.each(function() { - var new_instance = $('
'); - new_instance.attr("id", "recaptcha-instance-" + recaptcha_instance_id++); - var autoinsert_point = $(this).find("button[type='submit'],input[type='submit']").parent(); - if (autoinsert_point.size()) { - new_instance.insertBefore(autoinsert_point); - } else { - new_instance.appendTo($(this)); - } - }); - var recaptcha_instances = $(".g-recaptcha"); - } - - recaptcha_instances.each(function() { - var instance = $(this); - grecaptcha.render(instance.attr("id"), { - sitekey: recaptcha_config.data("sitekey"), - size: recaptcha_config.data("size"), - theme: recaptcha_config.data("theme") - }); - }); -} diff --git a/addons/resize_image/btn.png b/addons/resize_image/btn.png deleted file mode 100644 index 2f48acd99..000000000 Binary files a/addons/resize_image/btn.png and /dev/null differ diff --git a/addons/resize_image/conf/info.xml b/addons/resize_image/conf/info.xml deleted file mode 100644 index 21dae4166..000000000 --- a/addons/resize_image/conf/info.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - 본문내 이미지 조절 애드온 - 本文内イメージリーサイズアドオン - 内容区图片缩放插件 - Image Resizer - Thay đổi cỡ hình ảnh - Imagen de control add-on bonmunnae - Аддон редактирования размера картинки в тексте - Image-Add-on bonmunnae - 圖片縮放 - - 본문내에 삽입된 이미지의 크기를 본문크기에 맞게 하고 클릭시 원본을 보여주는 애드온입니다. - - - 本文内に挿入されたイメージのサイズを本文の幅サイズに合わせてリーサイズし、クリックした時、オリジナルサイズのイメージを表示します。 - - - 自动调整主题内容区内的图片大小,点击将显示原始大小的插件。 - - - Addon này sẽ lấy lại kích thước nguyên bản của hình ảnh trong bài viết hoặc bình luận khi bạn bấm vào hình. - - - This addon resizes images inserted in the article. When clicked, the original images are shown. - - - La imagen corporal se inserta dentro del cuerpo para que se adapte al tamaño de la muestra original cuando hago clic en los add-ons. - - - Аддон, изменяющий размер картинки к размеру текста, при клике на картинку, появляется полное изображение. - - - Body Bild eingefügt im Inneren des Körpers zu passen die Größe des Originals zeigen, wenn ich darauf klicke auf das Add-ons. - - - 自動調整文章内的圖片大小,點擊圖片後會顯示原始大小。 - - 1.7 - 2013-11-27 - - - NAVER - NAVER - NAVER - NAVER - NAVER - NAVER - NAVER - NAVER - NAVER - - diff --git a/addons/resize_image/css/resize_image.mobile.css b/addons/resize_image/css/resize_image.mobile.css deleted file mode 100644 index 53d753413..000000000 --- a/addons/resize_image/css/resize_image.mobile.css +++ /dev/null @@ -1 +0,0 @@ -.xe_content img{max-width:100%;height:auto !important} diff --git a/addons/resize_image/js/resize_image.js b/addons/resize_image/js/resize_image.js deleted file mode 100644 index 865b4c0cb..000000000 --- a/addons/resize_image/js/resize_image.js +++ /dev/null @@ -1,268 +0,0 @@ -/** - * @brief 화면내에서 상위 영역보다 이미지가 크면 리사이즈를 하고 클릭시 원본을 보여줄수 있도록 변경 - **/ -(function($){ - -var xScreen = null; - -// 슬라이드를 위한 블랙 스크린을 만들거나 반환하는 함수 -function getScreen() { - var body = $(document.body); - var controls, imgframe, closebtn, prevbtn, nextbtn; - // 스크린이 없으면 스크린을 만든다. - if (!xScreen) { - // 검은 스크린 - xScreen = $("
") - .attr("id","xe_gallery_screen") - .css({ - position:"fixed", - display:"none", - backgroundColor:"black", - zIndex:500, - opacity:0.7 - }); - - // 이미지를 보여주고 컨트롤 버튼을 다룰 레이어 - controls = $("
") - .attr("id","xe_gallery_controls") - .css({ - position:"fixed", - display:"none", - overflow:"hidden", - zIndex:510 - }); - - // 이전 버튼 - prevbtn = $(' +
+
+ +
+
+ + diff --git a/modules/admin/tpl/config_advanced.html b/modules/admin/tpl/config_advanced.html index 066e357e4..7c4540f59 100644 --- a/modules/admin/tpl/config_advanced.html +++ b/modules/admin/tpl/config_advanced.html @@ -2,7 +2,7 @@ -
+

{$XE_VALIDATOR_MESSAGE}

@@ -13,8 +13,10 @@
- - + + + +

{$lang->about_use_rewrite}

@@ -28,6 +30,7 @@ {$lang->cmd_no} +

{$lang->about_mobile_view}

@@ -62,7 +65,7 @@
@@ -86,8 +89,8 @@
- -

{$lang->about_mobile_viewport}

+ +

{$lang->about_mobile_viewport} {$lang->restore_default_viewport}

@@ -108,6 +111,18 @@

{$lang->about_delay_session}

+
+ +
+ +

{$lang->about_delay_template_compile}

+
+
@@ -126,15 +141,21 @@
- +
- -
@@ -142,11 +163,16 @@
+
@@ -154,7 +180,7 @@
- {$lang->unit_sec} + {$lang->unit_sec}
@@ -166,6 +192,36 @@

{$lang->about_cache_truncate_method}

+
+ +
+ + + +
+

{$lang->about_cache_control_header}

+
+
+
+ +
+ +

{$lang->about_outgoing_proxy}

+
+
+
+ +
+ +

{$lang->about_partial_page_rendering}

+
+
@@ -197,17 +253,12 @@
- +
- - -
-
-
- -
- - + + +
+

{$lang->about_jquery_version}

@@ -217,3 +268,9 @@
+ + diff --git a/modules/admin/tpl/config_debug.html b/modules/admin/tpl/config_debug.html index f292961d5..77335c9e4 100644 --- a/modules/admin/tpl/config_debug.html +++ b/modules/admin/tpl/config_debug.html @@ -2,7 +2,7 @@ -
+

{$XE_VALIDATOR_MESSAGE}

@@ -56,25 +56,25 @@
- - - - - - - - + + + + + + + +
- +

{$lang->about_debug_log_filename}

- +
@@ -88,11 +88,37 @@
- + +
+ + +

{$lang->about_debug_query_comment}

+
+
+
+ +
+ + +

{$lang->about_debug_query_full_stack}

+
+
+
+ +
+ + +

{$lang->about_debug_consolidate}

+
+
+
+
+

{$lang->about_debug_write_error_log}

+

{sprintf($lang->about_debug_error_log_path, escape(ini_get('error_log')) ?: $lang->about_debug_error_log_path_empty)}

diff --git a/modules/admin/tpl/config_domains.html b/modules/admin/tpl/config_domains.html index 475bcd9a3..a78addf9d 100644 --- a/modules/admin/tpl/config_domains.html +++ b/modules/admin/tpl/config_domains.html @@ -2,7 +2,7 @@ -
+

{$XE_VALIDATOR_MESSAGE}

@@ -21,7 +21,7 @@ {$lang->use_ssl} {$lang->cmd_index_module_srl} {$lang->cmd_index_document_srl} - {$lang->cmd_modify} / {$lang->cmd_delete} + {$lang->cmd_modify} / {$lang->cmd_delete} / {$lang->cmd_copy} @@ -34,7 +34,7 @@ {preg_replace('/\\(.+$/', '', $lang->ssl_options[$domain->security ?: 'none'])} - {($domain->index_module_srl && $module_list[$domain->index_module_srl]) ? $module_list[$domain->index_module_srl]->browser_title : ''} + {($domain->index_module_srl && $module_list[$domain->index_module_srl]) ? $module_list[$domain->index_module_srl]->browser_title : ''|noescape} @@ -51,6 +51,8 @@ {$lang->cmd_delete} + / + {$lang->cmd_copy} @@ -78,7 +80,7 @@

{$lang->cmd_multidomain_configuration}

- +
diff --git a/modules/admin/tpl/config_domains_edit.html b/modules/admin/tpl/config_domains_edit.html index ed4cfc60c..f6d5c1aee 100644 --- a/modules/admin/tpl/config_domains_edit.html +++ b/modules/admin/tpl/config_domains_edit.html @@ -11,20 +11,21 @@ +
- +
- +
@@ -35,87 +36,106 @@
- +
- +
- +
{lang('admin.about_use_ssl')}
- +
- +
- +
+   +
- +
- + +
+ +
+ +
+
+ +
+ +
+ +
+
+
-
- +
+
{$lang->detail_input_header_script}
- +
-
- +
+
{$lang->detail_input_footer_script}
- +

- Favicon - Favicon + Favicon + Favicon

- +

- Mobile Home Icon + Mobile Home Icon Rhymix

- +
@@ -153,12 +173,24 @@ {$lang->about_site_default_image}
- + +
+ +
+ +
{lang('admin.about_site_default_color_scheme')}
+
+
+
- +
diff --git a/modules/admin/tpl/config_ftp.html b/modules/admin/tpl/config_ftp.html deleted file mode 100644 index 9122691a9..000000000 --- a/modules/admin/tpl/config_ftp.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - - -
-

{$lang->menu_gnb_sub['adminConfigurationFtp']}

-
-
-

{$XE_VALIDATOR_MESSAGE}

-
-
- - - - -
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-

{$lang->msg_ftp_autodetected_ftp_realpath} : {_XE_PATH_}

-
-
-
-
{$lang->use_ftp_passive_mode}
-
- - -
-
-
- -
- - -

{$lang->disable_sftp_support}

-
-
-
-
- -
-
diff --git a/modules/admin/tpl/config_header.html b/modules/admin/tpl/config_header.html index a8e7e7a1c..84e8b3a25 100644 --- a/modules/admin/tpl/config_header.html +++ b/modules/admin/tpl/config_header.html @@ -12,5 +12,6 @@
  • {$lang->subtitle_advanced}
  • {$lang->subtitle_debug}
  • {$lang->subtitle_seo}
  • +
  • {$lang->subtitle_queue}
  • {$lang->subtitle_sitelock}
  • diff --git a/modules/admin/tpl/config_notification.html b/modules/admin/tpl/config_notification.html index aab69ecdd..c3cc37685 100644 --- a/modules/admin/tpl/config_notification.html +++ b/modules/admin/tpl/config_notification.html @@ -3,7 +3,7 @@ -
    +

    {$XE_VALIDATOR_MESSAGE}

    @@ -11,29 +11,29 @@ var mail_drivers = {json_encode($mail_drivers)}; var sms_drivers = {json_encode($sms_drivers)}; - +
    - +
    - +

    {$lang->email}

    - +
    - +

    {$lang->cmd_admin_default_from_name_help}

    - +
    - +  
    - +
    - +

    {$lang->cmd_admin_default_reply_to_help}

    - +

    @@ -69,13 +69,15 @@

    - + - + - + {@ $conf_value = escape(config("mail.$driver_name.$conf_name"))} - + {@ $text_keys = ['api_domain', 'api_user', 'api_key', 'api_token']} + {@ $password_keys = ['api_pass', 'api_secret']} +
    @@ -93,27 +95,27 @@
    - +
    - +
    - +
    - - + +
    - +
    @@ -122,21 +124,21 @@
    - +
    - +
    - +
    - +
    - @@ -144,71 +146,35 @@
    - - + +
    - +
    - +
    - - + +
    - +
    - +
    - - -
    - -
    - -
    -
    - - - -
    - -
    - -
    -
    - - - -
    - -
    - -
    -
    - - - -
    - -
    - -
    -
    - - + - + - +
    - +
    - +

    {$lang->sms}

    - +
    @@ -222,13 +188,13 @@

    {$lang->cmd_admin_default_from_phone_help}

    - +

    @@ -236,74 +202,63 @@

    - + - + {@ $conf_names = array_merge($driver_definition['required'], $driver_definition['optional'])} - + - + {@ $conf_value = escape(config("sms.$driver_name.$conf_name"))} - - + {@ $text_keys = ['service_id', 'account_sid', 'api_user', 'api_key', 'api_token']} + {@ $password_keys = ['api_pass', 'api_secret', 'auth_token']} + +
    - +
    - + {@ $conf_exists = config("sms.$driver_name.api_key")} +
    - - + +
    - -
    - -
    -
    - - - -
    - -
    - -
    -
    - - - -
    - +
    - +
    - - + +
    - +
    - +
    - +
    - +
    - +

    {$lang->cmd_admin_sms_sender_key_help}

    - + - + - +
    @@ -319,7 +274,7 @@

    {$lang->cmd_admin_allow_split_sms_help}

    - +
    @@ -335,10 +290,98 @@

    {$lang->cmd_admin_allow_split_lms_help}

    - + +
    + +
    + +

    {$lang->push_notification}

    + +
    + +
    + + + +
    +
    + + + + {@ $conf_names = array_merge($driver_definition['required'], $driver_definition['optional'])} + + + + {@ $conf_value = escape(config("push.$driver_name.$conf_name"))} + + +
    + +
    + +

    {$lang->msg_advanced_mailer_about_fcm_legacy}

    +
    +
    + + + +
    + +
    + +

    {$lang->msg_advanced_mailer_about_fcm_service_account}

    +
    +
    + + + +
    + +
    + +
    +
    + + + +
    + +
    + +
    +
    + + + + + + +
    + +
    + + +
    +

    {$lang->cmd_advanced_mailer_about_allow_guest_device}

    +
    +
    +
    +
    diff --git a/modules/admin/tpl/config_queue.html b/modules/admin/tpl/config_queue.html new file mode 100644 index 000000000..4838a80f7 --- /dev/null +++ b/modules/admin/tpl/config_queue.html @@ -0,0 +1,210 @@ + + + + + + +
    +

    {$lang->cmd_queue_description}

    +
    + +
    +

    {$XE_VALIDATOR_MESSAGE}

    +
    + + + + + + + + +
    + +

    {$lang->subtitle_queue}

    + +
    + +
    + +
    +

    {$lang->cmd_queue_enabled_help}

    +
    +
    + +
    + +
    + +

    {$lang->cmd_queue_driver_help}

    +
    +
    + + + + {@ $conf_names = array_merge($driver_definition['required'], $driver_definition['optional'])} + + + + {@ $conf_value = escape(config("queue.$driver_name.$conf_name"))} + {@ $text_keys = ['host', 'user']} + {@ $number_keys = ['port', 'dbnum']} + {@ $password_keys = ['pass']} + + +
    + +
    + +
    +
    + + + +
    + +
    + +
    +
    + + + +
    + +
    + +
    +
    + + + + + + +
    + +
    + +

    {$lang->cmd_queue_call_script}

    + +
    + +
    + {@ + if (function_exists('posix_getpwuid') && function_exists('posix_getuid')): + $user_info = posix_getpwuid(posix_getuid()); + if (!empty($user_info['dir'])): + $user_info['dir'] .= DIRECTORY_SEPARATOR; + endif; + else: + $user_info = []; + $user_info['name'] = $lang->msg_queue_instructions['same_as_php']; + endif; + } +

    + {sprintf($lang->msg_queue_instructions['crontab1'], $user_info['name'] ?? 'PHP', $user_info['name'] ?? 'PHP')|noescape} +

    +
    * * * * * /usr/bin/php {\RX_BASEDIR}index.php common.cron >> {$user_info['dir']}logs{\DIRECTORY_SEPARATOR}queue.log 2>&1
    +

    + {sprintf($lang->msg_queue_instructions['crontab2'], $user_info['dir'] . 'logs')|noescape} +

    +
    +
    +

    + {$lang->msg_queue_instructions['webcron']|noescape} +

    +
    {getFullUrl('')}common/scripts/cron.php?key={config('queue.key')}
    +
    +
    +

    + {$lang->msg_queue_instructions['systemd1']|noescape} +

    +
    [Unit]
    +Description=Rhymix Queue Service
    +
    +[Service]
    +ExecStart=/usr/bin/php {\RX_BASEDIR}index.php common.cron
    +User={$user_info['name']}
    +

    + {$lang->msg_queue_instructions['systemd2']|noescape} +

    +
    [Unit]
    +Description=Rhymix Queue Timer
    +
    +[Timer]
    +OnCalendar=*-*-* *:*:00
    +Unit=rhymix-queue.service
    +
    +[Install]
    +WantedBy=multi-user.target
    +

    + {$lang->msg_queue_instructions['systemd3']|noescape} +

    +
    systemctl daemon-reload
    +systemctl start rhymix-queue.timer
    +systemctl enable rhymix-queue.timer
    +
    +
    + +
    + +
    + +
    +
    + +
    + +
    + + +

    {$lang->cmd_queue_webcron_display_errors_help}

    +
    +
    + +
    + +
    + + {$lang->unit_min} +
    +

    {sprintf($lang->cmd_queue_interval_help, ini_get('max_execution_time'))|noescape}

    +
    +
    + +
    + +
    + +

    {$lang->cmd_queue_process_count_help}

    +
    +
    + +
    + +
    +
    + +
    +
    + diff --git a/modules/admin/tpl/config_security.html b/modules/admin/tpl/config_security.html index ae99efdb1..34dd49b69 100644 --- a/modules/admin/tpl/config_security.html +++ b/modules/admin/tpl/config_security.html @@ -2,7 +2,7 @@ -
    +

    {$XE_VALIDATOR_MESSAGE}

    @@ -11,21 +11,24 @@
    - -
    - + +
    + +

    {$lang->about_mediafilter_whitelist}

    - -
    - + +
    + +

    {$lang->about_mediafilter_classes}

    - -
    - + +
    + +

    {$lang->about_robot_user_agents}

    @@ -43,12 +46,12 @@
    - +
    - - + {$lang->unit_day}   +
    -

    {$lang->about_use_session_keys}

    +

    {$lang->about_autologin_lifetime}

    @@ -87,6 +90,45 @@

    {$lang->about_use_nofollow}

    +
    + +
    + + +
    +

    {$lang->about_use_httponly}

    +
    +
    +
    + +
    + + + + +
    +

    {$lang->about_use_samesite}

    +
    +
    +
    + +
    + + + +
    +

    {$lang->about_x_frame_options}

    +
    +
    +
    + +
    + + +
    +

    {$lang->about_x_content_type_options}

    +
    +
    diff --git a/modules/admin/tpl/config_seo.html b/modules/admin/tpl/config_seo.html index d0004c903..8267e800f 100644 --- a/modules/admin/tpl/config_seo.html +++ b/modules/admin/tpl/config_seo.html @@ -2,7 +2,7 @@ -
    +

    {$XE_VALIDATOR_MESSAGE}

    @@ -13,35 +13,35 @@
    - +

    {$lang->about_seo_main_title}

    - +

    {$lang->about_seo_subpage_title}

    - +

    {$lang->about_seo_document_title}

    - +

    {$lang->about_site_meta_keywords}

    - +

    {$lang->about_site_meta_description}

    @@ -52,6 +52,13 @@
    +
    + +
    + + +
    +
    @@ -73,6 +80,13 @@
    +
    + +
    + + +
    +
    diff --git a/modules/admin/tpl/config_sitelock.html b/modules/admin/tpl/config_sitelock.html index d829401bb..c759cf316 100644 --- a/modules/admin/tpl/config_sitelock.html +++ b/modules/admin/tpl/config_sitelock.html @@ -2,7 +2,7 @@ -
    +

    {$XE_VALIDATOR_MESSAGE}

    @@ -34,8 +34,8 @@
    -
    - +
    + {$lang->sitelock_message_help}
    diff --git a/modules/admin/tpl/css/admin.bootstrap.css b/modules/admin/tpl/css/admin.bootstrap.css index c0c29500e..bf4fc74a8 100644 --- a/modules/admin/tpl/css/admin.bootstrap.css +++ b/modules/admin/tpl/css/admin.bootstrap.css @@ -23,7 +23,7 @@ .x section{display:block} .x audio, .x canvas, -.x video{display:inline-block;*display:inline;*zoom:1} +.x video{display:inline-block;} .x audio:not([controls]){display:none} .x a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px} .x a:hover, @@ -41,12 +41,12 @@ .x textarea{margin:0;font-size:100%;vertical-align:middle} .x button, .x input{*overflow:visible} -.x button::-moz-focus-inner, +.x button::-moz-focus-inner, .x input::-moz-focus-inner{padding:0;border:0} .x button, .x input[type="button"], .x input[type="reset"], -.x input[type="submit"]{cursor:pointer;-webkit-appearance:button} +.x input[type="submit"]{cursor:pointer;appearance:button} .x label, .x select, .x button, @@ -55,9 +55,9 @@ .x input[type="submit"], .x input[type="radio"], .x input[type="checkbox"]{cursor:pointer} -.x input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield} -.x input[type="search"]::-webkit-search-decoration, -.x input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none} +.x input[type="search"]{box-sizing:content-box;appearance:textfield} +.x input[type="search"]::-webkit-search-decoration, +.x input[type="search"]::-webkit-search-cancel-button{appearance:none} .x textarea{overflow:auto;vertical-align:top} @media print{ .x *{color:#000 !important;text-shadow:none !important;background:transparent !important;box-shadow:none !important} @@ -81,18 +81,20 @@ .x h2, .x h3{page-break-after:avoid} } -.x .x_clearfix{*zoom:1} .x .x_clearfix:before, .x .x_clearfix:after{display:table;line-height:0;content:""} .x .x_clearfix:after{clear:both} .x .x_hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0} -.x .x_input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box} +.x .x_input-block-level{display:block;width:100%;min-height:30px;box-sizing:border-box} .x a{color:#1a87ff;text-decoration:none} .x a:hover{color:#005580;text-decoration:underline} +.x a [class^="x_icon-"], .x a [class*=" x_icon-"] { + position: relative; top: 2px; +} .x .x_img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px} -.x .x_img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.1)} +.x .x_img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);box-shadow:0 1px 3px rgba(0, 0, 0, 0.1)} .x .x_img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px} -.x .x_row{margin-left:-20px;*zoom:1} +.x .x_row{margin-left:-20px;} .x .x_row:before, .x .x_row:after{display:table;line-height:0;content:""} .x .x_row:after{clear:both} @@ -125,11 +127,11 @@ .x .x_offset3{margin-left:260px} .x .x_offset2{margin-left:180px} .x .x_offset1{margin-left:100px} -.x .x_row-fluid{width:100%;*zoom:1} +.x .x_row-fluid{width:100%;} .x .x_row-fluid:before, .x .x_row-fluid:after{display:table;line-height:0;content:""} .x .x_row-fluid:after{clear:both} -.x .x_row-fluid [class*="x_span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box} +.x .x_row-fluid [class*="x_span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;box-sizing:border-box} .x .x_row-fluid [class*="x_span"]:first-child{margin-left:0} .x .x_row-fluid .x_controls-row [class*="x_span"]+[class*="x_span"]{margin-left:2.127659574468085%} .x .x_row-fluid .x_span12{width:100%;*width:99.94680851063829%} @@ -172,11 +174,11 @@ .x .x_row-fluid [class*="x_span"].x_hide{display:none} .x [class*="x_span"].x_pull-right, .x .x_row-fluid [class*="x_span"].x_pull-right{float:right} -.x .x_container{margin-right:auto;margin-left:auto;*zoom:1} +.x .x_container{margin-right:auto;margin-left:auto;} .x .x_container:before, .x .x_container:after{display:table;line-height:0;content:""} .x .x_container:after{clear:both} -.x .x_container-fluid{padding-right:20px;padding-left:20px;*zoom:1} +.x .x_container-fluid{padding-right:20px;padding-left:20px;} .x .x_container-fluid:before, .x .x_container-fluid:after{display:table;line-height:0;content:""} .x .x_container-fluid:after{clear:both} @@ -240,7 +242,7 @@ .x dd{line-height:20px} .x dt{font-weight:bold} .x dd{margin-left:10px} -.x .x_dl-horizontal{*zoom:1} +.x .x_dl-horizontal{} .x .x_dl-horizontal:before, .x .x_dl-horizontal:after{display:table;line-height:0;content:""} .x .x_dl-horizontal:after{clear:both} @@ -305,7 +307,10 @@ .x input, .x textarea, .x .x_uneditable-input{width:206px} -.x textarea{height:auto} +.x input.x_full-width, +.x textarea.x_full-width, +.x .x_uneditable-input.x_full-width{width:calc(100% - 14px);resize:vertical} +.x textarea{height:auto;min-height:80px;} .x textarea, .x input[type="text"], .x input[type="password"], @@ -321,7 +326,7 @@ .x input[type="search"], .x input[type="tel"], .x input[type="color"], -.x .x_uneditable-input{background-color:#ffffff;border:1px solid #cccccc;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-webkit-transition:border linear 0.2s, box-shadow linear 0.2s;-moz-transition:border linear 0.2s, box-shadow linear 0.2s;-o-transition:border linear 0.2s, box-shadow linear 0.2s;transition:border linear 0.2s, box-shadow linear 0.2s} +.x .x_uneditable-input{background-color:#ffffff;border:1px solid #cccccc;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);transition:border linear 0.2s, box-shadow linear 0.2s} .x textarea:focus, .x input[type="text"]:focus, .x input[type="password"]:focus, @@ -337,7 +342,7 @@ .x input[type="search"]:focus, .x input[type="tel"]:focus, .x input[type="color"]:focus, -.x .x_uneditable-input:focus{border-color:rgba(82, 168, 236, 0.8);outline:0;outline:thin dotted \9; /* IE6-9 */ -webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6)} +.x .x_uneditable-input:focus{border-color:rgba(82, 168, 236, 0.8);outline:0;outline:thin dotted \9; box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6)} .x input[type="radio"], .x input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal} .x input[type="file"], @@ -357,15 +362,16 @@ .x input[type="radio"]:focus, .x input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px} .x .x_uneditable-input, -.x .x_uneditable-textarea{color:#999999;cursor:not-allowed;background-color:#fcfcfc;border-color:#cccccc;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025)} +.x .x_uneditable-textarea{color:#999999;cursor:not-allowed;background-color:#fcfcfc;border-color:#cccccc;box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025)} .x .x_uneditable-input{overflow:hidden;white-space:nowrap} .x .x_uneditable-textarea{width:auto;height:auto} -.x input:-moz-placeholder, +.x input:-moz-placeholder, .x textarea:-moz-placeholder{color:#999999} -.x input:-ms-input-placeholder, +.x input:-ms-input-placeholder, .x textarea:-ms-input-placeholder{color:#999999} -.x input::-webkit-input-placeholder, +.x input::-webkit-input-placeholder, .x textarea::-webkit-input-placeholder{color:#999999} +.x textarea.x_code-font, .x textarea.x_code-font + textarea.lang_code { font-family: Consolas, SF Mono, monospace !important; } .x .x_radio, .x .x_checkbox{min-height:20px;padding-left:20px} .x .x_radio input[type="radio"], @@ -440,7 +446,7 @@ .x input.x_span1, .x textarea.x_span1, .x .x_uneditable-input.x_span1{width:46px} -.x .x_controls-row{*zoom:1} +.x .x_controls-row{} .x .x_controls-row:before, .x .x_controls-row:after{display:table;line-height:0;content:""} .x .x_controls-row:after{clear:both} @@ -468,10 +474,10 @@ .x .x_control-group.x_warning textarea{color:#c09853} .x .x_control-group.x_warning input, .x .x_control-group.x_warning select, -.x .x_control-group.x_warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075)} +.x .x_control-group.x_warning textarea{border-color:#c09853;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075)} .x .x_control-group.x_warning input:focus, .x .x_control-group.x_warning select:focus, -.x .x_control-group.x_warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e} +.x .x_control-group.x_warning textarea:focus{border-color:#a47e3c;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e} .x .x_control-group.x_warning .x_input-prepend .x_add-on, .x .x_control-group.x_warning .x_input-append .x_add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853} .x .x_control-group.x_error .x_control-label, @@ -484,10 +490,10 @@ .x .x_control-group.x_error textarea{color:#b94a48} .x .x_control-group.x_error input, .x .x_control-group.x_error select, -.x .x_control-group.x_error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075)} +.x .x_control-group.x_error textarea{border-color:#b94a48;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075)} .x .x_control-group.x_error input:focus, .x .x_control-group.x_error select:focus, -.x .x_control-group.x_error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392} +.x .x_control-group.x_error textarea:focus{border-color:#953b39;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392} .x .x_control-group.x_error .x_input-prepend .x_add-on, .x .x_control-group.x_error .x_input-append .x_add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48} .x .x_control-group.x_success .x_control-label, @@ -500,10 +506,10 @@ .x .x_control-group.x_success textarea{color:#468847} .x .x_control-group.x_success input, .x .x_control-group.x_success select, -.x .x_control-group.x_success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075)} +.x .x_control-group.x_success textarea{border-color:#468847;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075)} .x .x_control-group.x_success input:focus, .x .x_control-group.x_success select:focus, -.x .x_control-group.x_success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b} +.x .x_control-group.x_success textarea:focus{border-color:#356635;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b} .x .x_control-group.x_success .x_input-prepend .x_add-on, .x .x_control-group.x_success .x_input-append .x_add-on{color:#468847;background-color:#dff0d8;border-color:#468847} .x .x_control-group.x_info .x_control-label, @@ -516,26 +522,26 @@ .x .x_control-group.x_info textarea{color:#3a87ad} .x .x_control-group.x_info input, .x .x_control-group.x_info select, -.x .x_control-group.x_info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075)} +.x .x_control-group.x_info textarea{border-color:#3a87ad;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075)} .x .x_control-group.x_info input:focus, .x .x_control-group.x_info select:focus, -.x .x_control-group.x_info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3} +.x .x_control-group.x_info textarea:focus{border-color:#2d6987;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3} .x .x_control-group.x_info .x_input-prepend .x_add-on, .x .x_control-group.x_info .x_input-append .x_add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad} -.x input:focus:invalid, -.x textarea:focus:invalid, +.x input:focus:invalid, +.x textarea:focus:invalid, .x select:focus:invalid{color:#b94a48;border-color:#ff534a} -.x input:focus:invalid:focus, -.x textarea:focus:invalid:focus, -.x select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7} -.x .x_form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1} +.x input:focus:invalid:focus, +.x textarea:focus:invalid:focus, +.x select:focus:invalid:focus{border-color:#e9322d;box-shadow:0 0 6px #f8b9b7} +.x .x_form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;} .x .x_form-actions:before, .x .x_form-actions:after{display:table;line-height:0;content:""} .x .x_form-actions:after{clear:both} .x .x_help-block, .x .x_help-inline{color:#595959} .x .x_help-block{display:block;margin-bottom:10px} -.x .x_help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1} +.x .x_help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;} .x .x_input-append, .x .x_input-prepend{margin-bottom:5px;font-size:0;white-space:nowrap} .x .x_input-append input, @@ -623,7 +629,7 @@ .x .x_form-horizontal .x_input-prepend, .x .x_form-search .x_input-append, .x .x_form-inline .x_input-append, -.x .x_form-horizontal .x_input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1} +.x .x_form-horizontal .x_input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;} .x .x_form-search .x_hide, .x .x_form-inline .x_hide, .x .x_form-horizontal .x_hide{display:none} @@ -645,7 +651,7 @@ .x .x_form-inline .x_checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0} .x .x_control-group{margin-bottom:10px} .x legend+.x_control-group{margin-top:20px;-webkit-margin-top-collapse:separate} -.x .x_form-horizontal .x_control-group{margin-bottom:20px;*zoom:1} +.x .x_form-horizontal .x_control-group{margin-bottom:20px;} .x .x_form-horizontal .x_control-group:before, .x .x_form-horizontal .x_control-group:after{display:table;line-height:0;content:""} .x .x_form-horizontal .x_control-group:after{clear:both} @@ -664,7 +670,7 @@ .x .x_table{width:100%;margin-bottom:20px} .x .x_table th, .x .x_table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #dddddd} -.x .x_table th{font-weight:bold} +.x .x_table th{font-weight:bold;word-break:keep-all} .x .x_table thead th{vertical-align:bottom} .x .x_table caption+thead tr:first-child th, .x .x_table caption+thead tr:first-child td, @@ -750,16 +756,16 @@ .x .x_table-hover tbody tr.x_info:hover td{background-color:#c4e3f3} @font-face { font-family: 'xeicon'; - src:url('../../../../../common/css/xeicon/fonts/xeicon.eot?v=1.0.4'); - src:url('../../../../../common/css/xeicon/fonts/xeicon.eot?#iefix&v=1.0.4') format('embedded-opentype'), - url('../../../../../common/css/xeicon/fonts/xeicon.woff2?v=1.0.4') format('woff2'), - url('../../../../../common/css/xeicon/fonts/xeicon.woff?v=1.0.4') format('woff'), - url('../../../../../common/css/xeicon/fonts/xeicon.ttf?v=1.0.4') format('truetype'), - url('../../../../../common/css/xeicon/fonts/xeicon.svg?v=1.0.4#xeicon') format('svg'); + src:url('../../../../common/css/xeicon/fonts/xeicon.eot?v=1.0.4'); + src:url('../../../../common/css/xeicon/fonts/xeicon.eot?#iefix&v=1.0.4') format('embedded-opentype'), + url('../../../../common/css/xeicon/fonts/xeicon.woff2?v=1.0.4') format('woff2'), + url('../../../../common/css/xeicon/fonts/xeicon.woff?v=1.0.4') format('woff'), + url('../../../../common/css/xeicon/fonts/xeicon.ttf?v=1.0.4') format('truetype'), + url('../../../../common/css/xeicon/fonts/xeicon.svg?v=1.0.4#xeicon') format('svg'); font-weight: normal; font-style: normal; } -.x [class^="x_icon-"], +.x [class^="x_icon-"], .x [class*=" x_icon-"]{ display:inline-block; width:14px; @@ -779,18 +785,18 @@ color: #000; } /* White icons with optional class, or on hover/active states of certain elements */ -#gnb.gnb.open .x_icon-white, -.x .x_nav-pills>.x_active>a>[class^="x_icon-"], -.x .x_nav-pills>.x_active>a>[class*=" x_icon-"], -.x .x_nav-list>.x_active>a>[class^="x_icon-"], -.x .x_nav-list>.x_active>a>[class*=" x_icon-"], -.x .x_navbar-inverse .x_nav>.x_active>a>[class^="x_icon-"], -.x .x_navbar-inverse .x_nav>.x_active>a>[class*=" x_icon-"], -.x .x_dropdown-menu>li>a:hover>[class^="x_icon-"], -.x .x_dropdown-menu>li>a:hover>[class*=" x_icon-"], -.x .x_dropdown-menu>.x_active>a>[class^="x_icon-"], -.x .x_dropdown-menu>.x_active>a>[class*=" x_icon-"], -.x .x_dropdown-submenu:hover>a>[class^="x_icon-"], +#gnb.gnb.open .x_icon-white, +.x .x_nav-pills>.x_active>a>[class^="x_icon-"], +.x .x_nav-pills>.x_active>a>[class*=" x_icon-"], +.x .x_nav-list>.x_active>a>[class^="x_icon-"], +.x .x_nav-list>.x_active>a>[class*=" x_icon-"], +.x .x_navbar-inverse .x_nav>.x_active>a>[class^="x_icon-"], +.x .x_navbar-inverse .x_nav>.x_active>a>[class*=" x_icon-"], +.x .x_dropdown-menu>li>a:hover>[class^="x_icon-"], +.x .x_dropdown-menu>li>a:hover>[class*=" x_icon-"], +.x .x_dropdown-menu>.x_active>a>[class^="x_icon-"], +.x .x_dropdown-menu>.x_active>a>[class*=" x_icon-"], +.x .x_dropdown-submenu:hover>a>[class^="x_icon-"], .x .x_dropdown-submenu:hover>a>[class*=" x_icon-"]{color: #fff;} .x .x_icon-glass::before{content: "\e872";} .x .x_icon-music::before{content: "\e744";} @@ -945,7 +951,7 @@ .x .x_open .x_dropdown-toggle{outline:0} .x .x_caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000000;border-right:4px solid transparent;border-left:4px solid transparent;content:""} .x .x_dropdown .x_caret{margin-top:8px;margin-left:2px} -.x .x_dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#ffffff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box} +.x .x_dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#ffffff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box} .x .x_dropdown-menu.x_pull-right{right:0;left:auto} .x .x_dropdown-menu .x_divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff} .x .x_dropdown-menu li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333333;white-space:nowrap} @@ -974,18 +980,18 @@ .x .x_dropdown-submenu.x_pull-left>.x_dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px} .x .x_dropdown .x_dropdown-menu .x_nav-header{padding-right:20px;padding-left:20px} .x .x_typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px} -.x .x_well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05)} +.x .x_well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05)} .x .x_well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15)} .x .x_well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px} .x .x_well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px} -.x .x_fade{opacity:0;-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear} +.x .x_fade{opacity:0;transition:opacity 0.15s linear} .x .x_fade.x_in{opacity:1} -.x .x_collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height 0.35s ease;-moz-transition:height 0.35s ease;-o-transition:height 0.35s ease;transition:height 0.35s ease} +.x .x_collapse{position:relative;height:0;overflow:hidden;transition:height 0.35s ease} .x .x_collapse.x_in{height:auto} .x .x_close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000000;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20)} .x .x_close:hover{color:#000000;text-decoration:none;cursor:pointer;opacity:0.4;filter:alpha(opacity=40)} -.x button.x_close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none} -.x .x_btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;color:#333333;text-align:center;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-moz-linear-gradient(top, #ffffff, #e6e6e6);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(top, #ffffff, #e6e6e6);background-image:-o-linear-gradient(top, #ffffff, #e6e6e6);background-image:linear-gradient(to bottom, #ffffff, #e6e6e6);background-repeat:repeat-x;border:1px solid #bbbbbb;*border:0;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);border-bottom-color:#a2a2a2;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05)} +.x button.x_close{padding:0;cursor:pointer;background:transparent;border:0;appearance:none} +.x .x_btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;color:#333333;text-align:center;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-moz-linear-gradient(top, #ffffff, #e6e6e6);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(top, #ffffff, #e6e6e6);background-image:-o-linear-gradient(top, #ffffff, #e6e6e6);background-image:linear-gradient(to bottom, #ffffff, #e6e6e6);background-repeat:repeat-x;border:1px solid #bbbbbb;*border:0;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);border-bottom-color:#a2a2a2;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05)} .x .x_btn:hover, .x .x_btn:active, .x .x_btn.x_active, @@ -994,12 +1000,12 @@ .x .x_btn:active, .x .x_btn.x_active{background-color:#cccccc \9} .x .x_btn:first-child{*margin-left:0} -.x .x_btn:hover{color:#333333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position 0.1s linear;-moz-transition:background-position 0.1s linear;-o-transition:background-position 0.1s linear;transition:background-position 0.1s linear} +.x .x_btn:hover{color:#333333;text-decoration:none;background-position:0 -15px;transition:background-position 0.1s linear} .x .x_btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px} .x .x_btn.x_active, -.x .x_btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05)} +.x .x_btn:active{background-image:none;outline:0;box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05)} .x .x_btn.x_disabled, -.x .x_btn[disabled]{cursor:default;background-image:none;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none} +.x .x_btn[disabled]{cursor:default;background-image:none;opacity:0.65;filter:alpha(opacity=65);box-shadow:none} .x .x_btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px} .x .x_btn-large [class^="x_icon-"], .x .x_btn-large [class*=" x_icon-"]{margin-top:4px} @@ -1009,7 +1015,7 @@ .x .x_btn-mini [class^="x_icon-"], .x .x_btn-mini [class*=" x_icon-"]{margin-top:-1px} .x .x_btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px} -.x .x_btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box} +.x .x_btn-block{display:block;width:100%;padding-right:0;padding-left:0;box-sizing:border-box} .x .x_btn-block+.x_btn-block{margin-top:5px} .x input[type="submit"].x_btn-block, .x input[type="reset"].x_btn-block, @@ -1071,7 +1077,7 @@ .x .x_btn-inverse.x_active{background-color:#080808 \9} .x button.x_btn, .x input[type="submit"].x_btn{*padding-top:3px;*padding-bottom:3px} -.x button.x_btn::-moz-focus-inner, +.x button.x_btn::-moz-focus-inner, .x input[type="submit"].x_btn::-moz-focus-inner{padding:0;border:0} .x button.x_btn.x_btn-large, .x input[type="submit"].x_btn.x_btn-large{*padding-top:7px;*padding-bottom:7px} @@ -1081,11 +1087,11 @@ .x input[type="submit"].x_btn.x_btn-mini{*padding-top:1px;*padding-bottom:1px} .x .x_btn-link, .x .x_btn-link:active, -.x .x_btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none} +.x .x_btn-link[disabled]{background-color:transparent;background-image:none;box-shadow:none} .x .x_btn-link{color:#1a87ff;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0} .x .x_btn-link:hover{color:#005580;text-decoration:underline;background-color:transparent} .x .x_btn-link[disabled]:hover{color:#333333;text-decoration:none} -.x .x_btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1} +.x .x_btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;} .x .x_btn-group:first-child{*margin-left:0} .x .x_btn-group+.x_btn-group{margin-left:5px} .x .x_btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0} @@ -1112,11 +1118,11 @@ .x .x_btn-group>.x_btn.x_active{z-index:2} .x .x_btn-group .x_dropdown-toggle:active, .x .x_btn-group.x_open .x_dropdown-toggle{outline:0} -.x .x_btn-group>.x_btn+.x_dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05)} +.x .x_btn-group>.x_btn+.x_dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;box-shadow:inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05)} .x .x_btn-group>.x_btn-mini+.x_dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px} .x .x_btn-group>.x_btn-small+.x_dropdown-toggle{*padding-top:5px;*padding-bottom:4px} .x .x_btn-group>.x_btn-large+.x_dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px} -.x .x_btn-group.x_open .x_dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05)} +.x .x_btn-group.x_open .x_dropdown-toggle{background-image:none;box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05)} .x .x_btn-group.x_open .x_btn.x_dropdown-toggle{background-color:#e6e6e6} .x .x_btn-group.x_open .x_btn-primary.x_dropdown-toggle{background-color:#007aff} .x .x_btn-group.x_open .x_btn-warning.x_dropdown-toggle{background-color:#ff9500} @@ -1136,7 +1142,7 @@ .x .x_btn-info .x_caret, .x .x_btn-success .x_caret, .x .x_btn-inverse .x_caret{border-top-color:#ffffff;border-bottom-color:#ffffff} -.x .x_btn-group-vertical{display:inline-block;*display:inline; /* IE7 inline-block hack */ *zoom:1} +.x .x_btn-group-vertical{display:inline-block;*display:inline; /* IE7 inline-block hack */ } .x .x_btn-group-vertical>.x_btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0} .x .x_btn-group-vertical>.x_btn+.x_btn{margin-top:-1px;margin-left:0} .x .x_btn-group-vertical>.x_btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0} @@ -1177,7 +1183,7 @@ .x .x_nav-list [class*=" x_icon-"]{margin-right:2px} .x .x_nav-list .x_divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff} .x .x_nav-tabs, -.x .x_nav-pills{*zoom:1} +.x .x_nav-pills{} .x .x_nav-tabs:before, .x .x_nav-pills:before, .x .x_nav-tabs:after, @@ -1222,7 +1228,7 @@ .x .x_nav li.x_dropdown.x_open.x_active .x_caret, .x .x_nav li.x_dropdown.x_open a:hover .x_caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:1;filter:alpha(opacity=100)} .x .x_tabs-stacked .x_open>a:hover{border-color:#999999} -.x .x_tabbable{*zoom:1} +.x .x_tabbable{} .x .x_tabbable:before, .x .x_tabbable:after{display:table;line-height:0;content:""} .x .x_tabbable:after{clear:both} @@ -1257,7 +1263,7 @@ .x .x_nav>.x_disabled>a{color:#999999} .x .x_nav>.x_disabled>a:hover{text-decoration:none;cursor:default;background-color:transparent} .x .x_navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible} -.x .x_navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top, #ffffff, #f2f2f2);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2));background-image:-webkit-linear-gradient(top, #ffffff, #f2f2f2);background-image:-o-linear-gradient(top, #ffffff, #f2f2f2);background-image:linear-gradient(to bottom, #ffffff, #f2f2f2);background-repeat:repeat-x;border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f2f2f2', GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);-moz-box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);box-shadow:0 1px 4px rgba(0, 0, 0, 0.065)} +.x .x_navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top, #ffffff, #f2f2f2);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2));background-image:-webkit-linear-gradient(top, #ffffff, #f2f2f2);background-image:-o-linear-gradient(top, #ffffff, #f2f2f2);background-image:linear-gradient(to bottom, #ffffff, #f2f2f2);background-repeat:repeat-x;border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;box-shadow:0 1px 4px rgba(0, 0, 0, 0.065)} .x .x_navbar-inner:before, .x .x_navbar-inner:after{display:table;line-height:0;content:""} .x .x_navbar-inner:after{clear:both} @@ -1274,7 +1280,7 @@ .x .x_navbar .x_btn-group .x_btn, .x .x_navbar .x_input-prepend .x_btn, .x .x_navbar .x_input-append .x_btn{margin-top:0} -.x .x_navbar-form{margin-bottom:0;*zoom:1} +.x .x_navbar-form{margin-bottom:0;} .x .x_navbar-form:before, .x .x_navbar-form:after{display:table;line-height:0;content:""} .x .x_navbar-form:after{clear:both} @@ -1308,9 +1314,9 @@ .x .x_navbar-fixed-bottom .x_container{width:940px} .x .x_navbar-fixed-top{top:0} .x .x_navbar-fixed-top .x_navbar-inner, -.x .x_navbar-static-top .x_navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 10px rgba(0, 0, 0, 0.1);box-shadow:0 1px 10px rgba(0, 0, 0, 0.1)} +.x .x_navbar-static-top .x_navbar-inner{box-shadow:0 1px 10px rgba(0, 0, 0, 0.1)} .x .x_navbar-fixed-bottom{bottom:0} -.x .x_navbar-fixed-bottom .x_navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0, 0, 0, 0.1);-moz-box-shadow:0 -1px 10px rgba(0, 0, 0, 0.1);box-shadow:0 -1px 10px rgba(0, 0, 0, 0.1)} +.x .x_navbar-fixed-bottom .x_navbar-inner{box-shadow:0 -1px 10px rgba(0, 0, 0, 0.1)} .x .x_navbar .x_nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0} .x .x_navbar .x_nav.x_pull-right{float:right;margin-right:0} .x .x_navbar .x_nav>li{float:left} @@ -1320,8 +1326,8 @@ .x .x_navbar .x_nav>li>a:hover{color:#333333;text-decoration:none;background-color:transparent} .x .x_navbar .x_nav>.x_active>a, .x .x_navbar .x_nav>.x_active>a:hover, -.x .x_navbar .x_nav>.x_active>a:focus{color:#555555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);-moz-box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125)} -.x .x_navbar .x_btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#ededed;*background-color:#e5e5e5;background-image:-moz-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5));background-image:-webkit-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:-o-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:linear-gradient(to bottom, #f2f2f2, #e5e5e5);background-repeat:repeat-x;border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f2f2f2', endColorstr='#e5e5e5', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075)} +.x .x_navbar .x_nav>.x_active>a:focus{color:#555555;text-decoration:none;background-color:#e5e5e5;box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125)} +.x .x_navbar .x_btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#ededed;*background-color:#e5e5e5;background-image:-moz-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5));background-image:-webkit-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:-o-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:linear-gradient(to bottom, #f2f2f2, #e5e5e5);background-repeat:repeat-x;border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f2f2f2', endColorstr='#e5e5e5', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075)} .x .x_navbar .x_btn-navbar:hover, .x .x_navbar .x_btn-navbar:active, .x .x_navbar .x_btn-navbar.x_active, @@ -1329,7 +1335,7 @@ .x .x_navbar .x_btn-navbar[disabled]{color:#ffffff;background-color:#e5e5e5;*background-color:#d9d9d9} .x .x_navbar .x_btn-navbar:active, .x .x_navbar .x_btn-navbar.x_active{background-color:#cccccc \9} -.x .x_navbar .x_btn-navbar .x_icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);-moz-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);box-shadow:0 1px 0 rgba(0, 0, 0, 0.25)} +.x .x_navbar .x_btn-navbar .x_icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;box-shadow:0 1px 0 rgba(0, 0, 0, 0.25)} .x .x_btn-navbar .x_icon-bar+.x_icon-bar{margin-top:3px} .x .x_navbar .x_nav>li>.x_dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0, 0, 0, 0.2);content:''} .x .x_navbar .x_nav>li>.x_dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #ffffff;border-left:6px solid transparent;content:''} @@ -1374,12 +1380,12 @@ .x .x_navbar-inverse .x_nav li.x_dropdown.x_open>.x_dropdown-toggle .x_caret, .x .x_navbar-inverse .x_nav li.x_dropdown.x_active>.x_dropdown-toggle .x_caret, .x .x_navbar-inverse .x_nav li.x_dropdown.x_open.x_active>.x_dropdown-toggle .x_caret{border-top-color:#ffffff;border-bottom-color:#ffffff} -.x .x_navbar-inverse .x_navbar-search .x_search-query{color:#ffffff;background-color:#515151;border-color:#111111;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none} +.x .x_navbar-inverse .x_navbar-search .x_search-query{color:#ffffff;background-color:#515151;border-color:#111111;box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);transition:none} .x .x_navbar-inverse .x_navbar-search .x_search-query:-moz-placeholder{color:#cccccc} .x .x_navbar-inverse .x_navbar-search .x_search-query:-ms-input-placeholder{color:#cccccc} .x .x_navbar-inverse .x_navbar-search .x_search-query::-webkit-input-placeholder{color:#cccccc} .x .x_navbar-inverse .x_navbar-search .x_search-query:focus, -.x .x_navbar-inverse .x_navbar-search .x_search-query.x_focused{padding:5px 15px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15)} +.x .x_navbar-inverse .x_navbar-search .x_search-query.x_focused{padding:5px 15px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border:0;outline:0;box-shadow:0 0 3px rgba(0, 0, 0, 0.15)} .x .x_navbar-inverse .x_btn-navbar{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e0e0e;*background-color:#040404;background-image:-moz-linear-gradient(top, #151515, #040404);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#151515), to(#040404));background-image:-webkit-linear-gradient(top, #151515, #040404);background-image:-o-linear-gradient(top, #151515, #040404);background-image:linear-gradient(to bottom, #151515, #040404);background-repeat:repeat-x;border-color:#040404 #040404 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#151515', endColorstr='#040404', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)} .x .x_navbar-inverse .x_btn-navbar:hover, .x .x_navbar-inverse .x_btn-navbar:active, @@ -1388,12 +1394,12 @@ .x .x_navbar-inverse .x_btn-navbar[disabled]{color:#ffffff;background-color:#040404;*background-color:#000000} .x .x_navbar-inverse .x_btn-navbar:active, .x .x_navbar-inverse .x_btn-navbar.x_active{background-color:#000000 \9} -.x .x_breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px} -.x .x_breadcrumb>li{display:inline-block;*display:inline;text-shadow:0 1px 0 #ffffff;*zoom:1} +.x .x_breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;border-radius:4px} +.x .x_breadcrumb>li{display:inline-block;*display:inline;text-shadow:0 1px 0 #ffffff;} .x .x_breadcrumb>li>.x_divider{padding:0 5px;color:#ccc} .x .x_breadcrumb>.x_active{color:#999999} .x .x_pagination{margin:20px 0} -.x .x_pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05)} +.x .x_pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;border-radius:4px;box-shadow:0 1px 2px rgba(0, 0, 0, 0.05)} .x .x_pagination ul>li{display:inline} .x .x_pagination ul>li>a, .x .x_pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#ffffff;border:1px solid #dddddd;border-left-width:0} @@ -1429,7 +1435,7 @@ .x .x_pagination-small ul>li>span{padding:2px 10px;font-size:11.9px} .x .x_pagination-mini ul>li>a, .x .x_pagination-mini ul>li>span{padding:0 6px;font-size:10.5px} -.x .x_pager{margin:20px 0;text-align:center;list-style:none;*zoom:1} +.x .x_pager{margin:20px 0;text-align:center;list-style:none;} .x .x_pager:before, .x .x_pager:after{display:table;line-height:0;content:""} .x .x_pager:after{clear:both} @@ -1448,15 +1454,15 @@ .x_modal-backdrop.x_fade{opacity:0} .x_modal-backdrop, .x_modal-backdrop.x_fade.x_in{opacity:0.8;filter:alpha(opacity=80)} -.x_modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:none;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box} -.x_modal.x_fade{top:-25%;-webkit-transition:opacity 0.3s linear, top 0.3s ease-out;-moz-transition:opacity 0.3s linear, top 0.3s ease-out;-o-transition:opacity 0.3s linear, top 0.3s ease-out;transition:opacity 0.3s linear, top 0.3s ease-out} +.x_modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:none;box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box} +.x_modal.x_fade{top:-25%;transition:opacity 0.3s linear, top 0.3s ease-out} .x_modal.x_fade.x_in{top:10%} .x .x_modal-header{padding:9px 15px;border-bottom:1px solid #eee} .x .x_modal-header .x_close{margin-top:2px} .x .x_modal-header h3{margin:0;line-height:30px} .x .x_modal-body{position:relative;max-height:400px;padding:15px;overflow-y:auto} .x .x_modal-form{margin-bottom:0} -.x .x_modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff} +.x .x_modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;box-shadow:inset 0 1px 0 #ffffff} .x .x_modal-footer:before, .x .x_modal-footer:after{display:table;line-height:0;content:""} .x .x_modal-footer:after{clear:both} @@ -1475,7 +1481,7 @@ .x .x_tooltip.x_right .x_tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000000;border-width:5px 5px 5px 0} .x .x_tooltip.x_left .x_tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000000;border-width:5px 0 5px 5px} .x .x_tooltip.x_bottom .x_tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000000;border-width:0 5px 5px} -.x .x_popover{position:absolute;top:0;left:0;z-index:1010;display:none;width:236px;padding:1px;text-align:left;white-space:normal;background-color:#ffffff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box} +.x .x_popover{position:absolute;top:0;left:0;z-index:1010;display:none;width:236px;padding:1px;text-align:left;white-space:normal;background-color:#ffffff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box} .x .x_popover.x_top{margin-top:-10px} .x .x_popover.x_right{margin-left:10px} .x .x_popover.x_bottom{margin-top:10px} @@ -1494,18 +1500,18 @@ .x .x_popover.x_bottom .x_arrow:after{top:1px;margin-left:-10px;border-bottom-color:#ffffff;border-top-width:0} .x .x_popover.x_left .x_arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0, 0, 0, 0.25);border-right-width:0} .x .x_popover.x_left .x_arrow:after{right:1px;bottom:-10px;border-left-color:#ffffff;border-right-width:0} -.x .x_thumbnails{margin-left:-20px;list-style:none;*zoom:1} +.x .x_thumbnails{margin-left:-20px;list-style:none;} .x .x_thumbnails:before, .x .x_thumbnails:after{display:table;line-height:0;content:""} .x .x_thumbnails:after{clear:both} .x .x_row-fluid .x_thumbnails{margin-left:0} .x .x_thumbnails>li{float:left;margin-bottom:20px;margin-left:20px} -.x .x_thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);-webkit-transition:all 0.2s ease-in-out;-moz-transition:all 0.2s ease-in-out;-o-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out} -.x a.x_thumbnail:hover{border-color:#1a87ff;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25)} +.x .x_thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);transition:all 0.2s ease-in-out} +.x a.x_thumbnail:hover{border-color:#1a87ff;box-shadow:0 1px 4px rgba(0, 105, 214, 0.25)} .x .x_thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto} .x .x_thumbnail .x_caption{padding:9px;color:#555555} .x .x_media, -.x .x_media-body{overflow:hidden;*overflow:visible;zoom:1} +.x .x_media-body{overflow:hidden;*overflow:visible;} .x .x_media, .x .x_media .x_media{margin-top:15px} .x .x_media:first-child{margin-top:0} @@ -1516,8 +1522,8 @@ .x .x_media-list{margin-left:0;list-style:none} .x .x_label, .x .x_badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);white-space:nowrap;vertical-align:baseline;background-color:#999999} -.x .x_label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px} -.x .x_badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px} +.x .x_label{border-radius:3px} +.x .x_badge{padding-right:9px;padding-left:9px;border-radius:9px} .x .x_label:empty, .x .x_badge:empty{display:none} .x a.x_label:hover, @@ -1546,31 +1552,15 @@ .x .x_btn .x_badge{position:relative;top:-1px} .x .x_btn-mini .x_label, .x .x_btn-mini .x_badge{top:0} -@-webkit-keyframes progress-bar-stripes{ -from{background-position:40px 0} -to{background-position:0 0} -} -@-moz-keyframes progress-bar-stripes{ -from{background-position:40px 0} -to{background-position:0 0} -} -@-ms-keyframes progress-bar-stripes{ -from{background-position:40px 0} -to{background-position:0 0} -} -@-o-keyframes progress-bar-stripes{ -from{background-position:0 0} -to{background-position:40px 0} -} @keyframes progress-bar-stripes{ -from{background-position:40px 0} -to{background-position:0 0} + from{background-position:40px 0} + to{background-position:0 0} } -.x .x_progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));background-image:-webkit-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-o-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:linear-gradient(to bottom, #f5f5f5, #f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f5f5f5', endColorstr='#f9f9f9', GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1)} -.x .x_progress .x_bar{float:left;width:0;height:100%;font-size:12px;color:#ffffff;text-align:center;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top, #149bdf, #0480be);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));background-image:-webkit-linear-gradient(top, #149bdf, #0480be);background-image:-o-linear-gradient(top, #149bdf, #0480be);background-image:linear-gradient(to bottom, #149bdf, #0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#149bdf', endColorstr='#0480be', GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width 0.6s ease;-moz-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease} -.x .x_progress .x_bar+.x_bar{-webkit-box-shadow:inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15)} +.x .x_progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));background-image:-webkit-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-o-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:linear-gradient(to bottom, #f5f5f5, #f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f5f5f5', endColorstr='#f9f9f9', GradientType=0);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1)} +.x .x_progress .x_bar{float:left;width:0;height:100%;font-size:12px;color:#ffffff;text-align:center;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top, #149bdf, #0480be);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));background-image:-webkit-linear-gradient(top, #149bdf, #0480be);background-image:-o-linear-gradient(top, #149bdf, #0480be);background-image:linear-gradient(to bottom, #149bdf, #0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#149bdf', endColorstr='#0480be', GradientType=0);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-sizing:border-box;transition:width 0.6s ease} +.x .x_progress .x_bar+.x_bar{box-shadow:inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15)} .x .x_progress-striped .x_bar{background-color:#149bdf;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px} -.x .x_progress.x_active .x_bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite} +.x .x_progress.x_active .x_bar{animation:progress-bar-stripes 2s linear infinite} .x .x_progress-danger .x_bar, .x .x_progress .x_bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top, #ff534a, #c43c35);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ff534a), to(#c43c35));background-image:-webkit-linear-gradient(top, #ff534a, #c43c35);background-image:-o-linear-gradient(top, #ff534a, #c43c35);background-image:linear-gradient(to bottom, #ff534a, #c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff534a', endColorstr='#c43c35', GradientType=0)} .x .x_progress-danger.x_progress-striped .x_bar, @@ -1595,7 +1585,7 @@ to{background-position:0 0} .x .x_accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5} .x .x_carousel{position:relative;margin-bottom:20px;line-height:1} .x .x_carousel-inner{position:relative;width:100%;overflow:hidden} -.x .x_carousel-inner>.x_item{position:relative;display:none;-webkit-transition:0.6s ease-in-out left;-moz-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left} +.x .x_carousel-inner>.x_item{position:relative;display:none;transition:0.6s ease-in-out left} .x .x_carousel-inner>.x_item>img{display:block;line-height:1} .x .x_carousel-inner>.x_active, .x .x_carousel-inner>.x_next, @@ -1609,7 +1599,7 @@ to{background-position:0 0} .x .x_carousel-inner>.x_prev.x_right{left:0} .x .x_carousel-inner>.x_active.x_left{left:-100%} .x .x_carousel-inner>.x_active.x_right{left:100%} -.x .x_carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#ffffff;text-align:center;background:#222222;border:3px solid #ffffff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:0.5;filter:alpha(opacity=50)} +.x .x_carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#ffffff;text-align:center;background:#222222;border:3px solid #ffffff;border-radius:23px;opacity:0.5;filter:alpha(opacity=50)} .x .x_carousel-control.x_right{right:15px;left:auto} .x .x_carousel-control:hover{color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90)} .x .x_carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#333333;background:rgba(0, 0, 0, 0.75)} @@ -1617,7 +1607,7 @@ to{background-position:0 0} .x .x_carousel-caption p{line-height:20px;color:#ffffff} .x .x_carousel-caption h4{margin:0 0 5px} .x .x_carousel-caption p{margin-bottom:0} -.x .x_hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eeeeee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px} +.x .x_hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eeeeee;border-radius:6px} .x .x_hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit} .x .x_hero-unit li{line-height:30px} .x .x_pull-right{float:right} diff --git a/modules/admin/tpl/css/admin.css b/modules/admin/tpl/css/admin.css index 06689a5d1..b93ec383e 100644 --- a/modules/admin/tpl/css/admin.css +++ b/modules/admin/tpl/css/admin.css @@ -24,7 +24,7 @@ body>.x, .x button { font-size: 13px; } -.x strong, +.x strong, .x th { font-weight: 600; } @@ -55,7 +55,6 @@ body>.x, .x a[target="_blank"]:hover:after, .x a[target="_blank"]:focus:after { opacity: .7; - filter: alpha(opacity=70); } .x mark { background-color: #FF0; @@ -93,6 +92,17 @@ body>.x, color: #666; text-rendering: auto; } +.message h2, +.message h3 { + color: #346535; +} +.message.error h2, +.message.error h3 { + color: #943a38; +} +.message.core_update { + display: none; +} .x h1 { font-size: 22px; } @@ -126,7 +136,6 @@ body>.x, width: 32px; height: 32px; opacity: .5; - filter: alpha(opacity=50); } .x .x_alert { position: relative; @@ -140,13 +149,13 @@ body>.x, height: 26px; margin: 10px 0 0 0; } -.x .x_pagination ul>li>a, +.x .x_pagination ul>li>a, .x .x_pagination ul>li>span, .x .x_pagination ul>li>strong { line-height: 24px; padding: 0 8px; } -.x .x_pagination ul>.x_active>a, +.x .x_pagination ul>.x_active>a, .x .x_pagination ul>.x_active>span, .x .x_pagination ul>.x_active>strong { line-height: 26px; @@ -176,6 +185,11 @@ body>.x, height: 24px; padding: 0 6px; } +@media screen and (max-width: 800px) { + .x .x_pagination { + clear: both; + } +} .x .btn { color: #333; } @@ -207,7 +221,7 @@ body>.x, border: 0; } .x .x_btn-group { - zoom: 1; + } .x .x_btn-group.margin_after { margin-right: 10px; @@ -223,28 +237,26 @@ body>.x, .x .x_btn-group>.x_btn-inverse+.x_btn-inverse { border-left-color: #555; } -.x input[type="radio"], +.x input[type="radio"], .x input[type="checkbox"] { margin: 0; } .x input[type="file"] { height: auto; } -.x td select, -.x td textarea, +.x td select, +.x td textarea, .x td input { margin-bottom: 0; } .x [class*="x_icon-"] { opacity: .5; - filter: alpha(opacity=50); } .x [class*="x_icon-"]:hover, .x [class*="x_icon-"]:focus, .x *:hover>[class*="x_icon-"], .x *:focus>[class*="x_icon-"] { opacity: 1; - filter: alpha(opacity=100); } .x .x_nav-tabs>li>a { padding-top: 6px; @@ -282,8 +294,6 @@ body>.x, } .x .x_inline { display: inline-block; - *display: inline; - *zoom: 1; } .x .x_page-header+.x_form-horizontal { margin-top: -20px; @@ -304,7 +314,6 @@ body>.x, } .x .x_form-horizontal .x_controls { margin-left: 200px; - *margin-left: 0; } .x .x_form-horizontal .x_controls label { padding: 5px 0; @@ -320,12 +329,12 @@ body>.x, .x .x_input-append>* { vertical-align: top; } -.x .x_input-append a.x_add-on, +.x .x_input-append a.x_add-on, .x .x_input-prepend a.x_add-on { height: 16px; line-height: 16px; } -.x .x_input-append button.x_add-on, +.x .x_input-append button.x_add-on, .x .x_input-prepend button.x_add-on { height: 24px; line-height: 16px; @@ -334,6 +343,9 @@ body>.x, padding: 10px 15px; border-bottom: 1px solid #aaa; background-color: #78909C; + display: flex; + justify-content: space-between; + align-items: center; } .x .x_modal-header>h1, .x .x_modal-header>h2, @@ -343,6 +355,17 @@ body>.x, margin: 0; color: #fff; } +.x .x_modal-header>.close_window { + font-size: 24px; + line-height: 30px; + font-weight: bold; + text-decoration: none; + color: #fff; + opacity: 0.6; +} +.x .x_modal-header>.close_window:hover { + opacity: 0.8; +} .x .x_modal-body { overflow-y: visible; max-height: none; @@ -356,7 +379,6 @@ body>.x, } .x_modal-backdrop { opacity: .6; - filter: alpha(opacity=60); } .x_modal { padding: 0; @@ -407,18 +429,18 @@ body>.x, .x .x_form-horizontal .x_control-group { margin-bottom: 10px; } -.x input[type="text"], -.x input[type="password"], -.x input[type="datetime"], -.x input[type="datetime-local"], -.x input[type="date"], -.x input[type="month"], -.x input[type="time"], -.x input[type="week"], -.x input[type="number"], -.x input[type="email"], -.x input[type="url"], -.x input[type="search"], +.x input[type="text"], +.x input[type="password"], +.x input[type="datetime"], +.x input[type="datetime-local"], +.x input[type="date"], +.x input[type="month"], +.x input[type="time"], +.x input[type="week"], +.x input[type="number"], +.x input[type="email"], +.x input[type="url"], +.x input[type="search"], .x input[type="tel"] { height: 16px; line-height: 16px; @@ -430,12 +452,17 @@ margin-bottom: 10px; width: 90px; } .x select { - padding: 0; - height: 26px; + padding: 0 2px; + -moz-padding-start: 6px; + font-size: 14px; line-height: 26px; + height: 26px; } .x select[multiple] { + padding: 2px 2px; + -moz-padding-start: 2px; height: auto; + font-size: 13px; } .x textarea { vertical-align: top; @@ -473,10 +500,9 @@ margin-bottom: 10px; .x .x_help-inline { display: inline; } -.x .x_btn.x_disabled, +.x .x_btn.x_disabled, .x .x_btn[disabled] { opacity: .5; - filter: alpha(opacity=50); cursor: not-allowed; } @media all and (max-width: 980px) { @@ -507,7 +533,6 @@ margin-bottom: 10px; width: 32px; height: 32px; opacity: .5; - filter: alpha(opacity=50); } .x .section>h2 { position: relative; @@ -548,7 +573,6 @@ margin-bottom: 10px; margin: 10px 0; border-top: 1px solid #ccc; text-align: right; - zoom: 1; clear: both; } .x .btnArea:after { @@ -588,13 +612,6 @@ margin-bottom: 10px; outline: 0; cursor: pointer; opacity: 0; - filter: alpha(opacity=0); - -webkit-transform: scale(4); - -webkit-transform-origin: 100% 0; - -moz-transform: scale(4); - -moz-transform-origin: 100% 0; - -o-transform: scale(4); - -o-transform-origin: 100% 0; transform: scale(4); transform-origin: 100% 0; } @@ -621,7 +638,6 @@ margin-bottom: 10px; width: 42px; height: 16px; opacity: 0; - filter: alpha(opacity=0); position: relative; z-index: 2; } @@ -634,7 +650,6 @@ margin-bottom: 10px; bottom: auto !important; margin: 0 0 0 -42px !important; opacity: 1 !important; - filter: alpha(opacity=100) !important; padding: 0; vertical-align: middle; display: inline-block; @@ -750,7 +765,6 @@ margin-bottom: 10px; position: relative; z-index: 2; padding: 10px 15px; - zoom: 1; box-shadow: 0 2px 3px rgba(0,0,0,0.19), 0 1px 1px rgba(0,0,0,0.23); background-color: #263238; } @@ -769,8 +783,8 @@ margin-bottom: 10px; } .x>.body { position: relative; - zoom: 1; padding: 0 0 50px 200px; + background-color: #fff; z-index: 1; } .x>.body.wide { @@ -951,7 +965,6 @@ margin-bottom: 10px; .x>.footer { border-top: 1px solid #e0e0e0; padding: 10px 15px; - zoom: 1; } .x>.footer:after { content: ""; @@ -1042,14 +1055,12 @@ margin-bottom: 10px; } .x>.body>.gnb .exMenu>button>i { opacity: .5; - filter: alpha(opacity=50); vertical-align: middle; text-indent: 0; } .x>.body>.gnb .exMenu>button:hover>i, .x>.body>.gnb .exMenu>button:focus>i { opacity: 1; - filter: alpha(opacity=100); } .x>.body>.gnb .exMenu .x_icon-chevron-up, .x>.body>.gnb>.ex .exMenu .x_icon-chevron-down { @@ -1116,7 +1127,6 @@ margin-bottom: 10px; margin: -4px 6px 0 0; vertical-align: middle; opacity: .75; - filter: alpha(opacity=75); } @media all and (min-width: 800px) { .x>.body.wide>.gnb>ul>li>a>i { @@ -1290,19 +1300,21 @@ margin-bottom: 10px; /* Dashboard */ .x .dashboard { - zoom: 1; + display: flex; + align-items: flex-start; + justify-content: space-between; } -.x .dashboard:after { - content: ""; - display: block; - clear: both; +.x .dashboard > div { + width: calc(50% - 12.5px); } -.x .dashboard>div { - float: right; - width: 49%; -} -.x .dashboard>div:first-child { - float: left; +@media all and (max-width: 800px) { + .x .dashboard { + flex-direction: column; + align-items: stretch; + } + .x .dashboard > div { + width: 100%; + } } .x .dashboard>div>section { position: relative; @@ -1328,8 +1340,6 @@ margin-bottom: 10px; .x .dashboard>div>.status dl { color: #767676; display: inline-block; - *display: inline; - zoom: 1; margin: 0 8px 0 0; padding: 1px 8px 0; font: 11px/1 돋움, Dotum, Arial, Helvetica, sans-serif; @@ -1410,7 +1420,6 @@ margin-bottom: 10px; margin: 10px; padding: 0; overflow: hidden; - zoom: 1; } .x .dashboard>div>section li { position: relative; @@ -1419,7 +1428,6 @@ margin-bottom: 10px; padding: 5px 70px 5px 5px; white-space: nowrap; overflow: hidden; - zoom: 1; vertical-align: top; } .x .dashboard>div>section li.hover { @@ -1442,7 +1450,6 @@ margin-bottom: 10px; text-overflow: ellipsis; white-space: nowrap; text-align: right; - zoom: 1; } .x .dashboard>div>section li>.action { display: none; @@ -1469,12 +1476,25 @@ margin-bottom: 10px; } } + +.x .g11n.x_input-append { + display: inline-flex; + align-items: flex-start; +} +.x .g11n.x_full-width { + width: 100%; +} +.x .g11n > input.lang_code, +.x .g11n > textarea.lang_code { + flex: 1; + width: unset; + min-width: 0; +} .x .g11n>.x_add-on { font-size: 0; position: relative; cursor: pointer; text-decoration: none; - *color: #eee; } .x .g11n>.x_add-on>i { position: absolute; @@ -1483,12 +1503,12 @@ margin-bottom: 10px; margin: -7px 0 0 -7px; z-index: 1; opacity: .25; - filter: alpha(opacity=25); } .x .g11n>.x_add-on.remover { display: none; - width: 26px; - height: 26px; + height: 16px; + padding: 4px; + box-sizing: content-box; } .x .g11n.active>[disabled] { padding-left: 25px; @@ -1496,11 +1516,10 @@ margin-bottom: 10px; background-repeat: no-repeat; } .x .g11n.active>.x_add-on.remover { - display: inline-block; + display: block; } .x .g11n>.x_add-on:hover>i { opacity: 1; - filter: alpha(opacity=100); } .x .g11n>textarea { border-top-right-radius: 0; @@ -1524,7 +1543,6 @@ margin-bottom: 10px; margin: -7px 0 0 0; right: 0; opacity: .5; - filter: alpha(opacity=50); } #g11n #lang_search .item>fieldset { display: none; @@ -1603,6 +1621,11 @@ html[lang="mn"] .x .g11n.active>[disabled], #g11n .flag.mn { background-image: url(../img/flag.mn.gif); } +html[lang="id"] .x .g11n.active>[disabled], +#g11n .item .id, +#g11n .flag.id { + background-image: url(../img/flag.id.gif); +} #g11n #lang_search .cancel, #g11n #lang_search .save, #g11n #lang_search .editMode .modify, @@ -1686,14 +1709,16 @@ html[lang="mn"] .x .g11n.active>[disabled], } .x .uDrag .dragBtn { position: absolute; - width: 8px; + width: 24px; height: 100%; - padding: 0; + box-sizing: content-box; overflow: hidden; - background: url(../img/bgDragable.gif); + background: url(../img/bgDragable.png); + background-position: center top; + background-repeat: no-repeat; top: 1px; - left: 0; - text-indent: 12px; + left: -8px; + text-indent: 20px; border: 0; cursor: n-resize; white-space: nowrap; @@ -1814,21 +1839,16 @@ html[lang="mn"] .x .g11n.active>[disabled], max-width: 160px; overflow: hidden; text-overflow: ellipsis; - -webkit-transition: .2s; - -moz-transition: .2s; - -o-transition: .2s; transition: .2s; } .tree li>a>i { opacity: .5; - filter: alpha(opacity=50); } .tree a.jstree-hovered:hover{background:#616161;color:#fff;} .tree .jstree-clicked{background:#000;color:#fff} .tree .jstree-hovered>i, .tree .jstree-clicked>i { opacity: 1; - filter: alpha(opacity=100); } /* Plugin style override */ .tree a>.jstree-icon { @@ -1844,14 +1864,13 @@ html[lang="mn"] .x .g11n.active>[disabled], /* ---------- Deprecated UI: Please do not use this CSS styles below. It will be removed as soon as possible. ---------- */ /* Section & Heading */ -.x .h2, -.x .h3, +.x .h2, +.x .h3, .x .h4 { position: relative; border-style: solid; border-top: 0; border-right: 0; - zoom: 1; padding-left: 8px; } .x .h1 { @@ -1881,9 +1900,9 @@ html[lang="mn"] .x .g11n.active>[disabled], .x .h2 + ul, .x .h3 + ul, .x .h4 + ul, -.x .h1 + .table table, -.x .h2 + .table table, -.x .h3 + .table table, +.x .h1 + .table table, +.x .h2 + .table table, +.x .h3 + .table table, .x .h4 + .table table { border-top: 0 !important; margin-top: -1em !important; @@ -1998,7 +2017,6 @@ html[lang="mn"] .x .g11n.active>[disabled], list-style: none; border-top: 2px solid #ccc; border-bottom: 1px solid #ccc; - zoom: 1; } .x .form li { list-style: none; @@ -2008,7 +2026,6 @@ html[lang="mn"] .x .g11n.active>[disabled], margin: -1px 0; padding: 8px 0; vertical-align: top; - zoom: 1; } .x .form li:hover { background: #ffd; @@ -2069,7 +2086,7 @@ html[lang="mn"] .x .g11n.active>[disabled], vertical-align: top; resize: both; } -.x .form span.desc, +.x .form span.desc, .x .form em.desc { line-height: 22px; vertical-align: middle; @@ -2093,7 +2110,6 @@ html[lang="mn"] .x .g11n.active>[disabled], .x .cnb { margin: 1em 0; position: relative; - zoom: 1; } .x .cnb:after { content: ""; @@ -2237,20 +2253,16 @@ html[lang="mn"] .x .g11n.active>[disabled], top: 0; left: 0; width: 100%; - _height: 100%; min-height: 100%; z-index: 1050; } .modal .bg { position: absolute; background: #000; - _background: none; width: 100%; height: 100%; opacity: .5; z-index: 2; - filter: progid:DXImageTransform.Microsoft.Alpha(opacity=50); - zoom: 1; } .modal .fg { position: relative; @@ -2258,15 +2270,13 @@ html[lang="mn"] .x .g11n.active>[disabled], margin: 5em auto; background: #fff; padding: 0 1em; - *padding: 1em; border: 8px solid #e0e0e0; z-index: 3; - zoom: 1; box-shadow: 0 0 6px #000; } -.modal ul, -.modal ol, -.modal .lined, +.modal ul, +.modal ol, +.modal .lined, .modal .table { margin-bottom: 1em; } @@ -2278,7 +2288,6 @@ html[lang="mn"] .x .g11n.active>[disabled], height: 100%; border: 0; opacity: 0; - filter: alpha(opacity=0); z-index: 1; } .modalClose { @@ -2310,6 +2319,30 @@ html[lang="mn"] .x .g11n.active>[disabled], white-space: pre-wrap; font-family: Consolas, Courier New, monospace; } +.core_symbol { + display: inline-block; + margin: 0 2px; + width: 14px; + height: 14px; +} +.x .x_table th.domain_prefix { + text-align: right; + padding-right: 0; +} +.x .x_table td.domain_prefix { + color: #888; + text-align: right; + padding-right: 0; +} +.x .x_table td.domain_prefix span.domain { + display: inline-block; + margin-right: 2px; + color: #333; +} +.x .x_table .domain_prefix + th, +.x .x_table .domain_prefix + td { + padding-left: 2px; +} /* language specific styles */ /* English admin_en.css */ diff --git a/modules/admin/tpl/css/admin.min.css b/modules/admin/tpl/css/admin.min.css deleted file mode 100644 index 303be945e..000000000 --- a/modules/admin/tpl/css/admin.min.css +++ /dev/null @@ -1 +0,0 @@ -/* This file is not used in Rhymix. */ diff --git a/modules/admin/tpl/css/queue_config.scss b/modules/admin/tpl/css/queue_config.scss new file mode 100644 index 000000000..c11f8df01 --- /dev/null +++ b/modules/admin/tpl/css/queue_config.scss @@ -0,0 +1,27 @@ +.queue-script-setup { + .qss-content { + display: none; + &.active { + display: block; + border: 1px solid #ddd; + border-top: 0; + margin-top: -20px; + padding: 20px 12px 10px 12px; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + margin-bottom: 20px; + } + .qss-instruction { + margin-bottom: 10px; + code { + color: #333; + border: 0; + background-color: transparent; + padding: 0 1px; + } + } + .pre { + margin-bottom: 10px; + } + } +} diff --git a/modules/admin/tpl/img/bgDragable.gif b/modules/admin/tpl/img/bgDragable.gif deleted file mode 100644 index 92fcd2298..000000000 Binary files a/modules/admin/tpl/img/bgDragable.gif and /dev/null differ diff --git a/modules/admin/tpl/img/bgDragable.png b/modules/admin/tpl/img/bgDragable.png new file mode 100644 index 000000000..86bbdc708 Binary files /dev/null and b/modules/admin/tpl/img/bgDragable.png differ diff --git a/modules/admin/tpl/img/faviconSample.png b/modules/admin/tpl/img/faviconSample.png deleted file mode 100644 index 4f98daeaa..000000000 Binary files a/modules/admin/tpl/img/faviconSample.png and /dev/null differ diff --git a/modules/admin/tpl/img/flag.id.gif b/modules/admin/tpl/img/flag.id.gif new file mode 100644 index 000000000..9d616f4f9 Binary files /dev/null and b/modules/admin/tpl/img/flag.id.gif differ diff --git a/modules/admin/tpl/img/mobiconSample.png b/modules/admin/tpl/img/mobiconSample.png deleted file mode 100644 index c08a71d5a..000000000 Binary files a/modules/admin/tpl/img/mobiconSample.png and /dev/null differ diff --git a/modules/admin/tpl/index.html b/modules/admin/tpl/index.html index 75b72a484..6e9261b72 100644 --- a/modules/admin/tpl/index.html +++ b/modules/admin/tpl/index.html @@ -10,33 +10,66 @@
    + +

    {$lang->control_panel}

    - - -
    + +

    {$XE_VALIDATOR_MESSAGE}

    -
    -

    {$lang->sitelock_in_use}

    -

    {$lang->about_sitelock_in_use} {$lang->cmd_configure}

    + +
    +

    {$lang->unsupported_php_version_notice}

    +
      +
    • {$lang->unsupported_php_version_required}
    • +
    • {sprintf($lang->unsupported_php_version_current, PHP_VERSION)}
    • +
    + +
    +

    {$lang->sitelock_in_use}

    +

    {$lang->about_sitelock_in_use} {$lang->cmd_configure}

    +
    + + +
    +

    {$lang->module_exists_in_wrong_path}

    +

    {$lang->about_module_exists_in_wrong_path}

    +
      + +
    • {$value}
    • +
      +
    +
    + + +
    +

    {$lang->update_available}

    +

    {$lang->core_update_available} {$lang->core_update_link}

    +
    + + +
    +

    {$lang->msg_cleanup_notice_title}

    +

    {sprintf($lang->msg_cleanup_notice_content, number_format(count($cleanup_list)), getUrl('', 'module', 'admin', 'act', 'dispAdminCleanupList'))|noescape}

    +
    + +

    {$lang->need_complete_configuration}

    {$lang->need_complete_configuration_details}

      -
    • +
    • {$value->title} ({$value->module})  
    • -
    • +
    • {$value->title} ({$value->module})  
    • @@ -44,6 +77,7 @@
    +

    {$lang->available_new_version}

    {$lang->available_new_version_details}

    @@ -55,31 +89,34 @@
    +
    - - - - - - -
    + {@ + $dashboard_stack = new stdClass; + $dashboard_stack->left = []; + $dashboard_stack->right = []; + } -
    -

    {$lang->msg_php_warning_title}

    -

    {$lang->msg_php_warning_notice}

    -

    {$lang->msg_php_warning_now_version} : {phpversion()}

    -

    {$lang->msg_php_warning_latest_version_check}

    -
      {$lang->msg_php_warning_notice_explain}
    + + + + + + + + {@ ModuleHandler::triggerCall('admin.dashboard', 'before', $dashboard_stack)} + + +
    + {implode("\n", $dashboard_stack->left)|noescape} +
    + + +
    + {implode("\n", $dashboard_stack->right)|noescape} +
    - - diff --git a/modules/admin/tpl/js/admin.js b/modules/admin/tpl/js/admin.js index 271633d8b..4d3d52c45 100644 --- a/modules/admin/tpl/js/admin.js +++ b/modules/admin/tpl/js/admin.js @@ -1,16 +1,52 @@ /* NAVER (developers@xpressengine.com) */ + +// core update check +jQuery(function($) { + var core_update = $('.core_update'); + if (!core_update.size()) { + return; + } + var version_decode = function(str) { + var version_number = 0; + var extra_strings = ''; + var parts = str.split(/[\.-]/); + $.each(parts, function(i, part) { + if (part.match(/^[0-9]+$/)) { + version_number += parseInt(part, 10) * Math.pow(1000, 3 - i); + } else { + extra_strings = extra_strings + part; + } + }); + return String(version_number) + (extra_strings === '' ? 'z' : extra_strings); + }; + var current_version = version_decode(core_update.data('current-version')); + $.ajax({ + url: 'https://api.rhymix.org/version.json', + dataType: 'json', + cache: true, + success: function(data) { + var latest_version = version_decode(data.latest); + if (latest_version > current_version) { + var version_replace = core_update.find('p').first(); + version_replace.html(version_replace.html().replace('$VERSION', data.latest)); + core_update.show(); + } + } + }); +}); + // install module function doInstallModule(module) { var params = []; params.module_name = module; - exec_xml('install','procInstallAdminInstall',params, completeInstallModule); + exec_json('install.procInstallAdminInstall', params, completeInstallModule); } // upgrade module function doUpdateModule(module) { var params = []; params.module_name = module; - exec_xml('install','procInstallAdminUpdate',params, completeInstallModule); + exec_json('install.procInstallAdminUpdate', params, completeInstallModule); } function completeInstallModule(ret_obj) { @@ -104,9 +140,6 @@ jQuery(function($){ var $xGnb = $xBody.find('>.gnb'); var $xGnb_li = $xGnb.find('>ul>li:not(.active_clone)'); - var d365 = new Date(); - d365.setTime(d365.getTime() + 60*60*24*356); - // Add icon $xGnb_li.find('>a').prepend(''); $xGnb_li.find('>ul').prev('a').append(''); @@ -129,12 +162,12 @@ jQuery(function($){ if(!hasOpen && !hasActive && hasList){ // Down to open $parent.addClass('open').find('>ul').slideDown(100); openGNB(); - setCookie('__xe_admin_gnb_tx_' + $this.data('href'), 'open', d365); + XE.cookie.set('__xe_admin_gnb_tx_' + $this.data('href'), 'open', { expires: 365 }); return false; } else if(hasOpen && !hasActive && hasList && !hasWide){ // Up to close $parent.removeClass('open').find('>ul').slideUp(100); openGNB(); - setCookie('__xe_admin_gnb_tx_' + $this.data('href'), 'close', d365); + XE.cookie.remove('__xe_admin_gnb_tx_' + $this.data('href')); return false; } else if(hasWide && !hasList || hasActive || hasWide && hasOpen){ // Right to open openGNB(); @@ -145,9 +178,9 @@ jQuery(function($){ $("a.mobile_menu_open").click(function(){ $xGnb.toggleClass('open'); if($(this).parent('.gnb').hasClass('open')){ - setCookie('__xe_admin_gnb_status', 'open', d365); + XE.cookie.set('__xe_admin_gnb_status', 'open', { expires: 365 }); }else{ - setCookie('__xe_admin_gnb_status', 'close', d365); + XE.cookie.remove('__xe_admin_gnb_status'); } return false; }); @@ -161,9 +194,9 @@ jQuery(function($){ // remember status if($(this).parent('.gnb').hasClass('open')){ - setCookie('__xe_admin_gnb_status', 'open', d365); + XE.cookie.set('__xe_admin_gnb_status', 'open', { expires: 365 }); }else{ - setCookie('__xe_admin_gnb_status', 'close', d365); + XE.cookie.remove('__xe_admin_gnb_status'); } return false; }); @@ -175,26 +208,28 @@ jQuery(function($){ // remember status if($('#gnbNav').hasClass('ex')){ - setCookie('__xe_admin_gnb_ex_status', 'open', d365); + XE.cookie.set('__xe_admin_gnb_ex_status', 'open', { expires: 365 }); }else{ - setCookie('__xe_admin_gnb_ex_status', 'close', d365); + XE.cookie.remove('__xe_admin_gnb_ex_status'); } }); // re-create cookie var gnb_status = getCookie('__xe_admin_gnb_status'); if(gnb_status){ - setCookie('__xe_admin_gnb_status', gnb_status, d365); + XE.cookie.set('__xe_admin_gnb_status', gnb_status, { expires: 365 }); } var gnb_ex_status = getCookie('__xe_admin_gnb_ex_status'); if(gnb_ex_status){ - setCookie('__xe_admin_gnb_xe_status', gnb_ex_status, d365); + XE.cookie.set('__xe_admin_gnb_xe_status', gnb_ex_status, { expires: 365 }); } if(typeof __xe_admin_gnb_txs != 'undefined'){ for(var i in __xe_admin_gnb_txs){ var item = __xe_admin_gnb_txs[i]; var status = getCookie(item); - setCookie(item, status, d365); + if (status === 'open') { + XE.cookie.set(item, status, { expires: 365 }); + } } } }; @@ -332,7 +367,17 @@ jQuery(function($){ $.fn.tableSpan = function(){ this.each(function(){ var $this = $(this); - var thNum = $this.find('>thead>tr:eq(0)>th').length; + var thList = $this.find('>thead>tr:eq(0)>th'); + var thNum = 0; + thList.each(function(){ + var $th = $(this); + if($th.attr('colspan')){ // th의 colspan 반영 + thNum += parseInt($th.attr('colspan'), 10); + } else { + thNum++; + } + }); + var $tdTarget = $this.find('>tbody>tr:eq(0)>td:only-child'); if(thNum != $tdTarget.attr('colspan')){ $tdTarget.attr('colspan', thNum).css('text-align','center'); @@ -942,7 +987,7 @@ jQuery(function($){ tmpCount++; // add html - var $btn = $('' + xe.cmd_find + ''); + var $btn = $('' + xe.lang.cmd_find + ''); var $displayInput = $(''); $this.after($btn).after(' ').after($displayInput).hide(); $btn.xeModuleSearch(); @@ -1015,10 +1060,10 @@ jQuery(function($){ $.xeMsgBox = { htOptions : {} }; - //xe.cmd_cancel = "{$lang->cmd_cancel}"; - //xe.cmd_confirm = "{$lang->cmd_confirm}"; + //xe.lang.cmd_cancel = "{$lang->cmd_cancel}"; + //xe.lang.cmd_confirm = "{$lang->cmd_confirm}"; var $msgBox = $.xeMsgBox.$msgBox = $("
    ").addClass("x_modal _common x").hide().css('z-index', 9999); - $msgBox.html('

    '); + $msgBox.html('

    '); $("body").append($msgBox); $msgBox.find("._ok").click(function(){ $.xeMsgBox.fnOnOK(); @@ -1264,7 +1309,7 @@ jQuery(function($){ position = {x:event.pageX, y:event.pageY}; offset = getOffset($tr.get(0), ofspar); - $clone = $tr.attr('target', true).clone(true).appendTo($table); + $clone = $tr.attr('target', true).clone(true).find('input').removeAttr('id name').end().appendTo($table); // get colspan cols = ($th=$table.find('thead th')).length; @@ -1425,6 +1470,7 @@ jQuery(function($){ // Details toggle in admin table var simpleBtn = $('.x .dsTg .__simple'); var detailBtn = $('.x .dsTg .__detail'); + var tableContainer = $('.x .dsTg'); var tdTitle = $('.x .dsTg td.title'); tdTitle.each(function(){ var $t = $(this); @@ -1439,12 +1485,14 @@ jQuery(function($){ simples.show(); detailBtn.removeClass('x_active'); simpleBtn.addClass('x_active'); + tableContainer.addClass('__simpleView'); }; var detailBtnFn = function(){ details.show(); simples.hide(); detailBtn.addClass('x_active'); simpleBtn.removeClass('x_active'); + tableContainer.removeClass('__simpleView'); }; simpleBtn.click(simpleBtnFn); detailBtn.click(detailBtnFn); @@ -1475,13 +1523,13 @@ jQuery(function($){ // change text if(options.create_type != 'save_and_use'){ - $g11n_create.find('.save-useit').text(xe.cmd_save); + $g11n_create.find('.save-useit').text(xe.lang.cmd_save); } // #lang_create confirm function g11n_create_save_confirm(){ if($g11n_create.is(':visible') && is_create_changed){ - if(confirm(xe.msg_confirm_save_and_use_multilingual)){ + if(confirm(xe.lang.msg_confirm_save_and_use_multilingual)){ $g11n_create.find('.save-useit').trigger('click'); } } @@ -1493,7 +1541,7 @@ jQuery(function($){ function g11n_search_save_confirm(){ if($g11n_search.is(':visible') && $g11n_search.find('.editMode').length){ var $search_item = $g11n_search.find('.editMode'); - if(confirm(xe.msg_confirm_save_and_use_multilingual)){ + if(confirm(xe.lang.msg_confirm_save_and_use_multilingual)){ $search_item.find('.save').trigger('click'); }else{ $search_item.find('.cancel').trigger('click'); @@ -1663,7 +1711,7 @@ jQuery(function($){ if(!options.view_modify) $g11n_search.find('.modify').hide(); if(!options.view_delete) $g11n_search.find('.delete').hide(); if(options.modify_type == 'save'){ - $g11n_search.find('.save').text(xe.cmd_save); + $g11n_search.find('.save').text(xe.lang.cmd_save); } // Modify click @@ -1689,7 +1737,7 @@ jQuery(function($){ // Delete click $g11n_search.find('.delete').click(function(){ - if(!confirm(xe.confirm_delete)) return; + if(!confirm(xe.lang.confirm_delete)) return; var $this = $(this); @@ -1789,7 +1837,7 @@ jQuery(function($){ }); if(!current_lang_value){ - alert(xe.msg_empty_multilingual); + alert(xe.lang.msg_empty_multilingual); return false; } @@ -1868,6 +1916,7 @@ jQuery(function($){ // make UI var $this = $(this); + var width = $this.width(); var t = this; if($this.parent().hasClass('g11n')){ @@ -1885,34 +1934,31 @@ jQuery(function($){ function makeUI(){ var $multilingualWindow = $('#g11n'); - var width = $this.width(); var $displayInput; if(t.tagName == 'TEXTAREA' || $this.data('type') == 'textarea'){ - $displayInput = $(' +
    +
    + +
    + +
    + +
    +
    + +
    + +
    +
    +
    +
    + +
    + +
    + +
    + + diff --git a/modules/advanced_mailer/tpl/sms_log.html b/modules/advanced_mailer/tpl/sms_log.html index 667ed2441..6c8c18887 100644 --- a/modules/advanced_mailer/tpl/sms_log.html +++ b/modules/advanced_mailer/tpl/sms_log.html @@ -4,7 +4,15 @@ diff --git a/modules/advanced_mailer/tpl/test_email.html b/modules/advanced_mailer/tpl/test_email.html new file mode 100644 index 000000000..de619d750 --- /dev/null +++ b/modules/advanced_mailer/tpl/test_email.html @@ -0,0 +1,12 @@ + + + + Rhymix Email Test : {$sending_method|upper} + + +

    라이믹스에서 발송한 테스트 메일입니다.

    +

    {$sending_method|upper} 발송 방법을 사용하여 정상적으로 발송되고 있습니다.

    +

    This is a TEST EMAIL from Rhymix.

    +

    Your email seems to be working fine using {$sending_method|upper}.

    + + diff --git a/modules/autoinstall/autoinstall.admin.controller.php b/modules/autoinstall/autoinstall.admin.controller.php index f6f195f79..ac66cf9b6 100644 --- a/modules/autoinstall/autoinstall.admin.controller.php +++ b/modules/autoinstall/autoinstall.admin.controller.php @@ -1,7 +1,7 @@ */ -require_once(_XE_PATH_ . 'modules/autoinstall/autoinstall.lib.php'); +require_once(RX_BASEDIR . 'modules/autoinstall/autoinstall.lib.php'); /** * autoinstall module admin controller class @@ -16,7 +16,6 @@ class autoinstallAdminController extends autoinstall */ function init() { - } /** @@ -75,15 +74,24 @@ class autoinstallAdminController extends autoinstall 'ssl_verify_peer' => FALSE, 'ssl_verify_host' => FALSE ); - $buff = FileHandler::getRemoteResource(_XE_DOWNLOAD_SERVER_, $body, 3, "POST", "application/xml", array(), array(), array(), $request_config); - $xml = new XmlParser(); - $xmlDoc = $xml->parse($buff); - $this->updateCategory($xmlDoc); - $this->updatePackages($xmlDoc); - $this->checkInstalled(); - $oAdminController = getAdminController('admin'); - $oAdminController->cleanFavorite(); + $oAdminModel = getAdminModel('autoinstall'); + $config = $oAdminModel->getAutoInstallAdminModuleConfig(); + + $buff = FileHandler::getRemoteResource($config->download_server, $body, 3, "POST", "application/xml", array(), array(), array(), $request_config); + if ($buff) + { + $xml = new XeXmlParser(); + $xmlDoc = $xml->parse($buff); + if ($xmlDoc) + { + $this->updateCategory($xmlDoc); + $this->updatePackages($xmlDoc); + $this->checkInstalled(); + } + $oAdminController = getAdminController('admin'); + $oAdminController->cleanFavorite(); + } } /** @@ -107,7 +115,7 @@ class autoinstallAdminController extends autoinstall $type = $oModel->getTypeFromPath($package->path); if($type == "core") { - $version = __XE_VERSION__; + $version = \RX_VERSION; } else { @@ -147,7 +155,7 @@ class autoinstallAdminController extends autoinstall continue; } - $xml = new XmlParser(); + $xml = new XeXmlParser(); $xmlDoc = $xml->loadXmlFile($real_path . $config_file); if(!$xmlDoc) @@ -162,7 +170,7 @@ class autoinstallAdminController extends autoinstall $args->package_srl = $package->package_srl; $args->version = $package->version; $args->current_version = $version; - if(version_compare($args->version, $args->current_version, ">")) + if($version !== 'RX_VERSION' && version_compare($args->version, $args->current_version, ">")) { $args->need_update = "Y"; } @@ -187,39 +195,26 @@ class autoinstallAdminController extends autoinstall $oModel = getModel('autoinstall'); $oAdminModel = getAdminModel('autoinstall'); $packages = explode(',', $package_srls); - $ftp_info = Context::getFTPInfo(); - if(!$_SESSION['ftp_password']) - { - $ftp_password = Context::get('ftp_password'); - } - else - { - $ftp_password = $_SESSION['ftp_password']; - } - $isSftpSupported = function_exists(ssh2_sftp); foreach($packages as $package_srl) { $package = $oModel->getPackage($package_srl); - if($oAdminModel->checkUseDirectModuleInstall($package)->toBool()) + $package->type = $oModel->getTypeFromPath($package->path); + if ($package->type === 'core') { - $oModuleInstaller = new DirectModuleInstaller($package); + continue; } - else if($ftp_info->sftp && $ftp_info->sftp == 'Y' && $isSftpSupported) + + if(!$oAdminModel->checkUseDirectModuleInstall($package)->toBool()) { - $oModuleInstaller = new SFTPModuleInstaller($package); - } - else if(function_exists(ftp_connect)) - { - $oModuleInstaller = new PHPFTPModuleInstaller($package); - } - else - { - $oModuleInstaller = new FTPModuleInstaller($package); + return new BaseObject(-1, 'msg_no_permission_to_install'); } - $oModuleInstaller->setServerUrl(_XE_DOWNLOAD_SERVER_); - $oModuleInstaller->setPassword($ftp_password); + $config = $oAdminModel->getAutoInstallAdminModuleConfig(); + + $oModuleInstaller = new DirectModuleInstaller($package); + $oModuleInstaller->setServerUrl($config->download_server); + //$oModuleInstaller->setPassword($ftp_password); $output = $oModuleInstaller->install(); if(!$output->toBool()) { @@ -359,41 +354,17 @@ class autoinstallAdminController extends autoinstall private function _uninstallPackage($package) { - $path = $package->path; - $oAdminModel = getAdminModel('autoinstall'); - - if(!$_SESSION['ftp_password']) + if(!$oAdminModel->checkUseDirectModuleInstall($package)->toBool()) { - $ftp_password = Context::get('ftp_password'); - } - else - { - $ftp_password = $_SESSION['ftp_password']; - } - $ftp_info = Context::getFTPInfo(); - - $isSftpSupported = function_exists(ssh2_sftp); - if($oAdminModel->checkUseDirectModuleInstall($package)->toBool()) - { - $oModuleInstaller = new DirectModuleInstaller($package); - } - else if($ftp_info->sftp && $ftp_info->sftp == 'Y' && $isSftpSupported) - { - $oModuleInstaller = new SFTPModuleInstaller($package); - } - else if(function_exists('ftp_connect')) - { - $oModuleInstaller = new PHPFTPModuleInstaller($package); - } - else - { - $oModuleInstaller = new FTPModuleInstaller($package); + return new BaseObject(-1, 'msg_no_permission_to_install'); } - $oModuleInstaller->setServerUrl(_XE_DOWNLOAD_SERVER_); + $config = $oAdminModel->getAutoInstallAdminModuleConfig(); - $oModuleInstaller->setPassword($ftp_password); + $oModuleInstaller = new DirectModuleInstaller($package); + $oModuleInstaller->setServerUrl($config->download_server); + //$oModuleInstaller->setPassword($ftp_password); $output = $oModuleInstaller->uninstall(); if(!$output->toBool()) { @@ -407,6 +378,39 @@ class autoinstallAdminController extends autoinstall return new BaseObject(); } + function procAutoinstallAdminInsertConfig() + { + // if end of string does not have a slash, add it + $_location_site = Context::get('location_site'); + if(substr($_location_site, -1) != '/' && strlen($_location_site) > 0) + { + $_location_site .= '/'; + } + $_download_server = Context::get('download_server'); + if(substr($_download_server, -1) != '/' && strlen($_download_server) > 0) + { + $_download_server .= '/'; + } + + $args = new stdClass(); + $args->location_site = $_location_site; + $args->download_server = $_download_server; + + $oModuleController = getController('module'); + $output = $oModuleController->updateModuleConfig('autoinstall', $args); + + // init. DB tables + executeQuery("autoinstall.deletePackages"); + executeQuery("autoinstall.deleteCategory"); + executeQuery("autoinstall.deleteInstalledPackage"); + + // default setting end + $this->setMessage('success_updated'); + + $returnUrl = Context::get('success_return_url') ? Context::get('success_return_url') : getNotEncodedUrl('', 'module', 'admin', 'act', 'dispAutoinstallAdminConfig'); + $this->setRedirectUrl($returnUrl); + } + } /* End of file autoinstall.admin.controller.php */ /* Location: ./modules/autoinstall/autoinstall.admin.controller.php */ diff --git a/modules/autoinstall/autoinstall.admin.model.php b/modules/autoinstall/autoinstall.admin.model.php index fad936108..0f1c1ec8a 100644 --- a/modules/autoinstall/autoinstall.admin.model.php +++ b/modules/autoinstall/autoinstall.admin.model.php @@ -202,7 +202,7 @@ class autoinstallAdminModel extends autoinstall } $result = array(); - $xml = new XmlParser(); + $xml = new XeXmlParser(); foreach($output->data as $package) { $packageSrl = $package->package_srl; @@ -215,7 +215,6 @@ class autoinstallAdminModel extends autoinstall if($packageInfo->type == 'core') { - //$title = 'XpressEngine'; continue; } else @@ -315,7 +314,11 @@ class autoinstallAdminModel extends autoinstall { $package->depends[$key]->installed = TRUE; $package->depends[$key]->cur_version = $packages[$dep->package_srl]->current_version; - if(version_compare($dep->version, $packages[$dep->package_srl]->current_version, ">")) + if($packages[$dep->package_srl]->current_version === 'RX_VERSION') + { + $package->need_update = FALSE; + } + elseif(version_compare($dep->version, $packages[$dep->package_srl]->current_version, ">")) { $package->depends[$key]->need_update = TRUE; $package->package_srl .= "," . $dep->package_srl; @@ -333,7 +336,7 @@ class autoinstallAdminModel extends autoinstall { $package->installed = TRUE; $package->cur_version = $installedPackage->current_version; - $package->need_update = version_compare($package->version, $installedPackage->current_version, ">"); + $package->need_update = $installedPackage->current_version !== 'RX_VERSION' && version_compare($package->version, $installedPackage->current_version, ">"); } if($package->path === '.') @@ -421,6 +424,20 @@ class autoinstallAdminModel extends autoinstall return new BaseObject(); } + public function getAutoInstallAdminModuleConfig() + { + $oModuleModel = getModel('module'); + $config_info = $oModuleModel->getModuleConfig('autoinstall'); + $_location_site = 'https://xe1.xpressengine.com/'; + $_download_server = 'https://download.xpressengine.com/'; + + $config = new stdClass(); + $config->location_site = ($config_info->location_site ?? null) ?: $_location_site; + $config->download_server = ($config_info->download_server ?? null) ?: $_download_server; + + return $config; + } + } /* End of file autoinstall.admin.model.php */ /* Location: ./modules/autoinstall/autoinstall.admin.model.php */ diff --git a/modules/autoinstall/autoinstall.admin.view.php b/modules/autoinstall/autoinstall.admin.view.php index 0f9cb7ec4..31d34f040 100644 --- a/modules/autoinstall/autoinstall.admin.view.php +++ b/modules/autoinstall/autoinstall.admin.view.php @@ -27,9 +27,13 @@ class autoinstallAdminView extends autoinstall */ function init() { + $oAdminModel = getAdminModel('autoinstall'); + $config = $oAdminModel->getAutoInstallAdminModuleConfig(); + Context::set('config', $config); + $template_path = sprintf("%stpl/", $this->module_path); - Context::set('original_site', _XE_LOCATION_SITE_); - Context::set('uri', _XE_DOWNLOAD_SERVER_); + Context::set('original_site', $config->location_site); + Context::set('uri', $config->download_server); $this->setTemplatePath($template_path); $ftp_info = Context::getFTPInfo(); @@ -184,7 +188,7 @@ class autoinstallAdminView extends autoinstall } $oModel = getModel('autoinstall'); - + if($package == null) { $packages = $oModel->getInstalledPackages(array_keys($targetpackages)); @@ -192,17 +196,34 @@ class autoinstallAdminView extends autoinstall $depto = array(); + $oAdminModel = getAdminModel('autoinstall'); + $config = $oAdminModel->getAutoInstallAdminModuleConfig(); + foreach($items as $item) { $v = $this->rearrange($item, $targets); - $v->item_screenshot_url = str_replace('./', _XE_DOWNLOAD_SERVER_, $v->item_screenshot_url); + $v->item_screenshot_url = str_replace('./', $config->download_server, $v->item_screenshot_url); $v->category = $this->categories[$v->category_srl]->title; - $v->url = _XE_LOCATION_SITE_ . '?mid=download&package_srl=' . $v->package_srl; + $v->url = $config->location_site . '?mid=download&package_srl=' . $v->package_srl; if($packages[$v->package_srl]) { $v->current_version = $packages[$v->package_srl]->current_version; - $v->need_update = $packages[$v->package_srl]->need_update; + // if version is up + // insert Y + if($v->current_version === 'RX_VERSION') + { + $v->need_update = 'N'; + } + elseif(version_compare($v->item_version, $v->current_version, '>')) + { + $v->need_update = 'Y'; + } + else + { + $v->need_update = 'N'; + } + //$v->need_update = $packages[$v->package_srl]->need_update; $v->type = $oModel->getTypeFromPath($packages[$v->package_srl]->path); if($this->ftp_set && $v->depfrom) @@ -216,7 +237,7 @@ class autoinstallAdminView extends autoinstall if($v->type == "core") { - $v->avail_remove = FALSE; + continue; } else if($v->type == "module") { @@ -249,7 +270,7 @@ class autoinstallAdminView extends autoinstall continue; } - $xml = new XmlParser(); + $xml = new XeXmlParser(); $xmlDoc = $xml->loadXmlFile(FileHandler::getRealPath($path) . $config_file); if(!$xmlDoc) { @@ -314,13 +335,18 @@ class autoinstallAdminView extends autoinstall 'ssl_verify_peer' => FALSE, 'ssl_verify_host' => FALSE ); - $buff = FileHandler::getRemoteResource(_XE_DOWNLOAD_SERVER_, $body, 3, "POST", "application/xml", array(), array(), array(), $request_config); - $xml_lUpdate = new XmlParser(); + + $oAdminModel = getAdminModel('autoinstall'); + $config = $oAdminModel->getAutoInstallAdminModuleConfig(); + + $buff = FileHandler::getRemoteResource($config->download_server, $body, 3, "POST", "application/xml", array(), array(), array(), $request_config); + $xml_lUpdate = new XeXmlParser(); $xmlDoc = $xml_lUpdate->parse($buff); + $item_list = array(); + $res = array(); if($xmlDoc && $xmlDoc->response->packagelist->item) { $item_list = $this->rearranges($xmlDoc->response->packagelist->item, $package_list); - $res = array(); foreach($package_list as $package_srl => $package) { if($item_list[$package_srl]) @@ -366,6 +392,7 @@ class autoinstallAdminView extends autoinstall Context::set("package", $package); Context::set('contain_core', $package->contain_core); + Context::set('module_config', $oAdminModel->getAutoInstallAdminModuleConfig()); if(!$_SESSION['ftp_password']) { @@ -407,14 +434,19 @@ class autoinstallAdminView extends autoinstall 'ssl_verify_peer' => FALSE, 'ssl_verify_host' => FALSE ); - $buff = FileHandler::getRemoteResource(_XE_DOWNLOAD_SERVER_, $body, 3, "POST", "application/xml", array(), array(), array(), $request_config); - $xml_lUpdate = new XmlParser(); + + $oAdminModel = getAdminModel('autoinstall'); + $config = $oAdminModel->getAutoInstallAdminModuleConfig(); + + $buff = FileHandler::getRemoteResource($config->download_server, $body, 3, "POST", "application/xml", array(), array(), array(), $request_config); + $xml_lUpdate = new XeXmlParser(); $lUpdateDoc = $xml_lUpdate->parse($buff); $updateDate = $lUpdateDoc->response->updatedate->body; if(!$updateDate) { - throw new Rhymix\Framework\Exception('msg_connection_fail'); + Context::set('isNotUpdate', true); + return; } $oModel = getModel('autoinstall'); @@ -558,8 +590,12 @@ class autoinstallAdminView extends autoinstall 'ssl_verify_peer' => FALSE, 'ssl_verify_host' => FALSE ); - $buff = FileHandler::getRemoteResource(_XE_DOWNLOAD_SERVER_, $body, 3, "POST", "application/xml", array(), array(), array(), $request_config); - $xml_lUpdate = new XmlParser(); + + $oAdminModel = getAdminModel('autoinstall'); + $config = $oAdminModel->getAutoInstallAdminModuleConfig(); + + $buff = FileHandler::getRemoteResource($config->download_server, $body, 3, "POST", "application/xml", array(), array(), array(), $request_config); + $xml_lUpdate = new XeXmlParser(); $xmlDoc = $xml_lUpdate->parse($buff); if($xmlDoc && $xmlDoc->response->packagelist->item) { @@ -570,7 +606,6 @@ class autoinstallAdminView extends autoinstall $installedPackage->deps = $item_list[$package_srl]->deps; Context::set('package', $installedPackage); $this->setTemplateFile('uninstall'); - Context::addJsFilter($this->module_path . 'tpl/filter', 'uninstall_package.xml'); $security = new Security(); $security->encodeHTML('package.'); @@ -583,6 +618,14 @@ class autoinstallAdminView extends autoinstall } } + /** + * Display config + * + */ + function dispAutoinstallAdminConfig() + { + $this->setTemplateFile('config'); + } } /* End of file autoinstall.admin.view.php */ /* Location: ./modules/autoinstall/autoinstall.admin.view.php */ diff --git a/modules/autoinstall/autoinstall.class.php b/modules/autoinstall/autoinstall.class.php index bb821e125..c2b57af9b 100644 --- a/modules/autoinstall/autoinstall.class.php +++ b/modules/autoinstall/autoinstall.class.php @@ -14,7 +14,7 @@ class XmlGenerater * @param array $params The data * @return string Returns xml string */ - function generate(&$params) + public static function generate(&$params) { $xmlDoc = ''; if(!is_array($params)) @@ -37,20 +37,26 @@ class XmlGenerater * @param array $params Request data * @return object */ - function getXmlDoc(&$params) + public static function getXmlDoc(&$params) { - $body = XmlGenerater::generate($params); + $body = self::generate($params); $request_config = array( 'ssl_verify_peer' => FALSE, 'ssl_verify_host' => FALSE ); - $buff = FileHandler::getRemoteResource(_XE_DOWNLOAD_SERVER_, $body, 3, "POST", "application/xml", array(), array(), array(), $request_config); + + $oModuleModel = getModel('module'); + $module_info = $oModuleModel->getModuleConfig('autoinstall'); + $location_site = $module_info->location_site ? : 'https://xe1.xpressengine.com/'; + $download_server = $module_info->download_server ? : 'https://download.xpressengine.com/'; + + $buff = FileHandler::getRemoteResource($download_server, $body, 3, "POST", "application/xml", array(), array(), array(), $request_config); if(!$buff) { return; } - $xml = new XmlParser(); + $xml = new XeXmlParser(); $xmlDoc = $xml->parse($buff); return $xmlDoc; } @@ -69,15 +75,6 @@ class autoinstall extends ModuleObject */ var $tmp_dir = './files/cache/autoinstall/'; - /** - * Constructor - * - * @return void - */ - function __construct() - { - } - /** * For additional tasks required when installing * diff --git a/modules/autoinstall/autoinstall.lib.php b/modules/autoinstall/autoinstall.lib.php index f4c57f6c0..04655e659 100644 --- a/modules/autoinstall/autoinstall.lib.php +++ b/modules/autoinstall/autoinstall.lib.php @@ -153,12 +153,8 @@ class ModuleInstaller { $path_array = explode("/", $this->package->path); $target_name = array_pop($path_array); - $oModule = getModule($target_name, "class"); - if(!$oModule) - { - return new BaseObject(-1, 'msg_invalid_request'); - } - if(!method_exists($oModule, "moduleUninstall")) + $oModule = ModuleModel::getModuleInstallClass($target_name); + if(!$oModule || !method_exists($oModule, 'moduleUninstall')) { return new BaseObject(-1, 'msg_invalid_request'); } @@ -203,16 +199,15 @@ class ModuleInstaller if($type == "module") { - $oModuleModel = getModel('module'); $oInstallController = getController('install'); $module_path = ModuleHandler::getModulePath($target_name); - if($oModuleModel->checkNeedInstall($target_name)) + if(ModuleModel::checkNeedInstall($target_name)) { $oInstallController->installModule($target_name, $module_path); } - if($oModuleModel->checkNeedUpdate($target_name)) + if(ModuleModel::checkNeedUpdate($target_name)) { - $oModule = getModule($target_name, 'class'); + $oModule = ModuleModel::getModuleInstallClass($target_name); if(method_exists($oModule, 'moduleUpdate')) { $oModule->moduleUpdate(); @@ -313,580 +308,6 @@ class ModuleInstaller } -/** - * Module installer for SFTP - * @author NAVER (developers@xpressengine.com) - */ -class SFTPModuleInstaller extends ModuleInstaller -{ - - /** - * FTP information - * @var object - */ - var $ftp_info = NULL; - - /** - * SFTP connection - * @var resource - */ - var $connection = NULL; - - /** - * SFTP resource - * @var resource - */ - var $sftp = NULL; - - /** - * Constructor - * - * @param object $package Package information - * @return void - */ - function __construct(&$package) - { - $this->package = &$package; - $this->ftp_info = Context::getFTPInfo(); - } - - /** - * Connect to SFTP - * - * @return Object - */ - function _connect() - { - if(!function_exists('ssh2_connect')) - { - return new BaseObject(-1, 'msg_sftp_not_supported'); - } - - if(!$this->ftp_info->ftp_user || !$this->ftp_info->sftp || $this->ftp_info->sftp != 'Y') - { - return new BaseObject(-1, 'msg_ftp_invalid_auth_info'); - } - - if($this->ftp_info->ftp_host) - { - $ftp_host = $this->ftp_info->ftp_host; - } - else - { - $ftp_host = "127.0.0.1"; - } - $this->connection = ssh2_connect($ftp_host, $this->ftp_info->ftp_port); - if(!@ssh2_auth_password($this->connection, $this->ftp_info->ftp_user, $this->ftp_password)) - { - return new BaseObject(-1, 'msg_ftp_invalid_auth_info'); - } - $_SESSION['ftp_password'] = $this->ftp_password; - $this->sftp = ssh2_sftp($this->connection); - return new BaseObject(); - } - - /** - * Close - * - * @return void - */ - function _close() - { - - } - - /** - * Remove file - * - * @param string $path Path to remove - * @return Object - */ - function _removeFile($path) - { - if(substr($path, 0, 2) == "./") - { - $path = substr($path, 2); - } - $target_path = $this->ftp_info->ftp_root_path . $path; - - if(!@ssh2_sftp_unlink($this->sftp, $target_path)) - { - return new BaseObject(-1, sprintf(lang('msg_delete_file_failed'), $path)); - } - return new BaseObject(); - } - - /** - * Remove Directory - * - * @param string $path Path to remove - * @return Object - */ - function _removeDir_real($path) - { - if(substr($path, 0, 2) == "./") - { - $path = substr($path, 2); - } - $target_path = $this->ftp_info->ftp_root_path . $path; - - if(!@ssh2_sftp_rmdir($this->sftp, $target_path)) - { - return new BaseObject(-1, sprintf(lang('msg_delete_dir_failed'), $path)); - } - return new BaseObject(); - } - - /** - * Copy directory - * - * @param array $file_list File list to copy - * @return Object - */ - function _copyDir(&$file_list) - { - if(!$this->ftp_password) - { - return new BaseObject(-1, 'msg_ftp_password_input'); - } - - $output = $this->_connect(); - if(!$output->toBool()) - { - return $output; - } - $target_dir = $this->ftp_info->ftp_root_path . $this->target_path; - $copied = array(); - - if(is_array($file_list)) - { - foreach($file_list as $k => $file) - { - $org_file = $file; - if($this->package->path == ".") - { - $file = substr($file, 3); - } - $path = FileHandler::getRealPath("./" . $this->target_path . "/" . $file); - $pathname = dirname($target_dir . "/" . $file); - - if(!file_exists(FileHandler::getRealPath($real_path))) - { - ssh2_sftp_mkdir($this->sftp, $pathname, 0755, TRUE); - } - - ssh2_scp_send($this->connection, FileHandler::getRealPath($this->download_path . "/" . $org_file), $target_dir . "/" . $file); - $copied[] = $path; - } - } - - FileHandler::clearStatCache($copied, true); - FileHandler::invalidateOpcache($copied); - - return new BaseObject(); - } - -} - -/** - * Module installer for PHP FTP - * @author NAVER (developers@xpressengine.com) - */ -class PHPFTPModuleInstaller extends ModuleInstaller -{ - - /** - * FTP information - * @var object - */ - var $ftp_info = NULL; - - /** - * FTP connection - * @var resource - */ - var $connection = NULL; - - /** - * Constructor - * - * @param object $package Package information - * @var void - */ - function __construct(&$package) - { - $this->package = &$package; - $this->ftp_info = Context::getFTPInfo(); - } - - /** - * Connect to FTP - * - * @return Object - */ - function _connect() - { - if($this->ftp_info->ftp_host) - { - $ftp_host = $this->ftp_info->ftp_host; - } - else - { - $ftp_host = "127.0.0.1"; - } - - $this->connection = ftp_connect($ftp_host, $this->ftp_info->ftp_port); - if(!$this->connection) - { - return new BaseObject(-1, sprintf(lang('msg_ftp_not_connected'), 'host')); - } - - $login_result = @ftp_login($this->connection, $this->ftp_info->ftp_user, $this->ftp_password); - if(!$login_result) - { - $this->_close(); - return new BaseObject(-1, 'msg_ftp_invalid_auth_info'); - } - - $_SESSION['ftp_password'] = $this->ftp_password; - if($this->ftp_info->ftp_pasv != "N") - { - ftp_pasv($this->connection, TRUE); - } - return new BaseObject(); - } - - /** - * Remove file - * - * @param string $path Path to remove - * @return Object - */ - function _removeFile($path) - { - if(substr($path, 0, 2) == "./") - { - $path = substr($path, 2); - } - $target_path = $this->ftp_info->ftp_root_path . $path; - - if(!@ftp_delete($this->connection, $target_path)) - { - return new BaseObject(-1, "failed to delete file " . $path); - } - return new BaseObject(); - } - - /** - * Remove directory - * - * @param string $path Path to remove - * @return Object - */ - function _removeDir_real($path) - { - if(substr($path, 0, 2) == "./") - { - $path = substr($path, 2); - } - $target_path = $this->ftp_info->ftp_root_path . $path; - - if(!@ftp_rmdir($this->connection, $target_path)) - { - return new BaseObject(-1, "failed to delete directory " . $path); - } - return new BaseObject(); - } - - /** - * Close - * - * @return void - */ - function _close() - { - ftp_close($this->connection); - } - - /** - * Copy directory - * - * @param array $file_list File list to copy - * @return Object - */ - function _copyDir(&$file_list) - { - if(!$this->ftp_password) - { - return new BaseObject(-1, 'msg_ftp_password_input'); - } - - $output = $this->_connect(); - if(!$output->toBool()) - { - return $output; - } - - if(!$this->target_path) - { - $this->target_path = '.'; - } - if(substr($this->download_path, -1) == '/') - { - $this->download_path = substr($this->download_path, 0, -1); - } - $target_dir = $this->ftp_info->ftp_root_path . $this->target_path; - $copied = array(); - - if(is_array($file_list)) - { - foreach($file_list as $k => $file) - { - if(!$file) - { - continue; - } - $org_file = $file; - if($this->package->path == ".") - { - $file = substr($file, 3); - } - $path = FileHandler::getRealPath("./" . $this->target_path . "/" . $file); - $path_list = explode('/', dirname($this->target_path . "/" . $file)); - - $real_path = "./"; - $ftp_path = $this->ftp_info->ftp_root_path; - - for($i = 0; $i < count($path_list); $i++) - { - if($path_list == "") - { - continue; - } - $real_path .= $path_list[$i] . "/"; - $ftp_path .= $path_list[$i] . "/"; - if(!file_exists(FileHandler::getRealPath($real_path))) - { - if(!@ftp_mkdir($this->connection, $ftp_path)) - { - return new BaseObject(-1, "msg_make_directory_failed"); - } - - if(strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') - { - if(function_exists('ftp_chmod')) - { - if(!ftp_chmod($this->connection, 0755, $ftp_path)) - { - return new BaseObject(-1, "msg_permission_adjust_failed"); - } - } - else - { - if(!ftp_site($this->connection, "CHMOD 755 " . $ftp_path)) - { - return new BaseObject(-1, "msg_permission_adjust_failed"); - } - } - } - } - } - if(!ftp_put($this->connection, $target_dir . '/' . $file, FileHandler::getRealPath($this->download_path . "/" . $org_file), FTP_BINARY)) - { - return new BaseObject(-1, "msg_ftp_upload_failed"); - } - $copied[] = $path; - } - } - - FileHandler::clearStatCache($copied, true); - FileHandler::invalidateOpcache($copied); - - $this->_close(); - return new BaseObject(); - } - -} - -/** - * Module installer for FTP - * @author NAVER (developers@xpressengine.com) - */ -class FTPModuleInstaller extends ModuleInstaller -{ - - /** - * FTP instance - * @var FTP - */ - var $oFtp = NULL; - - /** - * FTP information - * @var object - */ - var $ftp_info = NULL; - - /** - * Constructor - * - * @param object $package Package information - */ - function __construct(&$package) - { - $this->package = &$package; - $this->ftp_info = Context::getFTPInfo(); - } - - /** - * Connect to FTP - * - * @return Object - */ - function _connect() - { - if($this->ftp_info->ftp_host) - { - $ftp_host = $this->ftp_info->ftp_host; - } - else - { - $ftp_host = "127.0.0.1"; - } - - $this->oFtp = new ftp(); - if(!$this->oFtp->ftp_connect($ftp_host, $this->ftp_info->ftp_port)) - { - return new BaseObject(-1, sprintf(lang('msg_ftp_not_connected'), 'host')); - } - if(!$this->oFtp->ftp_login($this->ftp_info->ftp_user, $this->ftp_password)) - { - $this->_close(); - return new BaseObject(-1, 'msg_ftp_invalid_auth_info'); - } - $_SESSION['ftp_password'] = $this->ftp_password; - return new BaseObject(); - } - - /** - * Remove file - * - * @param string $path Path to remove - * @return Object - */ - function _removeFile($path) - { - if(substr($path, 0, 2) == "./") - { - $path = substr($path, 2); - } - $target_path = $this->ftp_info->ftp_root_path . $path; - - if(!$this->oFtp->ftp_delete($target_path)) - { - return new BaseObject(-1, sprintf(lang('msg_delete_file_failed'), $path)); - } - return new BaseObject(); - } - - /** - * Remove directory - * @param string $path Path to remove - * @return Object - */ - function _removeDir_real($path) - { - if(substr($path, 0, 2) == "./") - { - $path = substr($path, 2); - } - $target_path = $this->ftp_info->ftp_root_path . $path; - - if(!$this->oFtp->ftp_rmdir($target_path)) - { - return new BaseObject(-1, sprintf(lang('msg_delete_dir_failed'), $path)); - } - return new BaseObject(); - } - - /** - * Close - * - * @return void - */ - function _close() - { - $this->oFtp->ftp_quit(); - } - - /** - * Copy directory - * - * @param array $file_list File list to copy - * @return Object - */ - function _copyDir(&$file_list) - { - if(!$this->ftp_password) - { - return new BaseObject(-1, 'msg_ftp_password_input'); - } - - $output = $this->_connect(); - if(!$output->toBool()) - { - return $output; - } - - $oFtp = &$this->oFtp; - $target_dir = $this->ftp_info->ftp_root_path . $this->target_path; - - $copied = array(); - - if(is_array($file_list)) - { - foreach($file_list as $k => $file) - { - $org_file = $file; - if($this->package->path == ".") - { - $file = substr($file, 3); - } - $path = FileHandler::getRealPath("./" . $this->target_path . "/" . $file); - $path_list = explode('/', dirname($this->target_path . "/" . $file)); - - $real_path = "./"; - $ftp_path = $this->ftp_info->ftp_root_path; - - for($i = 0; $i < count($path_list); $i++) - { - if($path_list == "") - { - continue; - } - $real_path .= $path_list[$i] . "/"; - $ftp_path .= $path_list[$i] . "/"; - if(!file_exists(FileHandler::getRealPath($real_path))) - { - $oFtp->ftp_mkdir($ftp_path); - $oFtp->ftp_site("CHMOD 755 " . $ftp_path); - } - } - $oFtp->ftp_put($target_dir . '/' . $file, FileHandler::getRealPath($this->download_path . "/" . $org_file)); - $copied[] = $path; - } - } - - FileHandler::clearStatCache($copied, true); - FileHandler::invalidateOpcache($copied); - - $this->_close(); - - return new BaseObject(); - } - -} - /** * Module installer for Direct. Not use FTP * @author NAVER (developers@xpressengine.com) diff --git a/modules/autoinstall/autoinstall.model.php b/modules/autoinstall/autoinstall.model.php index b837d9751..94e45644c 100644 --- a/modules/autoinstall/autoinstall.model.php +++ b/modules/autoinstall/autoinstall.model.php @@ -157,7 +157,7 @@ class autoinstallModel extends autoinstall * @param int $depth Depth of category * @param array $list Category list * @param array $resultList Final result list - * @return string $siblingList Comma seperated list + * @return string $siblingList Comma separated list */ function setDepth(&$item, $depth, &$list, &$resultList) { @@ -316,7 +316,7 @@ class autoinstallModel extends autoinstall { $path_array = explode("/", $path); $target_name = array_pop($path_array); - $oModule = getModule($target_name, "class"); + $oModule = ModuleModel::getModuleInstallClass($target_name); if(!$oModule) { return FALSE; @@ -349,16 +349,16 @@ class autoinstallModel extends autoinstall $path = substr($path, 0, strlen($path) - 1); } - if(!$GLOBLAS['XE_AUTOINSTALL_PACKAGE_SRL_BY_PATH'][$path]) + if(empty($GLOBALS['XE_AUTOINSTALL_PACKAGE_SRL_BY_PATH'][$path])) { $args = new stdClass(); $args->path = $path; $output = executeQuery('autoinstall.getPackageSrlByPath', $args); - $GLOBLAS['XE_AUTOINSTALL_PACKAGE_SRL_BY_PATH'][$path] = $output->data->package_srl; + $GLOBALS['XE_AUTOINSTALL_PACKAGE_SRL_BY_PATH'][$path] = $output->data->package_srl ?? null; } - return $GLOBLAS['XE_AUTOINSTALL_PACKAGE_SRL_BY_PATH'][$path]; + return $GLOBALS['XE_AUTOINSTALL_PACKAGE_SRL_BY_PATH'][$path]; } /** @@ -370,7 +370,7 @@ class autoinstallModel extends autoinstall function getRemoveUrlByPackageSrl($packageSrl) { $ftp_info = Context::getFTPInfo(); - if(!$ftp_info->ftp_root_path) + if(empty($ftp_info->ftp_root_path)) { return; } diff --git a/modules/autoinstall/conf/info.xml b/modules/autoinstall/conf/info.xml index 74e8459e7..3d3638ed0 100644 --- a/modules/autoinstall/conf/info.xml +++ b/modules/autoinstall/conf/info.xml @@ -15,8 +15,8 @@ 很方便的在线安装/更新XE相关模块(模块/皮肤/布局/控件/控件样式等)。 管理者モードにてクリックすることだけでモジュール/スキン/レイアウト/ウィジェット/ウィジェットスタイルのインストールを可能にするモジュールです。 Bu modülle; modüller, dış görünümler, yerleşim düzenleri vs. gibi programlarınızı www.xpressengine.com adresinden tek tıkla kurabilir ve sürümlerini yükseltebilirsiniz. - 1.7 - 2013-11-27 + RX_VERSION + RX_CORE system NAVER diff --git a/modules/autoinstall/conf/module.xml b/modules/autoinstall/conf/module.xml index 1e7c1780a..d5b0e87a7 100644 --- a/modules/autoinstall/conf/module.xml +++ b/modules/autoinstall/conf/module.xml @@ -6,16 +6,20 @@ - + + + + + diff --git a/modules/autoinstall/lang/en.php b/modules/autoinstall/lang/en.php index 73d91756a..8c4281413 100644 --- a/modules/autoinstall/lang/en.php +++ b/modules/autoinstall/lang/en.php @@ -6,7 +6,7 @@ $lang->order_newest = 'Newest'; $lang->order_popular = 'Popular'; $lang->order_download = 'Download'; $lang->success_installed = 'Successfully Installed'; -$lang->description_ftp_note = 'If the %s is not set, the installation or update will not work. Pleas configure the FTP information.'; +$lang->description_ftp_note = 'If the %s is not set, the installation or update will not work. Please configure the FTP information.'; $lang->ftp_setup = 'FTP configuration'; $lang->description_update = 'Click %s before using EasyInstall.'; $lang->status_update = 'update button'; @@ -16,7 +16,7 @@ $lang->current_version = 'Version'; $lang->require_update = 'Update is required.'; $lang->require_installation = 'Installation is required.'; $lang->description_install = 'EasyInstall will also install/update all other programs which this program depends on.'; -$lang->description_download = 'If FTP is unavailable, you should manually download and extract it into the target path. (If the target path is ./modules/board, extract it to ./modules)'; +$lang->description_download = 'If permission change is not possible, you should manually download and extract it into the target path. (If the target path is ./modules/board, extract it to ./modules)'; $lang->path = 'Path'; $lang->cmd_download = 'Download'; $lang->description_uninstall = 'Package will be uninstalled. All data will be deleted.'; @@ -39,8 +39,11 @@ $lang->msg_delete_file_failed = 'Failed to delete the file. %s.'; $lang->msg_delete_dir_failed = 'Failed to delete the directory. %s'; $lang->msg_ftp_password_input = 'No password entered.'; $lang->msg_sftp_not_supported = 'SFTP is not supported.'; -$lang->msg_direct_install_not_supported = 'Use FTP because there is no write permission to the directories listed in the list below.'; +$lang->msg_no_permission_to_install = 'Your web server does not have permission to update the installation path. Please check server permissions.'; +$lang->msg_direct_install_not_supported = 'Cannot proceed due to write permission missing to the directories listed in the list below.'; $lang->msg_does_not_support_delete = 'Cannot delete this package (no moduleUninstall() in the module class).'; +$lang->msg_update_core_title = 'Rhymix Core is updateing.'; +$lang->msg_update_core = 'Prior to updating Rhymix Core, please check the compatibility of the installed packages (e.g., modules, widgets, layouts, skins, etc.).'; $lang->installed = 'Installed'; $lang->typename['core'] = 'Core'; $lang->typename['m.layout'] = 'Mobile layout'; @@ -53,3 +56,8 @@ $lang->typename['m.skin'] = 'Mobile Skin'; $lang->typename['skin'] = 'Skin'; $lang->typename['widgetstyle'] = 'Widget style'; $lang->typename['style'] = 'Document style'; + +$lang->location_site = 'Download Homepage'; +$lang->about_location_site = 'Please type your download homepage. ex)https://xe1.xpressengine.com/'; +$lang->download_server = 'Download Server'; +$lang->about_download_server = 'Please type your download server. ex)https://download.xpressengine.com/'; diff --git a/modules/autoinstall/lang/ja.php b/modules/autoinstall/lang/ja.php index 28d53917a..cc58aa99a 100644 --- a/modules/autoinstall/lang/ja.php +++ b/modules/autoinstall/lang/ja.php @@ -16,7 +16,7 @@ $lang->current_version = '現在のバージョン'; $lang->require_update = 'アップデートが必要です。'; $lang->require_installation = 'インストールが必要です。'; $lang->description_install = 'インストールに進みますと、このプログラムが依存しているすべてのプログラムをアップデート/インストールします。'; -$lang->description_download = 'FTPの利用ができない場合は、直接ダウンロードし、サーバー上の該当パスにてインストールしてください。 (一つ上の階層にて解凍します。 ./modules/board の場合 ./modulesに tarを解凍してください。)'; +$lang->description_download = '直接ダウンロードし、サーバー上の該当パスにてインストールしてください。 (一つ上の階層にて解凍します。 ./modules/board の場合 ./modulesに tarを解凍してください。)'; $lang->path = 'インストール先'; $lang->cmd_download = 'ダウンロード'; $lang->description_uninstall = 'パッケージを削除します。モジュールの場合、すべてのデータを失います。'; @@ -39,7 +39,7 @@ $lang->msg_delete_file_failed = 'ファイルの削除に失敗しました。%s $lang->msg_delete_dir_failed = 'ディレクトリの削除に失敗しました。'; $lang->msg_ftp_password_input = 'パスワードを入力してください。'; $lang->msg_sftp_not_supported = 'SFTPの非対応環境です。'; -$lang->msg_direct_install_not_supported = '下記のリストにリストされているディレクトリへの書き込み権限がないため、FTPを使用してください。'; +$lang->msg_direct_install_not_supported = '以下のリストに記載されているディレクトリへの書き込み権限がないため、続行できません。'; $lang->msg_does_not_support_delete = 'このパッケージは、削除をサポートしません(モジュールクラスにmoduleUninstall()がありません)。'; $lang->msg_update_core_title = 'Rhymix coreがアップデートされます。'; $lang->msg_update_core = 'coreアップデート時、インストールされたプログラムの互換性を必ず点検ください。'; diff --git a/modules/autoinstall/lang/ko.php b/modules/autoinstall/lang/ko.php index 846f36e6e..0c01a9bba 100644 --- a/modules/autoinstall/lang/ko.php +++ b/modules/autoinstall/lang/ko.php @@ -16,7 +16,7 @@ $lang->current_version = '설치 버전'; $lang->require_update = '업데이트가 필요합니다.'; $lang->require_installation = '설치가 필요합니다.'; $lang->description_install = '설치를 진행하면, 이 프로그램이 의존하는 모든 프로그램을 업데이트/설치 합니다.'; -$lang->description_download = 'FTP를 이용할 수 없는 경우, 직접 다운로드 하여 해당 경로(path)에 설치해야 합니다. (한 단계 상위에서 압축을 풀면 됩니다. ./modules/board의 경우 ./modules에서 묶음(tar)을 푸세요)'; +$lang->description_download = '권한 변경이 불가능한 경우, 직접 다운로드 하여 해당 경로(path)에 설치해야 합니다. (한 단계 상위에서 압축을 풀면 됩니다. ./modules/board의 경우 ./modules에서 묶음(tar)을 푸세요)'; $lang->path = '설치경로'; $lang->cmd_download = '다운로드'; $lang->description_uninstall = '패키지를 삭제합니다. 모든 데이터가 사라집니다.'; @@ -39,7 +39,8 @@ $lang->msg_delete_file_failed = '파일 삭제에 실패했습니다. %s.'; $lang->msg_delete_dir_failed = '디렉터리 삭제에 실패했습니다. %s'; $lang->msg_ftp_password_input = '입력된 비밀번호가 없습니다.'; $lang->msg_sftp_not_supported = 'SFTP를 지원하지 않는 환경입니다.'; -$lang->msg_direct_install_not_supported = '아래 목록에 나열된 디렉터리에 쓰기 권한이 없기 때문에 FTP를 사용합니다.'; +$lang->msg_no_permission_to_install = '설치 경로에 쓰기 권한이 없습니다. 퍼미션을 확인해 주십시오.'; +$lang->msg_direct_install_not_supported = '아래 목록에 나열된 디렉터리에 쓰기 권한이 없어서 진행할 수 없습니다.'; $lang->msg_does_not_support_delete = '이 패키지가 삭제를 지원하지 않습니다(모듈 클래스에 moduleUninstall()이 없음).'; $lang->msg_update_core_title = 'Rhymix core가 업데이트 됩니다.'; $lang->msg_update_core = 'core 업데이트 시 설치된 프로그램의 호환성을 반드시 점검 바랍니다.'; @@ -55,3 +56,9 @@ $lang->typename['m.skin'] = '모바일 스킨'; $lang->typename['skin'] = '스킨'; $lang->typename['widgetstyle'] = '위젯스타일'; $lang->typename['style'] = '문서스타일'; + +$lang->location_site = '다운로드 홈페이지'; +$lang->about_location_site = '다운로드 홈페이지를 입력해 주세요. 예)https://xe1.xpressengine.com/'; +$lang->download_server = '다운로드 서버'; +$lang->about_download_server = '다운로드 서버를 입력해 주세요. 예)https://download.xpressengine.com/'; + diff --git a/modules/autoinstall/lang/tr.php b/modules/autoinstall/lang/tr.php index 2e4142dd1..490073406 100644 --- a/modules/autoinstall/lang/tr.php +++ b/modules/autoinstall/lang/tr.php @@ -12,7 +12,7 @@ $lang->current_version = 'Sürüm'; $lang->require_update = 'Güncelleme gerekmektedir.'; $lang->require_installation = 'Kurulum gerekmektedir.'; $lang->description_install = 'KolayKurulum, bu program için gerekli olan tüm diğer programları kurup/güncelleştirecektir.'; -$lang->description_download = 'Eğer FTP kullanılamaz durumduysa, indirmeyi kendiniz yapmanız ve dosyaları hedef dizine çıkartmanız gerekmektedir. (eğer hedef yol ./modules/board ise, çıkarma işlemini ./modules yoluna yapınız)'; +$lang->description_download = 'indirmeyi kendiniz yapmanız ve dosyaları hedef dizine çıkartmanız gerekmektedir. (eğer hedef yol ./modules/board ise, çıkarma işlemini ./modules yoluna yapınız)'; $lang->path = 'Yol'; $lang->cmd_download = 'İndirme'; $lang->description_uninstall = 'Paket kaldırılacaktır. Modüller için, tüm veriler silinecektir.'; diff --git a/modules/autoinstall/lang/vi.php b/modules/autoinstall/lang/vi.php index 3d3e84e9b..e6a3f24c5 100644 --- a/modules/autoinstall/lang/vi.php +++ b/modules/autoinstall/lang/vi.php @@ -11,7 +11,7 @@ $lang->current_version = 'Phiên bản đang dùng'; $lang->require_update = 'Yêu cầu cập nhật.'; $lang->require_installation = 'Yêu cầu cài đặt.'; $lang->description_install = 'Quá trình Cài đặt / Cập nhật này phụ thuộc vào '; -$lang->description_download = 'Khi FTP không được mở, bạn nên tải về và giả nén, sau đó Upload theo đường dẫn. (Nếu đường dẫn là ./modules/board, thì giải nén vào ./modules)'; +$lang->description_download = 'bạn nên tải về và giả nén, sau đó Upload theo đường dẫn. (Nếu đường dẫn là ./modules/board, thì giải nén vào ./modules)'; $lang->path = 'Đường dẫn'; $lang->description_uninstall = 'Loại bỏ gói cài đặt. Tất cả dữ liệu của các gói sẽ đồng thời bị xóa.'; $lang->rate = 'points'; diff --git a/modules/autoinstall/lang/zh-CN.php b/modules/autoinstall/lang/zh-CN.php index 2a3138dc0..accef7df5 100644 --- a/modules/autoinstall/lang/zh-CN.php +++ b/modules/autoinstall/lang/zh-CN.php @@ -12,7 +12,7 @@ $lang->current_version = '现用版本'; $lang->require_update = '需要更新'; $lang->require_installation = '需要安装'; $lang->description_install = '安装时,与其联动的插件/控件也会同时被安装(更新)。'; -$lang->description_download = '无法使用FTP时,需得自行下载安装到指定路径。'; +$lang->description_download = '需得自行下载安装到指定路径。'; $lang->path = '安装路径'; $lang->cmd_download = '下载'; $lang->description_uninstall = '确定要删除此数据包吗?如果此数据包为模块,模块中的数据将会全部消失。'; diff --git a/modules/autoinstall/lang/zh-TW.php b/modules/autoinstall/lang/zh-TW.php index 301547c99..fe92ac751 100644 --- a/modules/autoinstall/lang/zh-TW.php +++ b/modules/autoinstall/lang/zh-TW.php @@ -16,7 +16,7 @@ $lang->current_version = '目前版本'; $lang->require_update = '需要更新'; $lang->require_installation = '需要安裝'; $lang->description_install = '自動安裝也能夠同時安裝與更新其他相關程式'; -$lang->description_download = '如果 FTP 無法使用的話,必須要手動下載並解壓縮到目標路徑。(假設目標路徑為 ./modules/board 的話,將檔案解壓縮到 ./modules就可以了)'; +$lang->description_download = '必須要手動下載並解壓縮到目標路徑。(假設目標路徑為 ./modules/board 的話,將檔案解壓縮到 ./modules就可以了)'; $lang->path = '路徑'; $lang->cmd_download = '下載'; $lang->description_uninstall = '移除模組,所有資料將會被刪除。'; diff --git a/modules/autoinstall/queries/deletePackages.xml b/modules/autoinstall/queries/deletePackages.xml new file mode 100644 index 000000000..ac303ef2f --- /dev/null +++ b/modules/autoinstall/queries/deletePackages.xml @@ -0,0 +1,5 @@ + + +
    - Total: {number_format($total_count)}, Page: {number_format($page)}/{number_format($total_page)} + +
    + Total: {number_format($total_count)}, Page: {number_format($page)}/{number_format($total_page)} +
    +
    + + diff --git a/modules/autoinstall/queries/getPackageSrlByPath.xml b/modules/autoinstall/queries/getPackageSrlByPath.xml index fef67cbdc..42e7f8af3 100644 --- a/modules/autoinstall/queries/getPackageSrlByPath.xml +++ b/modules/autoinstall/queries/getPackageSrlByPath.xml @@ -1,4 +1,4 @@ - +
    diff --git a/modules/autoinstall/ruleset/ftp.xml b/modules/autoinstall/ruleset/ftp.xml deleted file mode 100644 index e8f584700..000000000 --- a/modules/autoinstall/ruleset/ftp.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/modules/autoinstall/tpl/category.html b/modules/autoinstall/tpl/category.html index d1cb9b7b5..7b9732b09 100644 --- a/modules/autoinstall/tpl/category.html +++ b/modules/autoinstall/tpl/category.html @@ -1,13 +1,14 @@ - + + + @@ -21,7 +23,7 @@ - + + - + + @@ -95,7 +116,7 @@ - {$lang->cmd_cancel} + {$lang->cmd_cancel} {$selected_manage_content} diff --git a/modules/board/tpl/js/board.js b/modules/board/tpl/js/board.js index d0e7fa165..5c05b2cfb 100644 --- a/modules/board/tpl/js/board.js +++ b/modules/board/tpl/js/board.js @@ -1,175 +1,182 @@ -/** - * @file modules/board/js/board.js - * @author NHN (developers@xpressengine.com) - * @brief board 모듈의 javascript - **/ - -/* complete tp insert document */ -function completeDocumentInserted(ret_obj) -{ - var error = ret_obj.error; - var message = ret_obj.message; - var mid = ret_obj.mid; - var document_srl = ret_obj.document_srl; - var category_srl = ret_obj.category_srl; - - if (ret_obj.redirect_url) { - redirect(ret_obj.redirect_url); - } else { - var url; - if(!document_srl) - { - url = current_url.setQuery('mid',mid).setQuery('act',''); - } - else - { - url = current_url.setQuery('mid',mid).setQuery('document_srl',document_srl).setQuery('act',''); - } - if(category_srl) url = url.setQuery('category',category_srl); - redirect(url); - } -} - -/* delete the document */ -function completeDeleteDocument(ret_obj) -{ - var error = ret_obj.error; - var message = ret_obj.message; - var mid = ret_obj.mid; - var page = ret_obj.page; - - var url = current_url.setQuery('mid',mid).setQuery('act','').setQuery('document_srl',''); - if(page) url = url.setQuery('page',page); - redirect(url); -} - -/* document search */ -function completeSearch(ret_obj, response_tags, params, fo_obj) -{ - fo_obj.submit(); -} - -function completeVote(ret_obj) -{ - var error = ret_obj.error; - var message = ret_obj.message; - redirect(window.location.href); -} - -// current page reload -function completeReload(ret_obj) -{ - var error = ret_obj.error; - var message = ret_obj.message; - redirect(window.location.href); -} - -/* complete to insert comment*/ -function completeInsertComment(ret_obj) -{ - var error = ret_obj.error; - var message = ret_obj.message; - var mid = ret_obj.mid; - var document_srl = ret_obj.document_srl; - var comment_srl = ret_obj.comment_srl; - if (ret_obj.redirect_url) { - redirect(ret_obj.redirect_url); - } else { - var url = current_url.setQuery('mid',mid).setQuery('document_srl',document_srl).setQuery('act',''); - if (comment_srl) url = url.setQuery('rnd',comment_srl)+"#comment_"+comment_srl; - redirect(url); - } -} - -/* delete the comment */ -function completeDeleteComment(ret_obj) -{ - var error = ret_obj.error; - var message = ret_obj.message; - var mid = ret_obj.mid; - var document_srl = ret_obj.document_srl; - var page = ret_obj.page; - - if (ret_obj.redirect_url) { - redirect(ret_obj.redirect_url); - } else { - var url = current_url.setQuery('mid',mid).setQuery('document_srl',document_srl).setQuery('act',''); - if (page) url = url.setQuery('page',page); - redirect(url); - } -} - -/* delete the trackback */ -function completeDeleteTrackback(ret_obj) -{ - var error = ret_obj.error; - var message = ret_obj.message; - var mid = ret_obj.mid; - var document_srl = ret_obj.document_srl; - var page = ret_obj.page; - - if (ret_obj.redirect_url) { - redirect(ret_obj.redirect_url); - } else { - var url = current_url.setQuery('mid',mid).setQuery('document_srl',document_srl).setQuery('act',''); - if (page) url = url.setQuery('page',page); - redirect(url); - } -} - -/* change category */ -function doChangeCategory() -{ - var category_srl = jQuery('#board_category option:selected').val(); - location.href = decodeURI(current_url).setQuery('category',category_srl).setQuery('page', ''); -} - -/* scrap */ -function doScrap(document_srl) -{ - var params = []; - params.document_srl = document_srl; - jQuery.exec_json('member.procMemberScrapDocument', params); -} - -jQuery(function($){ - $(document.body).click(function(e){ - var t = $(e.target), act, params = {}; - - if(t.parents('.layer_voted_member').length === 0 && !t.is('.layer_voted_member')){ - $('.layer_voted_member').hide().remove(); - } - - if(!t.is('a[class^=voted_member_]')) return; - - var srl = parseInt(t.attr('class').replace(/[^0-9]/g,'')); - if(!srl) return; - - if(t.hasClass('comment')){ - act = 'comment.getCommentVotedMemberList'; - params = - {'comment_srl':srl,'point':(t.hasClass('votedup')?1:-1)}; - }else{ - act = 'document.getDocumentVotedMemberList'; - params = - {'document_srl':srl,'point':(t.hasClass('votedup')?1:-1)}; - } - - $.exec_json(act, params, function(data){ - var l = data.voted_member_list; - var ul = []; - - if(!l || l.length === 0) return; - - $.each(l,function(){ - ul.push(this.nick_name); - }); - - t.after($('
      ') - .addClass('layer_voted_member') - .css({'position':'absolute','top':e.pageY+5,'left':e.pageX}) - .append('
    • '+ul.join('
    • ')+'
    • ') - ); - }); - }); -}); +/** + * @file modules/board/js/board.js + * @author NHN (developers@xpressengine.com) + * @brief board 모듈의 javascript + **/ + +/* complete tp insert document */ +function completeDocumentInserted(ret_obj) +{ + var mid = ret_obj.mid; + var document_srl = ret_obj.document_srl; + var category_srl = ret_obj.category_srl; + var current_category_srl = parseInt(current_url.getQuery('category'), 10); + var url; + + if (ret_obj.redirect_url) { + url = ret_obj.redirect_url; + } else { + url = current_url.setQuery('mid', mid).setQuery('act', ''); + if (document_srl) { + url = url.setQuery('document_srl', document_srl); + } + } + + if (current_category_srl && category_srl && current_category_srl == category_srl) { + url = url.setQuery('category', current_category_srl); + } + redirect(url); +} + +/* delete the document */ +function completeDeleteDocument(ret_obj) +{ + var mid = ret_obj.mid; + var page = ret_obj.page; + var url; + + if (ret_obj.redirect_url) { + url = ret_obj.redirect_url; + } else { + url = current_url.setQuery('mid', mid).setQuery('act', '').setQuery('document_srl', ''); + if (page) { + url = url.setQuery('page', page); + } + } + + redirect(url); +} + +/* document search */ +function completeSearch(ret_obj, response_tags, params, fo_obj) +{ + fo_obj.submit(); +} + +function completeVote(ret_obj) +{ + var error = ret_obj.error; + var message = ret_obj.message; + redirect(window.location.href); +} + +// current page reload +function completeReload(ret_obj) +{ + var error = ret_obj.error; + var message = ret_obj.message; + redirect(window.location.href); +} + +/* complete to insert comment*/ +function completeInsertComment(ret_obj) +{ + var error = ret_obj.error; + var message = ret_obj.message; + var mid = ret_obj.mid; + var document_srl = ret_obj.document_srl; + var comment_srl = ret_obj.comment_srl; + if (ret_obj.redirect_url) { + redirect(ret_obj.redirect_url); + } else { + var url = current_url.setQuery('mid',mid).setQuery('document_srl',document_srl).setQuery('act',''); + if (comment_srl) url = url.setQuery('comment_srl',comment_srl)+"#comment_"+comment_srl; + redirect(url); + } +} + +/* delete the comment */ +function completeDeleteComment(ret_obj) +{ + var mid = ret_obj.mid; + var document_srl = ret_obj.document_srl; + var page = ret_obj.page; + var url; + + if (ret_obj.redirect_url) { + url = ret_obj.redirect_url; + } else { + url = current_url.setQuery('mid', mid).setQuery('document_srl', document_srl).setQuery('act', ''); + if (page) { + url = url.setQuery('page', page); + } + } + redirect(url); +} + +/* delete the trackback */ +function completeDeleteTrackback(ret_obj) +{ + var error = ret_obj.error; + var message = ret_obj.message; + var mid = ret_obj.mid; + var document_srl = ret_obj.document_srl; + var page = ret_obj.page; + + if (ret_obj.redirect_url) { + redirect(ret_obj.redirect_url); + } else { + var url = current_url.setQuery('mid',mid).setQuery('document_srl',document_srl).setQuery('act',''); + if (page) url = url.setQuery('page',page); + redirect(url); + } +} + +/* change category */ +function doChangeCategory() +{ + var category_srl = jQuery('#board_category option:selected').val(); + location.href = decodeURI(current_url).setQuery('category',category_srl).setQuery('page', ''); +} + +/* scrap */ +function doScrap(document_srl) +{ + var params = []; + params.document_srl = document_srl; + jQuery.exec_json('member.procMemberScrapDocument', params); +} + + +jQuery(function($){ + + $(document.body).click(function(e){ + var t = $(e.target), act, params = {}; + + if(t.parents('.layer_voted_member').length === 0 && !t.is('.layer_voted_member')){ + $('.layer_voted_member').hide().remove(); + } + + if(!t.is('a[class^=voted_member_]')) return; + + var srl = parseInt(t.attr('class').replace(/[^0-9]/g,'')); + if(!srl) return; + + if(t.hasClass('comment')){ + act = 'comment.getCommentVotedMemberList'; + params = + {'comment_srl':srl,'point':(t.hasClass('votedup')?1:-1)}; + }else{ + act = 'document.getDocumentVotedMemberList'; + params = + {'document_srl':srl,'point':(t.hasClass('votedup')?1:-1)}; + } + + $.exec_json(act, params, function(data){ + var l = data.voted_member_list; + var ul = []; + + if(!l || l.length === 0) return; + + $.each(l,function(){ + ul.push(this.nick_name); + }); + + t.after($('
        ') + .addClass('layer_voted_member') + .css({'position':'absolute','top':e.pageY+5,'left':e.pageX}) + .append('
      • '+ul.join('
      • ')+'
      • ') + ); + }); + }); +}); diff --git a/modules/board/tpl/js/board_admin.js b/modules/board/tpl/js/board_admin.js index 687d3fe1b..53f4de04a 100644 --- a/modules/board/tpl/js/board_admin.js +++ b/modules/board/tpl/js/board_admin.js @@ -1,149 +1,164 @@ -/** - * @file modules/board/js/board_admin.js - * @author NHN (developers@xpressengine.com) - * @brief board 모듈의 관리자용 javascript - **/ -/* complete to insert board module */ -function completeInsertBoard(ret_obj) -{ - var error = ret_obj.error; - var message = ret_obj.message; - - var page = ret_obj.page; - var module_srl = ret_obj.module_srl; - - alert(message); - - var url = current_url.setQuery('act','dispBoardAdminBoardInfo'); - if(module_srl) url = url.setQuery('module_srl',module_srl); - if(page) url.setQuery('page',page); - location.href = url; -} - -/* delete the board module*/ -function completeDeleteBoard(ret_obj) -{ - var error = ret_obj.error; - var message = ret_obj.message; - var page = ret_obj.page; - alert(message); - - var url = current_url.setQuery('act','dispBoardAdminContent').setQuery('module_srl',''); - if(page) url = url.setQuery('page',page); - location.href = url; -} - -/* update category */ -function doUpdateCategory(category_srl, mode, message) -{ - if(typeof(message)!='undefined'&&!confirm(message)) return; - - var fo_obj = xGetElementById('fo_category_info'); - fo_obj.category_srl.value = category_srl; - fo_obj.mode.value = mode; - - procFilter(fo_obj, update_category); -} - -/* change category */ -function completeUpdateCategory(ret_obj) -{ - var error = ret_obj.error; - var message = ret_obj.message; - var module_srl = ret_obj.module_srl; - var page = ret_obj.page; - alert(message); - - var url = current_url.setQuery('module_srl',module_srl).setQuery('act','dispBoardAdminCategoryInfo'); - if(page) url.setQuery('page',page); - location.href = url; -} - -/* setup all*/ -function doCartSetup(url) -{ - var module_srl = []; - jQuery('#fo_list input[name=cart]:checked').each(function() - { - module_srl[module_srl.length] = jQuery(this).val(); - }); - - if(module_srl.length<1) return; - - url += "&module_srls="+module_srl.join(','); - popopen(url,'modulesSetup'); -} - -/* setup index */ -function doInsertItem() -{ - var target_obj = xGetElementById('targetItem'); - var display_obj = xGetElementById('displayItem'); - if(!target_obj || !display_obj) return; - - var text = target_obj.options[target_obj.selectedIndex].text; - var value = target_obj.options[target_obj.selectedIndex].value; - - for(var i=0;i=sel_obj.options.length-1) return; - - var text = sel_obj.options[idx].text; - var value = sel_obj.options[idx].value; - - sel_obj.options[idx].text = sel_obj.options[idx+1].text; - sel_obj.options[idx].value = sel_obj.options[idx+1].value; - sel_obj.options[idx+1].text = text; - sel_obj.options[idx+1].value = value; - sel_obj.selectedIndex = idx+1; -} - -function doSaveListConfig(module_srl) -{ - if(!module_srl) return; - var sel_obj = xGetElementById('displayItem'); - var idx = sel_obj.selectedIndex; - - var list = []; - for(var i=0;i=sel_obj.options.length-1) return; + + var text = sel_obj.options[idx].text; + var value = sel_obj.options[idx].value; + + sel_obj.options[idx].text = sel_obj.options[idx+1].text; + sel_obj.options[idx].value = sel_obj.options[idx+1].value; + sel_obj.options[idx+1].text = text; + sel_obj.options[idx+1].value = value; + sel_obj.selectedIndex = idx+1; +} + +function doSaveListConfig(module_srl) +{ + if(!module_srl) return; + var sel_obj = document.getElementById('displayItem'); + var idx = sel_obj.selectedIndex; + + var list = []; + for(var i=0;istatus = $will_publish; + if ($will_publish) + { + $args->old_status = array(0, 1, 2, 3, 4, 5, 6); + } + else + { + $args->old_status = array(0, 1); + } $args->comment_srls_list = $comment_srl_list; $output = executeQuery('comment.updatePublishedStatus', $args); if(!$output->toBool()) @@ -89,15 +97,16 @@ class commentAdminController extends comment $logged_info = Context::get('logged_info'); //$oMemberModule = getModel("member"); //$logged_info = $oMemberModule->getMemberInfoByMemberSrl($logged_member_srl); - $new_status = ($will_publish) ? "published" : "unpublished"; + $new_status = lang('comment.published_name_list')[$will_publish]; foreach($comment_srl_list as $comment_srl) { // check if comment already exists $comment = $oCommentModel->getComment($comment_srl); - if($comment->comment_srl != $comment_srl) + if($comment->comment_srl != $comment_srl || $comment->status != $will_publish) { - throw new Rhymix\Framework\Exceptions\InvalidRequest; + continue; } + $document_srl = $comment->document_srl; if(!in_array($document_srl, $updated_documents_arr)) { @@ -108,38 +117,31 @@ class commentAdminController extends comment $output = $oDocumentController->updateCommentCount($document_srl, $comment_count, NULL, FALSE); $oDocument = $oDocumentModel->getDocument($document_srl); - $author_email_address = $oDocument->get('email_address'); $oModuleModel = getModel("module"); $module_info = $oModuleModel->getModuleInfoByModuleSrl($comment->module_srl); - - // send email to comment's author, all admins and thread(document) subscribers - START - // ------------------------------------------------------- - $mail_title = "[Rhymix - " . $module_info->mid . "] comment(s) status changed to " . $new_status . " on document: \"" . $oDocument->getTitleText() . "\""; - $mail_content = " - The comment #" . $comment_srl . " on document \"" . $oDocument->getTitleText() . "\" has been " . $new_status . " by admin of " . strtoupper($module_info->mid) . " module. -
        -
        Comment content: - " . $comment->content . " -
        - "; - $oMail = new \Rhymix\Framework\Mail(); - $oMail->setSubject($mail_title); - $oMail->setBody($mail_content); - $oMail->setFrom(config('mail.default_from') ?: $logged_info->email_address, $logged_info->nick_name); - $oMail->setReplyTo($logged_info->email_address); - foreach (array_map('trim', explode(',', $module_info->admin_mail)) as $email_address) + if($module_info->admin_mail) { - if ($email_address && $email_address !== $author_email_address) + $mail_title = "[Rhymix - " . $module_info->mid . "] comment(s) status changed to " . $new_status . " on document: \"" . $oDocument->getTitleText() . "\""; + $mail_content = " + The comment #" . $comment_srl . " on document \"" . $oDocument->getTitleText() . "\" has been " . $new_status . " by admin of " . strtoupper($module_info->mid) . " module. +
        +
        Comment content: + " . $comment->content . " +
        + "; + $oMail = new \Rhymix\Framework\Mail(); + $oMail->setSubject($mail_title); + $oMail->setBody($mail_content); + $oMail->setFrom(config('mail.default_from') ?: $logged_info->email_address, $logged_info->nick_name); + $oMail->setReplyTo($logged_info->email_address); + foreach (array_map('trim', explode(',', $module_info->admin_mail)) as $email_address) { $oMail->addTo($email_address); } + $oMail->send(); } - $oMail->send(); - //mail to all emails set for administrators - STOP } - // ---------------------------------------------------------- - // send email to comment's author, all admins and thread(document) subscribers - STOP } // call a trigger for calling "send mail to subscribers" (for moment just for forum) ModuleHandler::triggerCall("comment.procCommentAdminChangeStatus", "after", $comment_srl_list); @@ -188,6 +190,7 @@ class commentAdminController extends comment } $oCommentController = getController('comment'); + // begin transaction $oDB = DB::getInstance(); $oDB->begin(); @@ -211,6 +214,8 @@ class commentAdminController extends comment } $deleted_count = 0; + $module_infos = []; + // Delete the comment posting for($i = 0; $i < $comment_count; $i++) { @@ -220,11 +225,53 @@ class commentAdminController extends comment continue; } - $output = $oCommentController->deleteComment($comment_srl, TRUE, toBool($isTrash)); - if(!$output->toBool()) + $comment = CommentModel::getComment($comment_srl); + if(!$comment->isExists()) { - $oDB->rollback(); - return $output; + continue; + } + + $module_srl = $comment->get('module_srl'); + if (!isset($module_infos[$module_srl])) + { + $module_infos[$module_srl] = ModuleModel::getModuleInfoByModuleSrl($module_srl)->comment_delete_message ?? ''; + } + + if($module_infos[$module_srl] === 'yes') + { + $output = $oCommentController->updateCommentByDelete($comment, true); + if(!$output->toBool() && $output->error !== -2) + { + $oDB->rollback(); + return $output; + } + } + elseif(starts_with('only_comm', $module_infos[$module_srl])) + { + $childs = CommentModel::getChildComments($comment_srl); + if(count($childs) > 0) + { + $output = $oCommentController->updateCommentByDelete($comment, true); + } + else + { + $output = $oCommentController->deleteComment($comment_srl, true, toBool($isTrash)); + } + + if(!$output->toBool() && $output->error !== -2) + { + $oDB->rollback(); + return $output; + } + } + else + { + $output = $oCommentController->deleteComment($comment_srl, TRUE, toBool($isTrash)); + if(!$output->toBool() && $output->error !== -2) + { + $oDB->rollback(); + return $output; + } } $deleted_count++; @@ -267,7 +314,7 @@ class commentAdminController extends comment for($i = 0; $i < $comment_count; $i++) { $comment_srl = $comment_srl_list[$i]; - $oComment = $oCommentModel->getComment($comment_srl, TRUE); + $oComment = $oCommentModel->getComment($comment_srl); if(!$oComment->get('member_srl') || $oComment->get('member_srl') == $sender_member_srl) { @@ -276,7 +323,7 @@ class commentAdminController extends comment $content = sprintf("
        %s

        %s
        ", $message_content, $oComment->getContentText(20)); - $oCommunicationController->sendMessage($sender_member_srl, $oComment->get('member_srl'), $title, $content, FALSE); + $oCommunicationController->sendMessage($sender_member_srl, $oComment->get('member_srl'), $title, $content, false, null, false); } } @@ -286,7 +333,7 @@ class commentAdminController extends comment */ function _moveCommentToTrash($commentSrlList, &$oCommentController, &$oDB, $message_content = NULL) { - require_once(_XE_PATH_ . 'modules/trash/model/TrashVO.php'); + require_once(RX_BASEDIR . 'modules/trash/model/TrashVO.php'); if(is_array($commentSrlList)) { @@ -299,7 +346,7 @@ class commentAdminController extends comment { $oTrashVO = new TrashVO(); $oTrashVO->setTrashSrl(getNextSequence()); - $oTrashVO->setTitle(trim(strip_tags($oComment->variables['content']))); + $oTrashVO->setTitle($oComment->getContentText(200)); $oTrashVO->setOriginModule('comment'); $oTrashVO->setSerializedObject(serialize($oComment->variables)); $oTrashVO->setDescription($message_content); @@ -310,7 +357,7 @@ class commentAdminController extends comment $oDB->rollback(); return $output; } - + $obj = new stdClass; $obj->comment_srl = $oComment->get('comment_srl'); $obj->module_srl = $oComment->get('module_srl'); @@ -337,7 +384,7 @@ class commentAdminController extends comment $comment_srl = Context::get('comment_srl'); $oCommentModel = getModel('comment'); $oCommentController = getController('comment'); - $oComment = $oCommentModel->getComment($comment_srl, false); + $oComment = $oCommentModel->getComment($comment_srl); if(!$oComment->isGranted()) throw new Rhymix\Framework\Exceptions\NotPermitted; @@ -370,6 +417,15 @@ class commentAdminController extends comment { return $output; } + + if(Context::get('prevent_redeclare') !== 'Y') + { + $output = executeQuery('comment.deleteDeclaredCommentLog', $args); + if(!$output->toBool()) + { + return $output; + } + } } } @@ -435,7 +491,7 @@ class commentAdminController extends comment { $originObject = (object) $originObject; } - + $oCommentController = getController('comment'); $oCommentModel = getModel('comment'); diff --git a/modules/comment/comment.admin.view.php b/modules/comment/comment.admin.view.php index 6eb0c68e6..c3f229ca0 100644 --- a/modules/comment/comment.admin.view.php +++ b/modules/comment/comment.admin.view.php @@ -9,7 +9,7 @@ * @package /modules/comment * @version 0.1 */ -class commentAdminView extends comment +class CommentAdminView extends Comment { /** @@ -49,11 +49,10 @@ class commentAdminView extends comment } */ - // get a list by using comment->getCommentList. + // get a list by using comment->getCommentList. $oCommentModel = getModel('comment'); $secretNameList = $oCommentModel->getSecretNameList(); - $columnList = array('comment_srl', 'document_srl','module_srl','is_secret', 'status', 'content', 'comments.member_srl', 'comments.nick_name', 'comments.regdate', 'ipaddress', 'voted_count', 'blamed_count'); - $output = $oCommentModel->getTotalCommentList($args, $columnList); + $output = $oCommentModel->getTotalCommentList($args); // $modules = $oCommentModel->getDistinctModules(); // $modules_list = $modules; @@ -68,31 +67,18 @@ class commentAdminView extends comment Context::set('secret_name_list', $secretNameList); // Module List - $oModuleModel = getModel('module'); - $module_list = array(); - $mod_srls = array(); - foreach($output->data as $val) + $module_list = []; + $mod_output = executeQueryArray('comment.getModuleList'); + foreach ($mod_output->data as $item) { - $mod_srls[] = $val->module_srl; - } - $mod_srls = array_unique($mod_srls); - $mod_srls_count = count($mod_srls); - if($mod_srls_count) - { - $columnList = array('module_srl', 'mid', 'browser_title'); - $module_output = $oModuleModel->getModulesInfo($mod_srls, $columnList); - if($module_output && is_array($module_output)) - { - foreach($module_output as $module) - { - $module_list[$module->module_srl] = $module; - } - } + $item->browser_title = Context::replaceUserLang($item->browser_title); + $module_list[$item->module_srl] = $item; } Context::set('module_list', $module_list); - + // Get anonymous nicknames $anonymous_member_srls = array(); + $member_nick_name = array(); foreach($output->data as $val) { if($val->get('member_srl') < 0) @@ -107,19 +93,19 @@ class commentAdminView extends comment $member_output = executeQueryArray('member.getMembers', $member_args); if($member_output) { - $member_nick_neme = array(); foreach($member_output->data as $member) { - $member_nick_neme[$member->member_srl] = $member->nick_name; + $member_nick_name[$member->member_srl] = $member->nick_name; } } } - Context::set('member_nick_name', $member_nick_neme); - - $security = new Security(); - $security->encodeHTML('search_target', 'search_keyword'); + Context::set('member_nick_name', $member_nick_name); - // set the template + // Other search options + Context::set('search_target', escape(Context::get('search_target'), false)); + Context::set('search_keyword', escape(Context::get('search_keyword'), false)); + + // set the template $this->setTemplatePath($this->module_path . 'tpl'); $this->setTemplateFile('comment_list'); } @@ -136,7 +122,7 @@ class commentAdminView extends comment $args->list_count = 30; // /< the number of comment postings to appear on a single page $args->page_count = 10; // /< the number of pages to appear on the page navigation $args->order_type = 'desc'; // /< sorted value - + // select sort method $sort_index = Context::get('sort_index'); if (!in_array($sort_index, array('declared_latest', 'declared_count', 'regdate'))) @@ -144,7 +130,7 @@ class commentAdminView extends comment $sort_index = 'declared_latest'; } Context::set('sort_index', $sort_index); - + // get latest declared list if ($sort_index === 'declared_latest') { @@ -185,6 +171,7 @@ class commentAdminView extends comment if ($declared_output->data && count($declared_output->data)) { $args->comment_srls = array_map(function($item) { return $item->comment_srl; }, $declared_output->data); + $args->list_count = 0; unset($args->page); $declared_latest = executeQueryArray('comment.getDeclaredLatest', $args); $comment_list = array(); foreach ($declared_output->data as $key => $comment) diff --git a/modules/comment/comment.class.php b/modules/comment/comment.class.php index 4e68b722e..883f42028 100644 --- a/modules/comment/comment.class.php +++ b/modules/comment/comment.class.php @@ -9,7 +9,7 @@ * @package /modules/comment * @version 0.1 */ -class comment extends ModuleObject +class Comment extends ModuleObject { /** @@ -19,20 +19,9 @@ class comment extends ModuleObject function moduleInstall() { $oDB = DB::getInstance(); - - // register the action forward (for using on the admin mode) - $oModuleController = getController('module'); - $oDB->addIndex( "comments", "idx_module_list_order", array("module_srl", "list_order"), TRUE ); - - // 2007. 10. 17 add a trigger to delete comments together with posting deleted - $oModuleController->insertTrigger('document.deleteDocument', 'comment', 'controller', 'triggerDeleteDocumentComments', 'after'); - // 2007. 10. 17 add a trigger to delete all of comments together with module deleted - $oModuleController->insertTrigger('module.deleteModule', 'comment', 'controller', 'triggerDeleteModuleComments', 'after'); - // 2008. 02. 22 add comment setting when a new module added - $oModuleController->insertTrigger('module.dispAdditionSetup', 'comment', 'view', 'triggerDispCommentAdditionSetup', 'before'); } /** @@ -42,60 +31,10 @@ class comment extends ModuleObject function checkUpdate() { $oDB = DB::getInstance(); - $oModuleModel = getModel('module'); - // 2007. 10. 17 add a trigger to delete comments together with posting deleted - if(!$oModuleModel->getTrigger('document.deleteDocument', 'comment', 'controller', 'triggerDeleteDocumentComments', 'after')) - { - return TRUE; - } - // 2007. 10. 17 add a trigger to delete all of comments together with module deleted - if(!$oModuleModel->getTrigger('module.deleteModule', 'comment', 'controller', 'triggerDeleteModuleComments', 'after')) - { - return TRUE; - } - // 2007. 10. 23 add a column for recommendation votes or notification of the comments - if(!$oDB->isColumnExists("comments", "voted_count")) - { - return TRUE; - } - if(!$oDB->isColumnExists("comments", "notify_message")) - { - return TRUE; - } - // 2008. 02. 22 add comment setting when a new module added - if(!$oModuleModel->getTrigger('module.dispAdditionSetup', 'comment', 'view', 'triggerDispCommentAdditionSetup', 'before')) - { - return TRUE; - } - // 2008. 05. 14 add a column for blamed count - if(!$oDB->isColumnExists("comments", "blamed_count")) - { - return TRUE; - } - if(!$oDB->isColumnExists("comment_voted_log", "point")) - { - return TRUE; - } - if(!$oDB->isIndexExists("comments", "idx_module_list_order")) { return TRUE; } - //2012. 02. 24 add comment published status column and index - if(!$oDB->isColumnExists("comments", "status")) - { - return TRUE; - } - if(!$oDB->isIndexExists("comments", "idx_status")) - { - return TRUE; - } - - // 2012. 08. 29 Add a trigger to copy additional setting when the module is copied - if(!$oModuleModel->getTrigger('module.procModuleAdminCopyModule', 'comment', 'controller', 'triggerCopyModule', 'after')) - { - return TRUE; - } // 2016. 1. 29: Add a column(declare_message) for report if(!$oDB->isColumnExists("comment_declared_log","declare_message")) @@ -113,17 +52,8 @@ class comment extends ModuleObject { return true; } - - if(!$oModuleModel->getTrigger('document.moveDocumentModule', 'comment', 'controller', 'triggerMoveDocument', 'after')) - { - return true; - } - if(!$oModuleModel->getTrigger('document.copyDocumentModule', 'comment', 'controller', 'triggerAddCopyDocument', 'add')) - { - return true; - } - - return FALSE; + + return false; } /** @@ -133,68 +63,9 @@ class comment extends ModuleObject function moduleUpdate() { $oDB = DB::getInstance(); - $oModuleModel = getModel('module'); - $oModuleController = getController('module'); - // 2007. 10. 17 add a trigger to delete comments together with posting deleted - if(!$oModuleModel->getTrigger('document.deleteDocument', 'comment', 'controller', 'triggerDeleteDocumentComments', 'after')) - { - $oModuleController->insertTrigger('document.deleteDocument', 'comment', 'controller', 'triggerDeleteDocumentComments', 'after'); - } - // 2007. 10. 17 add a trigger to delete all of comments together with module deleted - if(!$oModuleModel->getTrigger('module.deleteModule', 'comment', 'controller', 'triggerDeleteModuleComments', 'after')) - { - $oModuleController->insertTrigger('module.deleteModule', 'comment', 'controller', 'triggerDeleteModuleComments', 'after'); - } - // 2007. 10. 23 add a column for recommendation votes or notification of the comments - if(!$oDB->isColumnExists("comments", "voted_count")) - { - $oDB->addColumn("comments", "voted_count", "number", "11"); - $oDB->addIndex("comments", "idx_voted_count", array("voted_count")); - } - - if(!$oDB->isColumnExists("comments", "notify_message")) - { - $oDB->addColumn("comments", "notify_message", "char", "1"); - } - // 2008. 02. 22 add comment setting when a new module added - if(!$oModuleModel->getTrigger('module.dispAdditionSetup', 'comment', 'view', 'triggerDispCommentAdditionSetup', 'before')) - { - $oModuleController->insertTrigger('module.dispAdditionSetup', 'comment', 'view', 'triggerDispCommentAdditionSetup', 'before'); - } - // 2008. 05. 14 add a column for blamed count - if(!$oDB->isColumnExists("comments", "blamed_count")) - { - $oDB->addColumn('comments', 'blamed_count', 'number', 11, 0, TRUE); - $oDB->addIndex('comments', 'idx_blamed_count', array('blamed_count')); - } - if(!$oDB->isColumnExists("comment_voted_log", "point")) - { - $oDB->addColumn('comment_voted_log', 'point', 'number', 11, 0, TRUE); - } - if(!$oDB->isIndexExists("comments", "idx_module_list_order")) { - $oDB->addIndex( - "comments", "idx_module_list_order", array("module_srl", "list_order"), TRUE - ); - } - - //2012. 02. 24 add comment published status column and index - if(!$oDB->isColumnExists("comments", "status")) - { - $oDB->addColumn("comments", "status", "number", 1, 1, TRUE); - } - if(!$oDB->isIndexExists("comments", "idx_status")) - { - $oDB->addIndex( - "comments", "idx_status", array("status", "comment_srl", "module_srl", "document_srl"), TRUE - ); - } - - // 2012. 08. 29 Add a trigger to copy additional setting when the module is copied - if(!$oModuleModel->getTrigger('module.procModuleAdminCopyModule', 'comment', 'controller', 'triggerCopyModule', 'after')) - { - $oModuleController->insertTrigger('module.procModuleAdminCopyModule', 'comment', 'controller', 'triggerCopyModule', 'after'); + $oDB->addIndex("comments", "idx_module_list_order", array("module_srl", "list_order"), TRUE); } // 2016. 1. 29: Add a column(declare_message) for report @@ -207,32 +78,13 @@ class comment extends ModuleObject { $oDB->addIndex('comments', 'idx_parent_srl', array('parent_srl')); } - + // 2017.12.21 Add an index for nick_name if(!$oDB->isIndexExists('comments', 'idx_nick_name')) { $oDB->addIndex('comments', 'idx_nick_name', array('nick_name')); } - - if(!$oModuleModel->getTrigger('document.moveDocumentModule', 'comment', 'controller', 'triggerMoveDocument', 'after')) - { - $oModuleController->insertTrigger('document.moveDocumentModule', 'comment', 'controller', 'triggerMoveDocument', 'after'); - } - if(!$oModuleModel->getTrigger('document.copyDocumentModule', 'comment', 'controller', 'triggerAddCopyDocument', 'add')) - { - $oModuleController->insertTrigger('document.copyDocumentModule', 'comment', 'controller', 'triggerAddCopyDocument', 'add'); - } } - - /** - * Regenerate cache file - * @return void - */ - function recompileCache() - { - - } - } /* End of file comment.class.php */ /* Location: ./modules/comment/comment.class.php */ diff --git a/modules/comment/comment.controller.php b/modules/comment/comment.controller.php index 2dc2fb651..31a42fba7 100644 --- a/modules/comment/comment.controller.php +++ b/modules/comment/comment.controller.php @@ -9,84 +9,128 @@ * @package /modules/comment * @version 0.1 */ -class commentController extends comment +class CommentController extends Comment { - - /** - * Initialization - * @return void - */ - function init() - { - - } - /** * Action to handle recommendation votes on comments (Up) * @return Object */ function procCommentVoteUp() { - if($this->module_info->non_login_vote !== 'Y') - { - if(!Context::get('is_logged')) - { - throw new Rhymix\Framework\Exceptions\NotPermitted; - } - } - $comment_srl = Context::get('target_srl'); if(!$comment_srl) { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - $oCommentModel = getModel('comment'); - $oComment = $oCommentModel->getComment($comment_srl, FALSE, FALSE); - $module_srl = $oComment->get('module_srl'); - if(!$module_srl) + // Check target comment. + $oComment = CommentModel::getComment($comment_srl); + if(!$oComment->isExists()) { - throw new Rhymix\Framework\Exceptions\InvalidRequest; + throw new Rhymix\Framework\Exceptions\TargetNotFound; + } + if(!$oComment->isAccessible(true)) + { + throw new Rhymix\Framework\Exceptions\NotPermitted; } - $oModuleModel = getModel('module'); - $comment_config = $oModuleModel->getModulePartConfig('comment', $module_srl); - if($comment_config->use_vote_up == 'N') + // Check if voting is enabled. + $comment_config = ModuleModel::getModulePartConfig('comment', $oComment->get('module_srl')); + if($comment_config->use_vote_up === 'N') { throw new Rhymix\Framework\Exceptions\FeatureDisabled; } + if(!Context::get('is_logged')) + { + if (isset($comment_config->allow_vote_non_member)) + { + if ($comment_config->allow_vote_non_member !== 'Y') + { + throw new Rhymix\Framework\Exceptions\MustLogin; + } + } + else + { + $module_info = $this->module_info ?: ModuleModel::getModuleInfoByModuleSrl($oComment->get('module_srl')); + if (($module_info->non_login_vote ?? 'N') !== 'Y') + { + throw new Rhymix\Framework\Exceptions\MustLogin; + } + } + } $point = 1; - $output = $this->updateVotedCount($comment_srl, $point); + $allow_same_ip = ($comment_config->allow_vote_from_same_ip ?? 'N') === 'Y'; + $output = $this->updateVotedCount($comment_srl, $point, $allow_same_ip); $this->add('voted_count', $output->get('voted_count')); return $output; } function procCommentVoteUpCancel() { - if($this->module_info->non_login_vote !== 'Y') + $comment_srl = Context::get('target_srl'); + if(!$comment_srl) { - if(!Context::get('is_logged')) - { - throw new Rhymix\Framework\Exceptions\NotPermitted; - } + throw new Rhymix\Framework\Exceptions\InvalidRequest; } - $comment_srl = Context::get('target_srl'); - if(!$comment_srl) throw new Rhymix\Framework\Exceptions\InvalidRequest; - - $oCommentModel = getModel('comment'); - $oComment = $oCommentModel->getComment($comment_srl, FALSE, FALSE); + // Check target comment. + $oComment = CommentModel::getComment($comment_srl); + if(!$oComment->isExists()) + { + throw new Rhymix\Framework\Exceptions\TargetNotFound; + } + if(!$oComment->isAccessible(true)) + { + throw new Rhymix\Framework\Exceptions\NotPermitted; + } if($oComment->get('voted_count') <= 0) { throw new Rhymix\Framework\Exception('failed_voted_canceled'); } + + // Check if voting and canceling are enabled. + $comment_config = ModuleModel::getModulePartConfig('comment', $oComment->get('module_srl')); + $module_info = $this->module_info ?: ModuleModel::getModuleInfoByModuleSrl($oComment->get('module_srl')); + if (isset($comment_config->allow_vote_cancel)) + { + if ($comment_config->allow_vote_cancel !== 'Y') + { + throw new Rhymix\Framework\Exceptions\FeatureDisabled; + } + } + else + { + if (($module_info->cancel_vote ?? 'N') !== 'Y') + { + throw new Rhymix\Framework\Exceptions\FeatureDisabled; + } + } + if(!Context::get('is_logged')) + { + if (isset($comment_config->allow_vote_non_member)) + { + if ($comment_config->allow_vote_non_member !== 'Y') + { + throw new Rhymix\Framework\Exceptions\MustLogin; + } + } + else + { + if (($module_info->non_login_vote ?? 'N') !== 'Y') + { + throw new Rhymix\Framework\Exceptions\MustLogin; + } + } + } + $point = 1; $output = $this->updateVotedCountCancel($comment_srl, $oComment, $point); - - $output = new BaseObject(); - $output->setMessage('success_voted_canceled'); - + if(!$output->toBool()) + { + return $output; + } + $this->add('voted_count', $output->get('voted_count')); return $output; } @@ -97,84 +141,143 @@ class commentController extends comment */ function procCommentVoteDown() { - if($this->module_info->non_login_vote !== 'Y') - { - if(!Context::get('is_logged')) - { - throw new Rhymix\Framework\Exceptions\NotPermitted; - } - } - $comment_srl = Context::get('target_srl'); if(!$comment_srl) { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - $oCommentModel = getModel('comment'); - $oComment = $oCommentModel->getComment($comment_srl, FALSE, FALSE); - $module_srl = $oComment->get('module_srl'); - if(!$module_srl) + // Check target comment. + $oComment = CommentModel::getComment($comment_srl); + if(!$oComment->isExists()) { - throw new Rhymix\Framework\Exceptions\InvalidRequest; + throw new Rhymix\Framework\Exceptions\TargetNotFound; + } + if(!$oComment->isAccessible(true)) + { + throw new Rhymix\Framework\Exceptions\NotPermitted; } - $oModuleModel = getModel('module'); - $comment_config = $oModuleModel->getModulePartConfig('comment', $module_srl); - if($comment_config->use_vote_down == 'N') + // Check if voting is enabled. + $comment_config = ModuleModel::getModulePartConfig('comment', $oComment->get('module_srl')); + if($comment_config->use_vote_down === 'N') { throw new Rhymix\Framework\Exceptions\FeatureDisabled; } + if(!Context::get('is_logged')) + { + if (isset($comment_config->allow_vote_non_member)) + { + if ($comment_config->allow_vote_non_member !== 'Y') + { + throw new Rhymix\Framework\Exceptions\MustLogin; + } + } + else + { + $module_info = $this->module_info ?: ModuleModel::getModuleInfoByModuleSrl($oComment->get('module_srl')); + if (($module_info->non_login_vote ?? 'N') !== 'Y') + { + throw new Rhymix\Framework\Exceptions\MustLogin; + } + } + } $point = -1; - $output = $this->updateVotedCount($comment_srl, $point); + $allow_same_ip = ($comment_config->allow_vote_from_same_ip ?? 'N') === 'Y'; + $output = $this->updateVotedCount($comment_srl, $point, $allow_same_ip); $this->add('blamed_count', $output->get('blamed_count')); return $output; } function procCommentVoteDownCancel() { - if($this->module_info->non_login_vote !== 'Y') + $comment_srl = Context::get('target_srl'); + if(!$comment_srl) { - if(!Context::get('is_logged')) - { - throw new Rhymix\Framework\Exceptions\NotPermitted; - } + throw new Rhymix\Framework\Exceptions\InvalidRequest; } - $comment_srl = Context::get('target_srl'); - if(!$comment_srl) throw new Rhymix\Framework\Exceptions\InvalidRequest; - - $oCommentModel = getModel('comment'); - $oComment = $oCommentModel->getComment($comment_srl, FALSE, FALSE); + // Check target comment. + $oComment = CommentModel::getComment($comment_srl); + if(!$oComment->isExists()) + { + throw new Rhymix\Framework\Exceptions\TargetNotFound; + } + if(!$oComment->isAccessible(true)) + { + throw new Rhymix\Framework\Exceptions\NotPermitted; + } if($oComment->get('blamed_count') >= 0) { throw new Rhymix\Framework\Exception('failed_blamed_canceled'); } + + // Check if voting and canceling are enabled. + $comment_config = ModuleModel::getModulePartConfig('comment', $oComment->get('module_srl')); + $module_info = $this->module_info ?: ModuleModel::getModuleInfoByModuleSrl($oComment->get('module_srl')); + if (isset($comment_config->allow_vote_cancel)) + { + if ($comment_config->allow_vote_cancel !== 'Y') + { + throw new Rhymix\Framework\Exceptions\FeatureDisabled; + } + } + else + { + if (($module_info->cancel_vote ?? 'N') !== 'Y') + { + throw new Rhymix\Framework\Exceptions\FeatureDisabled; + } + } + if(!Context::get('is_logged')) + { + if (isset($comment_config->allow_vote_non_member)) + { + if ($comment_config->allow_vote_non_member !== 'Y') + { + throw new Rhymix\Framework\Exceptions\MustLogin; + } + } + else + { + if (($module_info->non_login_vote ?? 'N') !== 'Y') + { + throw new Rhymix\Framework\Exceptions\MustLogin; + } + } + } + $point = -1; $output = $this->updateVotedCountCancel($comment_srl, $oComment, $point); - - $output = new BaseObject(); - $output->setMessage('success_blamed_canceled'); - + if(!$output->toBool()) + { + return $output; + } + $this->add('blamed_count', $output->get('blamed_count')); return $output; } function updateVotedCountCancel($comment_srl, $oComment, $point) { - $logged_info = Context::get('logged_info'); - + // Guests can only cancel votes that are registered in the current session. + if(!$this->user->member_srl && empty($_SESSION['voted_comment'][$comment_srl])) + { + return new BaseObject(-1, $point > 0 ? 'failed_voted_canceled' : 'failed_blamed_canceled'); + } + // Check if the current user has voted previously. $args = new stdClass; $args->comment_srl = $comment_srl; $args->point = $point; - if($logged_info->member_srl) + if($this->user->member_srl) { - $args->member_srl = $logged_info->member_srl; + $args->member_srl = $this->user->member_srl; } else { - $args->ipaddress = $_SERVER['REMOTE_ADDR']; + $args->member_srl = 0; + $args->ipaddress = \RX_CLIENT_IP; } $output = executeQuery('comment.getCommentVotedLogInfo', $args); if(!$output->data->count) @@ -206,7 +309,7 @@ class commentController extends comment $args = new stdClass(); $d_args = new stdClass(); $args->comment_srl = $d_args->comment_srl = $comment_srl; - $d_args->member_srl = $logged_info->member_srl; + $d_args->member_srl = $this->user->member_srl; if ($trigger_obj->update_target === 'voted_count') { $args->voted_count = $trigger_obj->after_point; @@ -221,12 +324,25 @@ class commentController extends comment if(!$d_output->toBool()) return $d_output; //session reset - $_SESSION['voted_comment'][$comment_srl] = false; - + unset($_SESSION['voted_comment'][$comment_srl]); + // Call a trigger (after) ModuleHandler::triggerCall('comment.updateVotedCountCancel', 'after', $trigger_obj); - + $oDB->commit(); + + // Return result + $output = new BaseObject(); + if($trigger_obj->update_target === 'voted_count') + { + $output->setMessage('success_voted_canceled'); + $output->add('voted_count', $trigger_obj->after_point); + } + else + { + $output->setMessage('success_blamed_canceled'); + $output->add('blamed_count', $trigger_obj->after_point); + } return $output; } @@ -246,6 +362,15 @@ class commentController extends comment { throw new Rhymix\Framework\Exceptions\InvalidRequest; } + $oComment = CommentModel::getComment($comment_srl); + if(!$oComment->isExists()) + { + throw new Rhymix\Framework\Exceptions\TargetNotFound; + } + if(!$oComment->isAccessible(true)) + { + throw new Rhymix\Framework\Exceptions\NotPermitted; + } // if an user select message from options, message would be the option. $message_option = strval(Context::get('message_option')); @@ -262,6 +387,59 @@ class commentController extends comment return $this->declaredComment($comment_srl, $declare_message); } + /** + * Action to be called when cancel reporting a comment + * @return void|Object + */ + function procCommentDeclareCancel() + { + if (!Context::get('is_logged')) + { + throw new Rhymix\Framework\Exceptions\MustLogin; + } + + // Check if the comment exists and is accessible to the current user. + $comment_srl = Context::get('target_srl'); + if (!$comment_srl) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + $oComment = CommentModel::getComment($comment_srl); + if (!$oComment->isExists()) + { + throw new Rhymix\Framework\Exceptions\TargetNotFound; + } + if (!$oComment->isAccessible(true)) + { + throw new Rhymix\Framework\Exceptions\NotPermitted; + } + + // Check if canceling is allowed. + $comment_config = ModuleModel::getModulePartConfig('comment', $oComment->get('module_srl')); + if (isset($comment_config->allow_declare_cancel)) + { + if ($comment_config->allow_declare_cancel !== 'Y') + { + throw new Rhymix\Framework\Exception('failed_declared_cancel'); + } + } + else + { + $module_info = ModuleModel::getModuleInfoByModuleSrl($oComment->get('module_srl')); + if (($module_info->cancel_vote ?? 'N') !== 'Y') + { + throw new Rhymix\Framework\Exception('failed_declared_cancel'); + } + } + + if (Context::get('success_return_url')) + { + $this->setRedirectUrl(Context::get('success_return_url')); + } + + return $this->declaredCommentCancel($comment_srl); + } + /** * Trigger to delete its comments together with document deleted * @return Object @@ -289,8 +467,8 @@ class commentController extends comment return; } - $oCommentController = getAdminController('comment'); - return $oCommentController->deleteModuleComments($module_srl); + $oCommentAdminController = CommentAdminController::getInstance(); + return $oCommentAdminController->deleteModuleComments($module_srl); } /** @@ -300,7 +478,7 @@ class commentController extends comment */ function addGrant($comment_srl) { - $comment = getModel('comment')->getComment($comment_srl); + $comment = CommentModel::getComment($comment_srl); if ($comment->isExists()) { $comment->setGrant(); @@ -315,14 +493,13 @@ class commentController extends comment */ function isModuleUsingPublishValidation($module_srl = NULL) { - if($module_srl == NULL) + if($module_srl == NULL || is_array($module_srl)) { return FALSE; } - $oModuleModel = getModel('module'); - $module_info = $oModuleModel->getModuleInfoByModuleSrl($module_srl); - $module_part_config = $oModuleModel->getModulePartConfig('comment', $module_info->module_srl); + $module_info = ModuleModel::getModuleInfoByModuleSrl($module_srl); + $module_part_config = ModuleModel::getModulePartConfig('comment', $module_info->module_srl); $use_validation = FALSE; if(isset($module_part_config->use_comment_validation) && $module_part_config->use_comment_validation == "Y") { @@ -340,7 +517,7 @@ class commentController extends comment */ function insertComment($obj, $manual_inserted = FALSE, $update_document = TRUE) { - if(!$manual_inserted && !checkCSRF()) + if(!$manual_inserted && !Rhymix\Framework\Security::checkCSRF()) { return new BaseObject(-1, 'msg_security_violation'); } @@ -390,15 +567,39 @@ class commentController extends comment } $obj->__isupdate = FALSE; - // Remove manual member info to prevent forgery. This variable can be set by triggers only. - unset($obj->manual_member_info); - // Sanitize variables $obj->comment_srl = intval($obj->comment_srl); $obj->module_srl = intval($obj->module_srl); $obj->document_srl = intval($obj->document_srl); - $obj->parent_srl = intval($obj->parent_srl); - + $obj->parent_srl = intval($obj->parent_srl ?? 0); + + // Only managers can customize dates. + $grant = Context::get('grant'); + if(!$grant->manager) + { + unset($obj->regdate); + unset($obj->last_update); + } + + // Add the current user's info, unless it is a guest post. + $logged_info = Context::get('logged_info'); + if($logged_info->member_srl && !$manual_inserted) + { + $obj->member_srl = $logged_info->member_srl; + $obj->user_id = htmlspecialchars_decode($logged_info->user_id); + $obj->user_name = htmlspecialchars_decode($logged_info->user_name); + $obj->nick_name = htmlspecialchars_decode($logged_info->nick_name); + $obj->email_address = $logged_info->email_address; + $obj->homepage = $logged_info->homepage; + } + if(!$logged_info->member_srl && !$manual_inserted) + { + unset($obj->member_srl); + unset($obj->user_id); + } + + $obj->uploaded_count = FileModel::getFilesCount($obj->comment_srl); + // call a trigger (before) $output = ModuleHandler::triggerCall('comment.insertComment', 'before', $obj); if(!$output->toBool()) @@ -412,23 +613,17 @@ class commentController extends comment { return new BaseObject(-1, 'msg_invalid_document'); } - - // creat the comment model object - $oCommentModel = getModel('comment'); - // get a object of document model - $oDocumentModel = getModel('document'); // even for manual_inserted if password exists, hash it. - if($obj->password) + if(!empty($obj->password)) { - $obj->password = getModel('member')->hashPassword($obj->password); + $obj->password = \Rhymix\Framework\Password::hashPassword($obj->password, \Rhymix\Framework\Password::getBackwardCompatibleAlgorithm()); } // get the original posting if(!$manual_inserted) { - $oDocument = $oDocumentModel->getDocument($document_srl); - + $oDocument = DocumentModel::getDocument($document_srl); if($document_srl != $oDocument->document_srl) { return new BaseObject(-1, 'msg_invalid_document'); @@ -437,29 +632,6 @@ class commentController extends comment { return new BaseObject(-1, 'msg_invalid_request'); } - - if($obj->homepage) - { - $obj->homepage = escape($obj->homepage); - if(!preg_match('/^[a-z]+:\/\//i',$obj->homepage)) - { - $obj->homepage = 'http://'.$obj->homepage; - } - } - - // input the member's information if logged-in - $logged_info = Context::get('logged_info'); - if(Context::get('is_logged') && !$obj->manual_member_info) - { - $obj->member_srl = $logged_info->member_srl; - - // user_id, user_name and nick_name already encoded - $obj->user_id = htmlspecialchars_decode($logged_info->user_id); - $obj->user_name = htmlspecialchars_decode($logged_info->user_name); - $obj->nick_name = htmlspecialchars_decode($logged_info->nick_name); - $obj->email_address = $logged_info->email_address; - $obj->homepage = $logged_info->homepage; - } } // error display if neither of log-in info and user name exist. @@ -468,15 +640,30 @@ class commentController extends comment return new BaseObject(-1, 'msg_invalid_request'); } + // Clean up the homepage link, if any + if($obj->homepage) + { + $obj->homepage = escape($obj->homepage); + if(!preg_match('/^[a-z]+:\/\//i',$obj->homepage)) + { + $obj->homepage = 'http://'.$obj->homepage; + } + } + if(!$obj->comment_srl) { $obj->comment_srl = getNextSequence(); } - elseif(!$is_admin && !$manual_inserted && !checkUserSequence($obj->comment_srl)) + elseif(!$is_admin && !$manual_inserted && !checkUserSequence($obj->comment_srl)) { return new BaseObject(-1, 'msg_not_permitted'); } + if (empty($obj->regdate)) + { + $obj->regdate = date('YmdHis'); + } + // determine the order $obj->list_order = getNextSequence() * -1; @@ -488,31 +675,29 @@ class commentController extends comment { return new BaseObject(-1, 'msg_empty_content'); } - + // if use editor of nohtml, Remove HTML tags from the contents. - if(!$manual_inserted || isset($obj->allow_html) || isset($obj->use_html)) + if (!$manual_inserted || isset($obj->allow_html) || isset($obj->use_html)) { - $obj->content = getModel('editor')->converter($obj, 'comment'); - } - - if(!$obj->regdate) - { - $obj->regdate = date("YmdHis"); + $obj->content = EditorModel::converter($obj, 'comment'); } // remove iframe and script if not a top administrator on the session. - if($logged_info->is_admin != 'Y') + if ($logged_info->is_admin !== 'Y') { - $obj->content = removeHackTag($obj->content); + $obj->content = Rhymix\Framework\Filters\HTMLFilter::clean((string)$obj->content); + } + if (config('db.master.charset') !== 'utf8mb4') + { + $obj->content = utf8_mbencode($obj->content); } - $obj->content = utf8_mbencode($obj->content); - if(!$obj->notify_message) + // Set other flags. + if (isset($obj->notify_message) && $obj->notify_message !== 'Y') { $obj->notify_message = 'N'; } - - if(!$obj->is_secret) + if (isset($obj->is_secret) && $obj->is_secret !== 'Y') { $obj->is_secret = 'N'; } @@ -553,6 +738,17 @@ class commentController extends comment $list_args->head = $parent->head; $list_args->depth = $parent->depth + 1; + // Check max thread depth. + $comment_config = ModuleModel::getModulePartConfig('comment', $obj->module_srl); + if (isset($comment_config->max_thread_depth) && $comment_config->max_thread_depth > 0) + { + if ($list_args->depth + 1 > $comment_config->max_thread_depth) + { + $oDB->rollback(); + return new BaseObject(-1, 'msg_exceeds_max_thread_depth'); + } + } + // if the depth of comments is less than 2, execute insert. if($list_args->depth < 2) { @@ -594,17 +790,30 @@ class commentController extends comment $oDB->rollback(); return $output; } - - // create the controller object of the document - $oDocumentController = getController('document'); // Update the number of comments in the post - $comment_count = $oCommentModel->getCommentCount($document_srl); + $oDocumentController = DocumentController::getInstance(); + $comment_count = CommentModel::getCommentCount($document_srl); if($comment_count && (!$using_validation || $is_admin)) { $output = $oDocumentController->updateCommentCount($document_srl, $comment_count, $obj->nick_name, $update_document); } + if($obj->uploaded_count > 0) + { + $attachOutput = FileController::getInstance()->setFilesValid($obj->comment_srl, 'com'); + if(!$attachOutput->toBool()) + { + $oDB->rollback(); + return $attachOutput; + } + $obj->updated_file_count = $attachOutput->get('updated_file_count'); + } + else + { + $obj->updated_file_count = 0; + } + // call a trigger(after) ModuleHandler::triggerCall('comment.insertComment', 'after', $obj); @@ -625,7 +834,7 @@ class commentController extends comment // send a message if notify_message option in enabled in the original comment if($obj->parent_srl) { - $oParent = $oCommentModel->getComment($obj->parent_srl); + $oParent = CommentModel::getComment($obj->parent_srl); if($oParent->get('member_srl') != $oDocument->get('member_srl')) { $oParent->notify(lang('comment'), $obj->content); @@ -633,7 +842,10 @@ class commentController extends comment } } - $this->sendEmailToAdminAfterInsertComment($obj); + if (config('mail.default_from')) + { + $this->sendEmailToAdminAfterInsertComment($obj); + } $output->add('comment_srl', $obj->comment_srl); @@ -642,23 +854,18 @@ class commentController extends comment /** * Send email to module's admins after a new comment was interted successfully - * if Comments Approval System is used - * @param object $obj + * if Comments Approval System is used + * @param object $obj * @return void */ function sendEmailToAdminAfterInsertComment($obj) { $using_validation = $this->isModuleUsingPublishValidation($obj->module_srl); - $oDocumentModel = getModel('document'); - $oDocument = $oDocumentModel->getDocument($obj->document_srl); - $author_email_address = $oDocument->get('email_address'); - - $oMemberModel = getModel("member"); - $is_logged = Context::get('is_logged'); + $oDocument = DocumentModel::getDocument($obj->document_srl); if(isset($obj->member_srl) && !is_null($obj->member_srl)) { - $member_info = $oMemberModel->getMemberInfoByMemberSrl($obj->member_srl); + $member_info = MemberModel::getMemberInfoByMemberSrl($obj->member_srl); } else { @@ -669,18 +876,17 @@ class commentController extends comment $member_info->email_address = $obj->email_address; } - $oCommentModel = getModel("comment"); - $oModuleModel = getModel("module"); - $module_info = $oModuleModel->getModuleInfoByDocumentSrl($obj->document_srl); + $module_info = ModuleModel::getModuleInfoByDocumentSrl($obj->document_srl); // If there is no problem to register comment then send an email to all admin were set in module admin panel - if($module_info->admin_mail && $member_info->is_admin != 'Y') + if(isset($module_info->admin_mail) && $module_info->admin_mail && $member_info->is_admin != 'Y') { - $mail_title = sprintf(lang('msg_comment_notify_mail'), Context::get('mid'), cut_str($oDocument->getTitleText(), 20, '...')); + $browser_title = Context::replaceUserLang($module_info->browser_title); + $mail_title = sprintf(lang('msg_comment_notify_mail'), $browser_title, cut_str($oDocument->getTitleText(), 20, '...')); $url_comment = getFullUrl('','document_srl',$obj->document_srl).'#comment_'.$obj->comment_srl; if($using_validation) { - $nr_comments_not_approved = $oCommentModel->getCommentAllCount(NULL, FALSE); + $nr_comments_not_approved = CommentModel::getCommentAllCount(NULL, FALSE); $url_approve = getFullUrl('', 'module', 'admin', 'act', 'procCommentAdminChangePublishedStatusChecked', 'cart[]', $obj->comment_srl, 'will_publish', '1', 'search_target', 'is_published', 'search_keyword', 'N'); $url_trash = getFullUrl('', 'module', 'admin', 'act', 'procCommentAdminDeleteChecked', 'cart[]', $obj->comment_srl, 'search_target', 'is_trash', 'search_keyword', 'true'); $mail_content = " @@ -690,10 +896,13 @@ class commentController extends comment Author: " . $member_info->nick_name . "
        Author e-mail: " . $member_info->email_address . "
        From : " . $url_comment . " +

        Comment:
        \"" . $obj->content . "\" +

        Document: -
        \"" . $oDocument->getContentText(). "\" +
        \"" . $oDocument->getTitleText(). "\" +
        \"" . $oDocument->getContentPlainText(). "\"

        Approve it: " . $url_approve . " @@ -708,10 +917,13 @@ class commentController extends comment Author: " . $member_info->nick_name . "
        Author e-mail: " . $member_info->email_address . "
        From : " . $url_comment . " +

        Comment:
        \"" . $obj->content . "\" +

        Document: -
        \"" . $oDocument->getContentText(). "\" +
        \"" . $oDocument->getTitleText(). "\" +
        \"" . $oDocument->getContentPlainText(). "\" "; } @@ -719,17 +931,11 @@ class commentController extends comment $oMail = new \Rhymix\Framework\Mail(); $oMail->setSubject($mail_title); $oMail->setBody($mail_content); - $oMail->setFrom(config('mail.default_from') ?: $member_info->email_address, $member_info->nick_name); - $oMail->setReplyTo($member_info->email_address); foreach (array_map('trim', explode(',', $module_info->admin_mail)) as $email_address) { - if ($email_address && $email_address !== $author_email_address) - { - $oMail->addTo($email_address); - } + $oMail->addTo($email_address); } $oMail->send(); - // send email to all admins - STOP } $comment_srl_list = array(0 => $obj->comment_srl); @@ -742,13 +948,13 @@ class commentController extends comment /** * Fix the comment * @param object $obj - * @param bool $is_admin + * @param bool $skip_grant_check * @param bool $manual_updated * @return object */ - function updateComment($obj, $is_admin = FALSE, $manual_updated = FALSE) + function updateComment($obj, $skip_grant_check = FALSE, $manual_updated = FALSE) { - if(!$manual_updated && !checkCSRF()) + if(!$manual_updated && !Rhymix\Framework\Security::checkCSRF()) { return new BaseObject(-1, 'msg_security_violation'); } @@ -760,15 +966,37 @@ class commentController extends comment $obj->__isupdate = TRUE; - // Remove manual member info to prevent forgery. This variable can be set by triggers only. - unset($obj->manual_member_info); - // Sanitize variables $obj->comment_srl = intval($obj->comment_srl); $obj->module_srl = intval($obj->module_srl); $obj->document_srl = intval($obj->document_srl); $obj->parent_srl = intval($obj->parent_srl); - + + // Preserve original author info. + $source_obj = CommentModel::getComment($obj->comment_srl); + if ($source_obj->get('member_srl')) + { + $obj->member_srl = $source_obj->get('member_srl'); + $obj->user_id = $source_obj->get('user_id'); + $obj->user_name = $source_obj->get('user_name'); + $obj->nick_name = $source_obj->get('nick_name'); + $obj->email_address = $source_obj->get('email_address'); + $obj->homepage = $source_obj->get('homepage'); + } + else + { + unset($obj->member_srl); + unset($obj->user_id); + unset($obj->user_name); + $obj->nick_name = $obj->nick_name ?? $source_obj->get('nick_name'); + $obj->email_address = $obj->email_address ?? $source_obj->get('email_address'); + $obj->homepage = $obj->homepage ?? $source_obj->get('homepage'); + } + + if(($obj->is_secret ?? 'N') !== 'Y') $obj->is_secret = 'N'; + if(($obj->notify_message ?? 'N') !== 'Y') $obj->notify_message = 'N'; + $obj->uploaded_count = FileModel::getFilesCount($obj->comment_srl); + // call a trigger (before) $output = ModuleHandler::triggerCall('comment.updateComment', 'before', $obj); if(!$output->toBool()) @@ -776,32 +1004,18 @@ class commentController extends comment return $output; } - // create a comment model object - $oCommentModel = getModel('comment'); - - // get the original data - $source_obj = $oCommentModel->getComment($obj->comment_srl); - if(!$source_obj->getMemberSrl()) - { - $obj->member_srl = $source_obj->get('member_srl'); - $obj->user_name = $source_obj->get('user_name'); - $obj->nick_name = $source_obj->get('nick_name'); - $obj->email_address = $source_obj->get('email_address'); - $obj->homepage = $source_obj->get('homepage'); - } - // check if permission is granted - if(!$is_admin && !$source_obj->isGranted()) + if(!$skip_grant_check && !$source_obj->isGranted()) { return new BaseObject(-1, 'msg_not_permitted'); } - if($obj->password) + if(!empty($obj->password)) { - $obj->password = getModel('member')->hashPassword($obj->password); + $obj->password = \Rhymix\Framework\Password::hashPassword($obj->password, \Rhymix\Framework\Password::getBackwardCompatibleAlgorithm()); } - if($obj->homepage) + if(!empty($obj->homepage)) { $obj->homepage = escape($obj->homepage); if(!preg_match('/^[a-z]+:\/\//i',$obj->homepage)) @@ -810,31 +1024,7 @@ class commentController extends comment } } - // set modifier's information if logged-in and posting author and modifier are matched. - $logged_info = Context::get('logged_info'); - if(Context::get('is_logged') && !$obj->manual_member_info) - { - if($source_obj->member_srl == $logged_info->member_srl) - { - $obj->member_srl = $logged_info->member_srl; - $obj->user_name = $logged_info->user_name; - $obj->nick_name = $logged_info->nick_name; - $obj->email_address = $logged_info->email_address; - $obj->homepage = $logged_info->homepage; - } - } - - // if nick_name of the logged-in author doesn't exist - if($source_obj->get('member_srl') && !$obj->nick_name && !$obj->manual_member_info) - { - $obj->member_srl = $source_obj->get('member_srl'); - $obj->user_name = $source_obj->get('user_name'); - $obj->nick_name = $source_obj->get('nick_name'); - $obj->email_address = $source_obj->get('email_address'); - $obj->homepage = $source_obj->get('homepage'); - } - - if(!$obj->content) + if(!isset($obj->content) || !$obj->content) { $obj->content = $source_obj->get('content'); } @@ -846,19 +1036,23 @@ class commentController extends comment { return new BaseObject(-1, 'msg_empty_content'); } - + // if use editor of nohtml, Remove HTML tags from the contents. if(!$manual_updated || isset($obj->allow_html) || isset($obj->use_html)) { - $obj->content = getModel('editor')->converter($obj, 'comment'); + $obj->content = EditorModel::converter($obj, 'comment'); } - + // remove iframe and script if not a top administrator on the session - if($logged_info->is_admin != 'Y') + $logged_info = Context::get('logged_info'); + if ($logged_info->is_admin !== 'Y') { - $obj->content = removeHackTag($obj->content); + $obj->content = Rhymix\Framework\Filters\HTMLFilter::clean((string)$obj->content); + } + if (config('db.master.charset') !== 'utf8mb4') + { + $obj->content = utf8_mbencode($obj->content); } - $obj->content = utf8_mbencode($obj->content); // begin transaction $oDB = DB::getInstance(); @@ -872,6 +1066,21 @@ class commentController extends comment return $output; } + if($obj->uploaded_count > 0) + { + $attachOutput = FileController::getInstance()->setFilesValid($obj->comment_srl, 'com'); + if(!$attachOutput->toBool()) + { + $oDB->rollback(); + return $attachOutput; + } + $obj->updated_file_count = $attachOutput->get('updated_file_count'); + } + else + { + $obj->updated_file_count = 0; + } + // call a trigger (after) ModuleHandler::triggerCall('comment.updateComment', 'after', $obj); @@ -886,16 +1095,27 @@ class commentController extends comment /** * Fix comment the delete comment message * @param object $obj - * @param bool $is_admin + * @param bool $skip_grant_check * @return object */ - function updateCommentByDelete($obj, $is_admin = FALSE) + function updateCommentByDelete($obj, $skip_grant_check = FALSE) { if (!$obj->comment_srl) { return new BaseObject(-1, 'msg_invalid_request'); } - + + // check if comment exists and permission is granted + $comment = CommentModel::getComment($obj->comment_srl); + if(!$comment->isExists()) + { + return new BaseObject(-1, 'msg_not_founded'); + } + if(!$skip_grant_check && !$comment->isGranted()) + { + return new BaseObject(-1, 'msg_not_permitted'); + } + // call a trigger (before) $output = ModuleHandler::triggerCall('comment.deleteComment', 'before', $comment); if(!$output->toBool()) @@ -903,23 +1123,34 @@ class commentController extends comment return $output; } - // begin transaction - $oDB = DB::getInstance(); - $oDB->begin(); - // If the case manager to delete comments, it indicated that the administrator deleted. - $logged_info = Context::get('logged_info'); - if($is_admin === true && $obj->member_srl !== $logged_info->member_srl) + if(abs($obj->member_srl) != $this->user->member_srl && $this->user->member_srl) { - $obj->content = lang('msg_admin_deleted_comment'); - $obj->status = RX_STATUS_DELETED_BY_ADMIN; + $grant = ModuleModel::getGrant(ModuleModel::getModuleInfoByModuleSrl($comment->get('module_srl')), $this->user); + if ($grant->manager) + { + $obj->content = lang('msg_admin_deleted_comment'); + $obj->status = RX_STATUS_DELETED_BY_ADMIN; + } + else + { + $obj->content = lang('msg_deleted_comment'); + $obj->status = RX_STATUS_DELETED; + } } else { $obj->content = lang('msg_deleted_comment'); $obj->status = RX_STATUS_DELETED; } + + // Begin transaction + $oDB = DB::getInstance(); + $oDB->begin(); + + // Update $obj->member_srl = 0; + $obj->uploaded_count = 0; unset($obj->last_update); $output = executeQuery('comment.updateCommentByDelete', $obj); if(!$output->toBool()) @@ -929,18 +1160,15 @@ class commentController extends comment } // call a trigger (after) - ModuleHandler::triggerCall('comment.deleteComment', 'after', $obj); + ModuleHandler::triggerCall('comment.deleteComment', 'after', $comment); // update the number of comments - $oCommentModel = getModel('comment'); - $comment_count = $oCommentModel->getCommentCount($obj->document_srl); + $comment_count = CommentModel::getCommentCount($obj->document_srl); // only document is exists - if(isset($comment_count)) + if(is_int($comment_count)) { - // create the controller object of the document - $oDocumentController = getController('document'); - // update comment count of the article posting + $oDocumentController = DocumentController::getInstance(); $output = $oDocumentController->updateCommentCount($obj->document_srl, $comment_count, NULL, FALSE); if(!$output->toBool()) { @@ -952,7 +1180,6 @@ class commentController extends comment $oDB->commit(); $output->add('document_srl', $obj->document_srl); - return $output; } @@ -978,15 +1205,12 @@ class commentController extends comment } // update the number of comments - $oCommentModel = getModel('comment'); - $comment_count = $oCommentModel->getCommentCount($obj->document_srl); + $comment_count = CommentModel::getCommentCount($obj->document_srl); // only document is exists - if(isset($comment_count)) + if(is_int($comment_count)) { - // create the controller object of the document - $oDocumentController = getController('document'); - // update comment count of the article posting + $oDocumentController = DocumentController::getInstance(); $output = $oDocumentController->updateCommentCount($obj->document_srl, $comment_count, NULL, FALSE); if(!$output->toBool()) { @@ -1010,22 +1234,22 @@ class commentController extends comment */ function deleteComment($comment_srl, $is_admin = FALSE, $isMoveToTrash = FALSE, $childs = null) { - // create the comment model object - $oCommentModel = getModel('comment'); - - $logged_info = Context::get('logged_info'); - // check if comment already exists - $comment = $oCommentModel->getComment($comment_srl); - if($comment->comment_srl != $comment_srl) + $comment = CommentModel::getComment($comment_srl); + + if(!$comment->isExists()) { - return new BaseObject(-1, 'msg_invalid_request'); + return new BaseObject(-2, 'msg_not_founded'); + } + if(!$is_admin && !$comment->isGranted()) + { + return new BaseObject(-1, 'msg_not_permitted'); } - $oMemberModel = getModel('member'); - $member_info = $oMemberModel->getMemberInfoByMemberSrl($comment->member_srl); - - $document_srl = $comment->document_srl; + $logged_info = Context::get('logged_info'); + $member_info = MemberModel::getMemberInfo($comment->get('member_srl')); + $module_info = ModuleModel::getModuleInfo($comment->get('module_srl')); + $document_srl = $comment->get('document_srl'); // call a trigger (before) $comment->isMoveToTrash = $isMoveToTrash ? true : false; @@ -1035,16 +1259,10 @@ class commentController extends comment return $output; } - // check if permission is granted - if(!$is_admin && !$comment->isGranted()) - { - return new BaseObject(-1, 'msg_not_permitted'); - } - // check if child comment exists on the comment - if(!$childs) + if($childs === null) { - $childs = $oCommentModel->getChildComments($comment_srl); + $childs = CommentModel::getChildComments($comment_srl); } if(count($childs) > 0) { @@ -1052,7 +1270,6 @@ class commentController extends comment $deleteAdminComment = TRUE; if(!$is_admin) { - $logged_info = Context::get('logged_info'); foreach($childs as $val) { if($val->member_srl != $logged_info->member_srl) @@ -1064,14 +1281,16 @@ class commentController extends comment } else if($is_admin) { - $logged_info = Context::get('logged_info'); foreach($childs as $val) { - $c_member_info = $oMemberModel->getMemberInfoByMemberSrl($val->member_srl); - if($c_member_info->is_admin == 'Y' && $logged_info->is_admin != 'Y') + if ($module_info->protect_admin_content_delete !== 'N' && $logged_info->is_admin !== 'Y') { - $deleteAdminComment = FALSE; - break; + $c_member_info = MemberModel::getMemberInfoByMemberSrl($val->member_srl); + if($c_member_info->is_admin == 'Y') + { + $deleteAdminComment = FALSE; + break; + } } } } @@ -1097,10 +1316,6 @@ class commentController extends comment } } - if($member_info->is_admin == 'Y' && $logged_info->is_admin != 'Y') - { - return new BaseObject(-1, 'msg_admin_comment_no_delete'); - } // begin transaction $oDB = DB::getInstance(); $oDB->begin(); @@ -1118,15 +1333,13 @@ class commentController extends comment $output = executeQuery('comment.deleteCommentList', $args); // update the number of comments - $comment_count = $oCommentModel->getCommentCount($document_srl); + $comment_count = CommentModel::getCommentCount($document_srl); // only document is exists - if(isset($comment_count)) + if(is_int($comment_count)) { - // create the controller object of the document - $oDocumentController = getController('document'); - // update comment count of the article posting + $oDocumentController = DocumentController::getInstance(); $output = $oDocumentController->updateCommentCount($document_srl, $comment_count, NULL, FALSE); if(!$output->toBool()) { @@ -1143,8 +1356,8 @@ class commentController extends comment { $this->_deleteDeclaredComments($args); $this->_deleteVotedComments($args); - } - else + } + else { $args = new stdClass(); $args->upload_target_srl = $comment_srl; @@ -1153,7 +1366,7 @@ class commentController extends comment } // Remove the thumbnail file - Rhymix\Framework\Storage::deleteEmptyDirectory(RX_BASEDIR . sprintf('files/thumbnails/%s', getNumberingPath($comment_srl, 3)), true); + Rhymix\Framework\Storage::deleteDirectory(RX_BASEDIR . sprintf('files/thumbnails/%s', getNumberingPath($comment_srl, 3))); // commit $oDB->commit(); @@ -1170,57 +1383,49 @@ class commentController extends comment */ function moveCommentToTrash($obj, $updateComment = false) { + // check if comment exists and permission is granted + $oComment = ($obj instanceof commentItem) ? $obj : CommentModel::getComment($obj->comment_srl); + if(!$oComment->isExists()) + { + return new BaseObject(-1, 'msg_not_founded'); + } + if(!$oComment->isGranted()) + { + return new BaseObject(-1, 'msg_not_permitted'); + } + $logged_info = Context::get('logged_info'); - $trash_args = new stdClass(); - if(!$obj->trash_srl) + $module_info = ModuleModel::getModuleInfo($oComment->get('module_srl')); + + if ($module_info->protect_admin_content_delete !== 'N' && $logged_info->is_admin !== 'Y') { - $trash_args->trash_srl = getNextSequence(); - } - else - { - $trash_args->trash_srl = $obj->trash_srl; + $member_info = MemberModel::getMemberInfo($oComment->get('member_srl')); + if($member_info->is_admin === 'Y') + { + return new BaseObject(-1, 'msg_admin_comment_no_move_to_trash'); + } } - $oCommentModel = getModel('comment'); - $oComment = $oCommentModel->getComment($obj->comment_srl); - - $oMemberModel = getModel('member'); - $member_info = $oMemberModel->getMemberInfoByMemberSrl($oComment->get('member_srl')); - if($member_info->is_admin == 'Y' && $logged_info->is_admin != 'Y') + // Call trigger (before). + $trigger_output = ModuleHandler::triggerCall('comment.moveCommentToTrash', 'before', $obj); + if (!$trigger_output->toBool()) { - return new BaseObject(-1, 'msg_admin_comment_no_move_to_trash'); + return $trigger_output; } - $obj->module_srl = $oComment->get('module_srl'); - $trash_args->module_srl = $obj->module_srl; - if($trash_args->module_srl === 0) - { - return new BaseObject(-1, 'msg_module_srl_not_exists'); - } - $trash_args->document_srl = $obj->document_srl; - $trash_args->comment_srl = $obj->comment_srl; - $trash_args->description = $obj->description; - - if(!Context::get('is_logged')) - { - $trash_args->member_Srl = $logged_info->member_srl; - $trash_args->user_id = htmlspecialchars_decode($logged_info->user_id); - $trash_args->user_name = htmlspecialchars_decode($logged_info->user_name); - $trash_args->nick_name = htmlspecialchars_decode($logged_info->nick_name); - } - - $oDB = &DB::getInstance(); - $oDB->begin(); - + // Create trash object. require_once(RX_BASEDIR.'modules/trash/model/TrashVO.php'); $oTrashVO = new TrashVO(); $oTrashVO->setTrashSrl(getNextSequence()); - $oTrashVO->setTitle(trim(strip_tags($obj->variables['content']))); + $oTrashVO->setTitle(mb_substr($oComment->getContentText(100), 0, 250, 'UTF-8')); $oTrashVO->setOriginModule('comment'); - $oTrashVO->setSerializedObject(serialize($obj->variables)); - $oTrashVO->setDescription($obj->description); + $oTrashVO->setSerializedObject(serialize($oComment->variables)); + $oTrashVO->setDescription($obj->description ?? ''); - $oTrashAdminController = getAdminController('trash'); + $oDB = DB::getInstance(); + $oDB->begin(); + + $oTrashAdminController = TrashAdminController::getInstance(); $output = $oTrashAdminController->insertTrash($oTrashVO); if(!$output->toBool()) { @@ -1228,9 +1433,17 @@ class commentController extends comment return $output; } - if($updateComment !== true) + if(!$updateComment) { - $output = executeQuery('comment.deleteComment', $trash_args); + $args = new stdClass; + $args->comment_srl = $obj->comment_srl; + $output = executeQuery('comment.deleteComment', $args); + if(!$output->toBool()) + { + $oDB->rollback(); + return $output; + } + $output = executeQuery('comment.deleteCommentList', $args); if(!$output->toBool()) { $oDB->rollback(); @@ -1238,21 +1451,12 @@ class commentController extends comment } } - $args = new stdClass(); - $args->comment_srl = $obj->comment_srl; - $output = executeQuery('comment.deleteCommentList', $args); - // update the number of comments - $comment_count = $oCommentModel->getCommentCount($obj->document_srl); - - // only document is exists - if(isset($comment_count)) + $comment_count = CommentModel::getCommentCount($oComment->document_srl); + if(is_int($comment_count)) { - // create the controller object of the document - $oDocumentController = getController('document'); - - // update comment count of the article posting - $output = $oDocumentController->updateCommentCount($obj->document_srl, $comment_count, NULL, FALSE); + $oDocumentController = DocumentController::getInstance(); + $output = $oDocumentController->updateCommentCount($oComment->document_srl, $comment_count); if(!$output->toBool()) { $oDB->rollback(); @@ -1260,6 +1464,7 @@ class commentController extends comment } } + /* if($oComment->hasUploadedFiles()) { $args = new stdClass(); @@ -1267,7 +1472,10 @@ class commentController extends comment $args->isvalid = 'N'; executeQuery('file.updateFileValid', $args); } + */ + $obj->trash_srl = $oTrashVO->getTrashSrl(); + $obj->module_srl = $oComment->get('module_srl'); $obj->document_srl = $oComment->get('document_srl'); $obj->parent_srl = $oComment->get('parent_srl'); $obj->member_srl = $oComment->get('member_srl'); @@ -1277,10 +1485,10 @@ class commentController extends comment $oDB->commit(); + // Remove the thumbnail file + Rhymix\Framework\Storage::deleteDirectory(RX_BASEDIR . sprintf('files/thumbnails/%s', getNumberingPath($obj->comment_srl, 3))); - Rhymix\Framework\Storage::deleteEmptyDirectory(RX_BASEDIR . sprintf('files/thumbnails/%s', getNumberingPath($comment_srl, 3)), true); - $output->add('document_srl', $obj->document_srl); - + $output->add('document_srl', $oComment->document_srl); return $output; } @@ -1302,10 +1510,6 @@ class commentController extends comment */ function deleteComments($document_srl, $obj = NULL) { - // create the document model object - $oDocumentModel = getModel('document'); - $oCommentModel = getModel('comment'); - // check if permission is granted if(is_object($obj)) { @@ -1314,7 +1518,7 @@ class commentController extends comment } else { - $oDocument = $oDocumentModel->getDocument($document_srl); + $oDocument = DocumentModel::getDocument($document_srl); } if(!$oDocument->isGranted()) @@ -1323,12 +1527,12 @@ class commentController extends comment } // get a list of comments and then execute a trigger(way to reduce the processing cost for delete all) + $commentSrlList = array(); $args = new stdClass(); $args->document_srl = $document_srl; $comments = executeQueryArray('comment.getAllComments', $args); if($comments->data) { - $commentSrlList = array(); foreach($comments->data as $comment) { $commentSrlList[] = $comment->comment_srl; @@ -1370,32 +1574,33 @@ class commentController extends comment /** * delete declared comment, log - * @param array|string $commentSrls : srls string (ex: 1, 2,56, 88) + * @param object $args should contain comment_srl * @return void */ - function _deleteDeclaredComments($commentSrls) + function _deleteDeclaredComments($args) { - executeQuery('comment.deleteDeclaredComments', $commentSrls); - executeQuery('comment.deleteCommentDeclaredLog', $commentSrls); + executeQuery('comment.deleteDeclaredComments', $args); + executeQuery('comment.deleteDeclaredCommentLog', $args); } /** * delete voted comment log - * @param array|string $commentSrls : srls string (ex: 1, 2,56, 88) + * @param object $args should contain comment_srl * @return void */ - function _deleteVotedComments($commentSrls) + function _deleteVotedComments($args) { - executeQuery('comment.deleteCommentVotedLog', $commentSrls); + executeQuery('comment.deleteCommentVotedLog', $args); } /** * Increase vote-up counts of the comment * @param int $comment_srl * @param int $point + * @param bool $allow_same_ip * @return Object */ - function updateVotedCount($comment_srl, $point = 1) + function updateVotedCount($comment_srl, $point = 1, $allow_same_ip = false) { if($point > 0) { @@ -1407,35 +1612,40 @@ class commentController extends comment } // invalid vote if vote info exists in the session info. - if($_SESSION['voted_comment'][$comment_srl]) + if (!empty($_SESSION['voted_comment'][$comment_srl])) { - return new BaseObject(-1, $failed_voted); + if ($_SESSION['voted_comment'][$comment_srl] > 0) + { + return new BaseObject(-1, 'failed_voted_already'); + } + else + { + return new BaseObject(-1, 'failed_blamed_already'); + } } // Get the original comment - $oCommentModel = getModel('comment'); - $oComment = $oCommentModel->getComment($comment_srl, FALSE, FALSE); + $oComment = CommentModel::getComment($comment_srl); // Pass if the author's IP address is as same as visitor's. - if($oComment->get('ipaddress') == $_SERVER['REMOTE_ADDR']) + if(!$allow_same_ip && $oComment->get('ipaddress') == \RX_CLIENT_IP && !$this->user->isAdmin()) { - $_SESSION['voted_comment'][$comment_srl] = false; return new BaseObject(-1, $failed_voted); } // Create a member model object - $oMemberModel = getModel('member'); - $member_srl = $oMemberModel->getLoggedMemberSrl(); + $member_srl = MemberModel::getLoggedMemberSrl(); // if the comment author is a member if($oComment->get('member_srl')) { - // session registered if the author information matches to the current logged-in user's. + /* session registered if the author information matches to the current logged-in user's. if($member_srl && $member_srl == abs($oComment->get('member_srl'))) { $_SESSION['voted_comment'][$comment_srl] = false; - return new BaseObject(-1, $failed_voted); + return new BaseObject(-1, $failed_voted . '_self'); } + */ } // If logged-in, use the member_srl. otherwise use the ipaddress. @@ -1446,16 +1656,24 @@ class commentController extends comment } else { - $args->ipaddress = $_SERVER['REMOTE_ADDR']; + $args->member_srl = 0; + $args->ipaddress = \RX_CLIENT_IP; } $args->comment_srl = $comment_srl; $output = executeQuery('comment.getCommentVotedLogInfo', $args); // Pass after registering a session if log information has vote-up logs - if($output->data->count) + if ($output->data->count) { - $_SESSION['voted_comment'][$comment_srl] = false; - return new BaseObject(-1, $failed_voted); + $_SESSION['voted_comment'][$comment_srl] = intval($output->data->point); + if ($output->data->point > 0) + { + return new BaseObject(-1, 'failed_voted_already'); + } + else + { + return new BaseObject(-1, 'failed_blamed_already'); + } } // Call a trigger (before) @@ -1516,6 +1734,12 @@ class commentController extends comment $output->add('blamed_count', $trigger_obj->after_point); } + // Prevent session data getting too large + if (count($_SESSION['voted_comment']) > 200) + { + $_SESSION['voted_comment'] = array_slice($_SESSION['voted_comment'], 100, null, true); + } + return $output; } @@ -1528,9 +1752,9 @@ class commentController extends comment function declaredComment($comment_srl, $declare_message) { // Fail if session information already has a reported document - if($_SESSION['declared_comment'][$comment_srl]) + if(!empty($_SESSION['declared_comment'][$comment_srl])) { - return new BaseObject(-1, 'failed_declared'); + return new BaseObject(-1, 'failed_declared_already'); } // check if already reported @@ -1541,15 +1765,15 @@ class commentController extends comment { return $output; } - + $declared_count = ($output->data->declared_count) ? $output->data->declared_count : 0; $declare_message = trim(htmlspecialchars($declare_message)); - + $trigger_obj = new stdClass(); $trigger_obj->comment_srl = $comment_srl; $trigger_obj->declared_count = $declared_count; $trigger_obj->declare_message = $declare_message; - + // Call a trigger (before) $trigger_output = ModuleHandler::triggerCall('comment.declaredComment', 'before', $trigger_obj); if(!$trigger_output->toBool()) @@ -1558,27 +1782,28 @@ class commentController extends comment } // get the original comment - $oCommentModel = getModel('comment'); - $oComment = $oCommentModel->getComment($comment_srl, FALSE, FALSE); + $oComment = CommentModel::getComment($comment_srl); // failed if both ip addresses between author's and the current user are same. - if($oComment->get('ipaddress') == $_SERVER['REMOTE_ADDR']) + $module_srl = $oComment->get('module_srl'); + $comment_config = ModuleModel::getModulePartConfig('comment', $module_srl); + $allow_same_ip = ($comment_config->allow_declare_from_same_ip ?? 'N') === 'Y'; + if(!$allow_same_ip && $oComment->get('ipaddress') == \RX_CLIENT_IP && !$this->user->isAdmin()) { - $_SESSION['declared_comment'][$comment_srl] = TRUE; return new BaseObject(-1, 'failed_declared'); } // Get currently logged in user. $member_srl = intval($this->user->member_srl); - + // if the comment author is a member if($oComment->get('member_srl')) { // session registered if the author information matches to the current logged-in user's. if($member_srl && $member_srl == abs($oComment->get('member_srl'))) { - $_SESSION['declared_comment'][$comment_srl] = TRUE; - return new BaseObject(-1, 'failed_declared'); + $_SESSION['declared_comment'][$comment_srl] = FALSE; + return new BaseObject(-1, 'failed_declared_self'); } } @@ -1596,15 +1821,15 @@ class commentController extends comment $log_output = executeQuery('comment.getCommentDeclaredLogInfo', $args); if($log_output->data->count) { - $_SESSION['declared_comment'][$comment_srl] = TRUE; - return new BaseObject(-1, 'failed_declared'); + $_SESSION['declared_comment'][$comment_srl] = FALSE; + return new BaseObject(-1, 'failed_declared_already'); } - + // Fill in remaining information for logging. $args->member_srl = $member_srl; $args->ipaddress = \RX_CLIENT_IP; $args->declare_message = $declare_message; - + // begin transaction $oDB = DB::getInstance(); $oDB->begin(); @@ -1637,9 +1862,6 @@ class commentController extends comment // Send message to admin $message_targets = array(); - $module_srl = $oComment->get('module_srl'); - $oModuleModel = getModel('module'); - $comment_config = $oModuleModel->getModulePartConfig('comment', $module_srl); if ($comment_config->declared_message && in_array('admin', $comment_config->declared_message)) { $output = executeQueryArray('member.getAdmins', new stdClass); @@ -1658,12 +1880,12 @@ class commentController extends comment } if ($message_targets) { - $oCommunicationController = getController('communication'); + $oCommunicationController = CommunicationController::getInstance(); $message_title = lang('document.declared_message_title'); $message_content = sprintf('

        %s

        %s

        ', $oComment->getPermanentUrl(), $oComment->getContentText(50), $declare_message); foreach ($message_targets as $target_member_srl => $val) { - $oCommunicationController->sendMessage($this->user->member_srl, $target_member_srl, $message_title, $message_content, false); + $oCommunicationController->sendMessage($this->user->member_srl, $target_member_srl, $message_title, $message_content, false, null, false); } } @@ -1677,18 +1899,173 @@ class commentController extends comment // leave into the session information $_SESSION['declared_comment'][$comment_srl] = TRUE; + // Prevent session data getting too large + if (count($_SESSION['declared_comment']) > 200) + { + $_SESSION['declared_comment'] = array_slice($_SESSION['declared_comment'], 100, null, true); + } + $this->setMessage('success_declared'); } + /** + * Cancel a report + * @param $comment_srl + * @return BaseObject|object|void + */ + function declaredCommentCancel($comment_srl) + { + $member_srl = $this->user->member_srl; + if (!$_SESSION['declared_comment'][$comment_srl] && !$member_srl) + { + return new BaseObject(-1, 'failed_declared_cancel'); + } + + // Get the original document + $oComment = CommentModel::getComment($comment_srl); + + $oDB = DB::getInstance(); + $oDB->begin(); + + $args = new stdClass; + $args->comment_srl = $comment_srl; + if ($member_srl) + { + $args->member_srl = $member_srl; + } + else + { + $args->ipaddress = \RX_CLIENT_IP; + } + $log_output = executeQuery('comment.getCommentDeclaredLogInfo', $args); + if (!isset($log_output->data->count) || !$log_output->data->count) + { + unset($_SESSION['declared_comment'][$comment_srl]); + return new BaseObject(-1, 'failed_declared_cancel'); + } + + // Get current declared count + $args = new stdClass(); + $args->comment_srl = $comment_srl; + $output = executeQuery('comment.getDeclaredComment', $args); + $declared_count = ($output->data->declared_count) ? $output->data->declared_count : 0; + + // Call a trigger (before) + $trigger_obj = new stdClass(); + $trigger_obj->comment_srl = $comment_srl; + $trigger_obj->document_srl = $oComment->get('document_srl'); + $trigger_obj->declared_count = $declared_count; + $trigger_output = ModuleHandler::triggerCall('comment.declaredCommentCancel', 'before', $trigger_obj); + if (!$trigger_output->toBool()) + { + return $trigger_output; + } + + if ($declared_count > 1) + { + $output = executeQuery('comment.updateDeclaredCommentCancel', $args); + } + else + { + $output = executeQuery('comment.deleteDeclaredComments', $args); + } + if (!$output->toBool()) + { + $oDB->rollback(); + return $output; + } + + $output = executeQuery('comment.deleteDeclaredCommentLog', $args); + if (!$output->toBool()) + { + $oDB->rollback(); + return $output; + } + + $message_targets = array(); + $module_srl = $oComment->get('module_srl'); + $comment_config = ModuleModel::getModulePartConfig('comment', $module_srl); + if ($comment_config->declared_message && in_array('admin', $comment_config->declared_message)) + { + $output = executeQueryArray('member.getAdmins', new stdClass); + foreach ($output->data as $admin) + { + $message_targets[$admin->member_srl] = true; + } + } + if ($comment_config->declared_message && in_array('manager', $comment_config->declared_message)) + { + $output = executeQueryArray('module.getModuleAdmin', (object)['module_srl' => $module_srl]); + foreach ($output->data as $manager) + { + $message_targets[$manager->member_srl] = true; + } + } + if ($message_targets) + { + $oCommunicationController = CommunicationController::getInstance(); + $message_title = lang('document.declared_cancel_message_title'); + $message_content = sprintf('

        %s

        ', $oComment->getPermanentUrl(), $oComment->getContentText(50)); + foreach ($message_targets as $target_member_srl => $val) + { + $oCommunicationController->sendMessage($this->user->member_srl, $target_member_srl, $message_title, $message_content, false, null, false); + } + } + + $oDB->commit(); + + $trigger_obj->declared_count = $declared_count - 1; + ModuleHandler::triggerCall('comment.declaredCommentCancel', 'after', $trigger_obj); + + unset($_SESSION['declared_comment'][$comment_srl]); + + $this->setMessage('success_declared_cancel'); + } + + /** + * Update the number of uploaded files in the comment + * + * @param int|array $comment_srl_list + * @return void + */ + public function updateUploadedCount($comment_srl_list) + { + if (!is_array($comment_srl_list)) + { + $comment_srl_list = array($comment_srl_list); + } + + if (!count($comment_srl_list)) + { + return; + } + + $comment_srl_list = array_unique($comment_srl_list); + + foreach($comment_srl_list as $comment_srl) + { + $comment_srl = (int)$comment_srl; + if ($comment_srl <= 0) + { + continue; + } + + $args = new stdClass; + $args->comment_srl = $comment_srl; + $args->uploaded_count = FileModel::getFilesCount($comment_srl); + executeQuery('comment.updateUploadedCount', $args); + } + } + /** * Method to add a pop-up menu when clicking for displaying child comments * @param string $url * @param string $str - * @param strgin $icon - * @param strgin $target + * @param string $icon + * @param string $target * @return void */ - function addCommentPopupMenu($url, $str, $icon = '', $target = 'self') + function addCommentPopupMenu($url, $str, $icon = '', $target = '_blank') { $comment_popup_menu_list = Context::get('comment_popup_menu_list'); if(!is_array($comment_popup_menu_list)) @@ -1716,23 +2093,22 @@ class commentController extends comment $target_module_srl = array_map('trim', explode(',', $target_module_srl)); $logged_info = Context::get('logged_info'); $module_srl = array(); - $oModuleModel = getModel('module'); foreach ($target_module_srl as $srl) { if (!$srl) continue; - - $module_info = $oModuleModel->getModuleInfoByModuleSrl($srl); + + $module_info = ModuleModel::getModuleInfoByModuleSrl($srl); if (!$module_info->module_srl) { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - - $module_grant = $oModuleModel->getGrant($module_info, $logged_info); + + $module_grant = ModuleModel::getGrant($module_info, $logged_info); if (!$module_grant->manager) { throw new Rhymix\Framework\Exceptions\NotPermitted; } - + $module_srl[] = $srl; } @@ -1742,6 +2118,18 @@ class commentController extends comment { $comment_config->comment_count = 50; } + $comment_config->comment_page_count = (int) Context::get('comment_page_count'); + if(!$comment_config->comment_page_count) + { + $comment_config->comment_page_count = 10; + } + $comment_config->max_thread_depth = (int)Context::get('max_thread_depth') ?: 0; + + $comment_config->default_page = Context::get('default_page'); + if($comment_config->default_page !== 'first') + { + $comment_config->default_page = 'last'; + } $comment_config->use_vote_up = Context::get('use_vote_up'); if(!$comment_config->use_vote_up) @@ -1755,6 +2143,21 @@ class commentController extends comment $comment_config->use_vote_down = 'Y'; } + $comment_config->allow_vote_from_same_ip = Context::get('allow_vote_from_same_ip'); + if(!$comment_config->allow_vote_from_same_ip) $comment_config->allow_vote_from_same_ip = 'N'; + + $comment_config->allow_vote_cancel = Context::get('allow_vote_cancel'); + if(!$comment_config->allow_vote_cancel) $comment_config->allow_vote_cancel = 'N'; + + $comment_config->allow_vote_non_member = Context::get('allow_vote_non_member'); + if(!$comment_config->allow_vote_non_member) $comment_config->allow_vote_non_member = 'N'; + + $comment_config->allow_declare_from_same_ip = Context::get('allow_declare_from_same_ip'); + if(!$comment_config->allow_declare_from_same_ip) $comment_config->allow_declare_from_same_ip = 'N'; + + $comment_config->allow_declare_cancel = Context::get('allow_declare_cancel'); + if(!$comment_config->allow_declare_cancel) $comment_config->allow_declare_cancel = 'N'; + $comment_config->declared_message = Context::get('declared_message'); if(!is_array($comment_config->declared_message)) $comment_config->declared_message = array(); $comment_config->declared_message = array_values($comment_config->declared_message); @@ -1785,7 +2188,7 @@ class commentController extends comment */ function setCommentModuleConfig($srl, $comment_config) { - $oModuleController = getController('module'); + $oModuleController = ModuleController::getInstance(); $oModuleController->insertModulePartConfig('comment', $srl, $comment_config); return new BaseObject(); } @@ -1809,14 +2212,13 @@ class commentController extends comment if(count($commentSrlList) > 0) { - $oCommentModel = getModel('comment'); - $commentList = $oCommentModel->getComments($commentSrlList); + $commentList = CommentModel::getComments($commentSrlList); if(is_array($commentList)) { foreach($commentList as $value) { - $value->content = strip_tags($value->content); + $value->content = escape(strip_tags($value->content), false); } } } @@ -1832,19 +2234,19 @@ class commentController extends comment $this->add('comment_list', $commentList); } - + function triggerMoveDocument($obj) { executeQuery('comment.updateCommentModule', $obj); executeQuery('comment.updateCommentListModule', $obj); } - + function triggerAddCopyDocument(&$obj) { $args = new stdClass; $args->document_srls = $obj->source->document_srl; $comment_list = executeQueryArray('comment.getCommentsByDocumentSrls', $args)->data; - + $copied_comments = array(); foreach($comment_list as $comment) { @@ -1853,30 +2255,31 @@ class commentController extends comment $copy->module_srl = $obj->copied->module_srl; $copy->document_srl = $obj->copied->document_srl; $copy->parent_srl = $comment->parent_srl ? $copied_comments[$comment->parent_srl] : 0; - + // call a trigger (add) $args = new stdClass; $args->source = $comment; $args->copied = $copy; ModuleHandler::triggerCall('comment.copyCommentByDocument', 'add', $args); - + ModuleHandler::triggerCall('comment.copyCommentByDocument.each', 'before', $args); + // insert a copied comment $this->insertComment($copy, true); - + + ModuleHandler::triggerCall('comment.copyCommentByDocument.each', 'after', $args); $copied_comments[$comment->comment_srl] = $copy->comment_srl; } - + // update $obj->copied->last_updater = $copy->nick_name; $obj->copied->comment_count = count($copied_comments); } - + function triggerCopyModule(&$obj) { - $oModuleModel = getModel('module'); - $commentConfig = $oModuleModel->getModulePartConfig('comment', $obj->originModuleSrl); + $commentConfig = ModuleModel::getModulePartConfig('comment', $obj->originModuleSrl); - $oModuleController = getController('module'); + $oModuleController = ModuleController::getInstance(); if(is_array($obj->moduleSrlList)) { foreach($obj->moduleSrlList as $moduleSrl) diff --git a/modules/comment/comment.item.php b/modules/comment/comment.item.php index 927a1a767..30508fc69 100644 --- a/modules/comment/comment.item.php +++ b/modules/comment/comment.item.php @@ -9,9 +9,8 @@ * @package /modules/comment * @version 0.1 */ -class commentItem extends BaseObject +class CommentItem extends BaseObject { - /** * comment number * @var int @@ -71,7 +70,7 @@ class commentItem extends BaseObject */ function setAttribute($attribute) { - if(!$attribute->comment_srl) + if(!is_object($attribute) || !$attribute->comment_srl) { $this->comment_srl = NULL; return; @@ -94,24 +93,24 @@ class commentItem extends BaseObject { return (bool) ($this->comment_srl); } - + function isGranted() { if(!$this->isExists()) { return false; } - + if (isset($_SESSION['granted_comment'][$this->comment_srl])) { return true; } - + if ($this->grant_cache !== null) { return $this->grant_cache; } - + $logged_info = Context::get('logged_info'); if (!$logged_info->member_srl) { @@ -125,90 +124,149 @@ class commentItem extends BaseObject { return $this->grant_cache = true; } - - $oModuleModel = getModel('module'); - $grant = $oModuleModel->getGrant($oModuleModel->getModuleInfoByModuleSrl($this->get('module_srl')), $logged_info); - if ($grant->manager) + + $grant = ModuleModel::getGrant(ModuleModel::getModuleInfoByModuleSrl($this->get('module_srl')), $logged_info); + if ($grant->manager && $grant->can('moderate:comment')) { return $this->grant_cache = true; } - + return $this->grant_cache = false; } - + function setGrant() { $this->grant_cache = true; } - + function setGrantForSession() { $_SESSION['granted_comment'][$this->comment_srl] = true; $this->setGrant(); } - - function isAccessible() + + /** + * Return the status code. + * + * @return string + */ + public function getStatus() + { + switch ($this->get('status')) + { + case RX_STATUS_TEMP: return 'TEMP'; + case RX_STATUS_PUBLIC: return $this->get('is_secret') !== 'Y' ? 'PUBLIC' : 'SECRET'; + case RX_STATUS_SECRET: return 'SECRET'; + case RX_STATUS_EMBARGO: return 'EMBARGO'; + case RX_STATUS_TRASH: return 'TRASH'; + case RX_STATUS_CENSORED: return 'CENSORED'; + case RX_STATUS_CENSORED_BY_ADMIN: return 'CENSORED_BY_ADMIN'; + case RX_STATUS_DELETED: return 'DELETED'; + case RX_STATUS_DELETED_BY_ADMIN: return 'DELETED_BY_ADMIN'; + case RX_STATUS_OTHER: return 'OTHER'; + default: return 'OTHER'; + } + } + + /** + * Return the status in human-readable text. + * + * @return string + */ + public function getStatusText() + { + $status = $this->getStatus(); + $statusList = lang('document.status_name_list'); + if ($status && isset($statusList[$status])) + { + return $statusList[$status]; + } + else + { + return $statusList['OTHER']; + } + } + + function isAccessible($strict = false) { if(!$this->isExists()) { return false; } - + + if ($strict) + { + $module_info = ModuleModel::getModuleInfoByModuleSrl($this->get('module_srl')); + if (!$module_info) + { + return false; + } + $grant = ModuleModel::getGrant($module_info, Context::get('logged_info')); + if (isset($grant->list) && isset($grant->view) && ($grant->list !== true || $grant->view !== true)) + { + return false; + } + } + if (isset($_SESSION['accessible'][$this->comment_srl]) && $_SESSION['accessible'][$this->comment_srl] === $this->get('last_update')) { return true; } - + if ($this->get('status') == RX_STATUS_PUBLIC && $this->get('is_secret') !== 'Y') { $this->setAccessible(); return true; } - + if ($this->isGranted()) { $this->setAccessible(); return true; } - - $oDocument = getModel('document')->getDocument($this->get('document_srl')); + + $oDocument = DocumentModel::getDocument($this->get('document_srl')); if ($oDocument->isGranted()) { $this->setAccessible(); return true; } - + return false; } - + function setAccessible() { if(Context::getSessionStatus()) { $_SESSION['accessible'][$this->comment_srl] = $this->get('last_update'); } + if(is_array($_SESSION['accessible']) && count($_SESSION['accessible']) > 200) + { + $_SESSION['accessible'] = array_slice($_SESSION['accessible'], 100, null, true); + } } - + function isEditable() { return !$this->get('member_srl') || $this->isGranted(); } - + function isSecret() { - return $this->get('is_secret') == 'Y'; + return $this->get('status') == RX_STATUS_SECRET || $this->get('is_secret') == 'Y'; } - + function isDeleted() { return $this->get('status') == RX_STATUS_DELETED || $this->get('status') == RX_STATUS_DELETED_BY_ADMIN; } - + function isDeletedByAdmin() { return $this->get('status') == RX_STATUS_DELETED_BY_ADMIN; } - + function useNotify() { return $this->get('notify_message') == 'Y'; @@ -226,7 +284,7 @@ class commentItem extends BaseObject return; } - // pass if the author is not logged-in user + // pass if the author is not logged-in user if(!$this->get('member_srl')) { return; @@ -239,10 +297,6 @@ class commentItem extends BaseObject return; } - // get where the comment belongs to - $oDocumentModel = getModel('document'); - $oDocument = $oDocumentModel->getDocument($this->get('document_srl')); - // Variables if($type) { @@ -256,7 +310,7 @@ class commentItem extends BaseObject // send a message $oCommunicationController = getController('communication'); - $oCommunicationController->sendMessage($sender_member_srl, $receiver_srl, $title, $content, FALSE); + $oCommunicationController->sendMessage($sender_member_srl, $receiver_srl, $title, $content, false, null, false); } function getIpAddress() @@ -271,7 +325,7 @@ class commentItem extends BaseObject function isExistsHomepage() { - if(trim($this->get('homepage'))) + if(trim($this->get('homepage') ?? '')) { return TRUE; } @@ -281,7 +335,7 @@ class commentItem extends BaseObject function getHomepageUrl() { - $url = trim($this->get('homepage')); + $url = trim($this->get('homepage') ?? ''); if(!$url) { return; @@ -316,6 +370,11 @@ class commentItem extends BaseObject } function getVote() + { + return $this->getMyVote(); + } + + function getMyVote() { if(!$this->comment_srl) return false; if(isset($_SESSION['voted_comment'][$this->comment_srl])) @@ -327,11 +386,18 @@ class commentItem extends BaseObject if(!$logged_info->member_srl) return false; $args = new stdClass(); - $args->member_srl = $logged_info->member_srl; + if($logged_info->member_srl) + { + $args->member_srl = $logged_info->member_srl; + } + else + { + $args->member_srl = 0; + $args->ipaddress = \RX_CLIENT_IP; + } $args->comment_srl = $this->comment_srl; $output = executeQuery('comment.getCommentVotedLog', $args); - - if($output->data->point) + if(isset($output->data) && $output->data->point) { return $_SESSION['voted_comment'][$this->comment_srl] = $output->data->point; } @@ -339,7 +405,45 @@ class commentItem extends BaseObject return $_SESSION['voted_comment'][$this->comment_srl] = false; } - function getContentPlainText($strlen = 0) + function getDeclared() + { + if (!$this->isExists()) + { + return false; + } + + $logged_info = Context::get('logged_info'); + if (!$logged_info->member_srl) + { + return false; + } + + if (isset($_SESSION['declared_comment'][$this->comment_srl])) + { + return $_SESSION['declared_comment'][$this->comment_srl]; + } + + $args = new stdClass(); + if ($logged_info->member_srl) + { + $args->member_srl = $logged_info->member_srl; + } + else + { + $args->ipaddress = \RX_CLIENT_IP; + } + $args->comment_srl = $this->comment_srl; + $output = executeQuery('comment.getCommentDeclaredLogInfo', $args); + $declared_count = isset($output->data) ? intval($output->data->count) : 0; + if ($declared_count > 0) + { + return $_SESSION['declared_comment'][$this->comment_srl] = $declared_count; + } + + return false; + } + + function getContentPlainText($strlen = 0, $default_content = '') { if($this->isDeletedByAdmin()) { @@ -357,13 +461,29 @@ class commentItem extends BaseObject { $content = $this->get('content'); } - + + $content = preg_replace('!(get('content'); } - + + $content = preg_replace('!(

        ||
        %s
        ', array( + $content = vsprintf('
        %s
        ', array( $this->comment_srl, $member_srl, $this->comment_srl, $member_srl, $additional_class, $content, $this->comment_srl, $member_srl )); } @@ -449,7 +573,7 @@ class commentItem extends BaseObject { if($add_xe_content_class) { - $content = sprintf('
        %s
        ', $additional_class, $content); + $content = sprintf('
        %s
        ', $additional_class, $content); } } @@ -460,21 +584,34 @@ class commentItem extends BaseObject * Return summary content * @return string */ - function getSummary($str_size = 50, $tail = '...') + function getSummary($str_size = 50, $tail = '...', $default_content = '') { // Remove tags - $content = strip_tags($this->getContent(false, false)); - + $content = $this->getContent(false, false); + $content = preg_replace('!(getModuleInfoByModuleSrl($this->get('module_srl'))->mid; + $mid = ModuleModel::getMidByModuleSrl($this->get('module_srl')); return getFullUrl('', 'mid', $mid, 'document_srl', $this->get('document_srl')) . '#comment_' . $this->get('comment_srl'); } @@ -529,7 +666,7 @@ class commentItem extends BaseObject { return false; } - + return $this->get('uploaded_count') ? TRUE : FALSE; } @@ -539,14 +676,13 @@ class commentItem extends BaseObject { return; } - + if(!$this->get('uploaded_count')) { return; } - - $oFileModel = getModel('file'); - $file_list = $oFileModel->getFiles($this->comment_srl, array(), 'file_srl', TRUE); + + $file_list = FileModel::getFiles($this->comment_srl, array(), 'file_srl', true, 'com', true); return $file_list; } @@ -561,8 +697,7 @@ class commentItem extends BaseObject { $module_srl = Context::get('module_srl'); } - $oEditorModel = getModel('editor'); - return $oEditorModel->getModuleEditor('comment', $module_srl, $this->comment_srl, 'comment_srl', 'content'); + return EditorModel::getModuleEditor('comment', $module_srl, $this->comment_srl, 'comment_srl', 'content'); } /** @@ -575,8 +710,7 @@ class commentItem extends BaseObject { return; } - $oMemberModel = getModel('member'); - $profile_info = $oMemberModel->getProfileImage($this->get('member_srl')); + $profile_info = MemberModel::getProfileImage($this->get('member_srl')); if(!$profile_info) { return; @@ -589,7 +723,7 @@ class commentItem extends BaseObject * Return author's signiture * @return string */ - function getSignature() + function getSignature($enforce_max_height = true) { // pass if the posting not exists. if(!$this->isExists() || $this->get('member_srl') <= 0) @@ -598,20 +732,18 @@ class commentItem extends BaseObject } // get the signiture information - $oMemberModel = getModel('member'); - $signature = $oMemberModel->getSignature($this->get('member_srl')); + $signature = MemberModel::getSignature($this->get('member_srl')); // check if max height of the signiture is specified on the member module if(!isset($GLOBALS['__member_signature_max_height'])) { - $oModuleModel = getModel('module'); - $member_config = $oModuleModel->getModuleConfig('member'); + $member_config = ModuleModel::getModuleConfig('member'); $GLOBALS['__member_signature_max_height'] = $member_config->signature_max_height; } $max_signature_height = $GLOBALS['__member_signature_max_height']; - if($max_signature_height) + if($max_signature_height && $enforce_max_height) { $signature = sprintf('
        %s
        ', $max_signature_height, $max_signature_height, $max_signature_height, $signature); } @@ -637,48 +769,42 @@ class commentItem extends BaseObject function getThumbnail($width = 80, $height = 0, $thumbnail_type = '') { // return false if no doc exists - if(!$this->comment_srl) + if(!$this->comment_srl || !$this->isAccessible()) { return; } // Get thumbnail type information from document module's configuration - $config = $GLOBALS['__document_config__']; - if(!$config) - { - $config = $GLOBALS['__document_config__'] = getModel('document')->getDocumentConfig(); - } + $config = DocumentModel::getDocumentConfig(); if ($config->thumbnail_target === 'none' || $config->thumbnail_type === 'none') { return; } - if(!in_array($thumbnail_type, array('crop', 'ratio'))) + if(!in_array($thumbnail_type, array('crop', 'ratio', 'fill', 'stretch', 'center'))) { - $thumbnail_type = $config->thumbnail_type ?: 'crop'; + $thumbnail_type = $config->thumbnail_type ?: 'fill'; } - - if(!$this->isAccessible()) + if(!$config->thumbnail_quality) { - return; - } - - // If signiture height setting is omitted, create a square - if(!$height) - { - $height = $width; + $config->thumbnail_quality = 75; } - // return false if neigher attached file nor image; - if(!$this->hasUploadedFiles() && !preg_match("!get('content'))) + // If signiture height setting is omitted, create a square + if(!is_int($width)) { - return; + $width = intval($width); + } + if(!$height || (!is_int($height) && !ctype_digit(strval($height)) && $height !== 'auto')) + { + $height = $width; } // Define thumbnail information $thumbnail_path = sprintf('files/thumbnails/%s', getNumberingPath($this->comment_srl, 3)); $thumbnail_file = sprintf('%s%dx%d.%s.jpg', $thumbnail_path, $width, $height, $thumbnail_type); $thumbnail_lockfile = sprintf('%s%dx%d.%s.lock', $thumbnail_path, $width, $height, $thumbnail_type); - $thumbnail_url = Context::getRequestUri() . $thumbnail_file; + $thumbnail_url = RX_BASEURL . $thumbnail_file; + $thumbnail_file = RX_BASEDIR . $thumbnail_file; // return false if a size of existing thumbnail file is 0. otherwise return the file path if(file_exists($thumbnail_file) || file_exists($thumbnail_lockfile)) @@ -689,10 +815,35 @@ class commentItem extends BaseObject } else { - return $thumbnail_url . '?' . date('YmdHis', filemtime($thumbnail_file)); + return $thumbnail_url . '?t=' . filemtime($thumbnail_file); } } + // Call trigger for custom thumbnails. + $trigger_obj = (object)[ + 'document_srl' => $this->document_srl, + 'comment_srl' => $this->comment_srl, + 'width' => $width, + 'height' => $height, + 'image_type' => 'jpg', + 'type' => $thumbnail_type, + 'quality' => $config->thumbnail_quality, + 'filename' => $thumbnail_file, + 'url' => $thumbnail_url, + ]; + $output = ModuleHandler::triggerCall('comment.getThumbnail', 'before', $trigger_obj); + clearstatcache(true, $thumbnail_file); + if (file_exists($thumbnail_file) && filesize($thumbnail_file) > 0) + { + return $thumbnail_url . '?t=' . filemtime($thumbnail_file); + } + + // return false if neigher attached file nor image; + if(!$this->get('uploaded_count') && !preg_match("!get('content'))) + { + return; + } + // Create lockfile to prevent race condition FileHandler::writeFile($thumbnail_lockfile, '', 'w'); @@ -712,7 +863,7 @@ class commentItem extends BaseObject if($file->cover_image === 'Y' && file_exists($file->uploaded_filename)) { - $source_file = $file->uploaded_filename; + $source_file = FileHandler::getRealPath($file->uploaded_filename); break; } @@ -729,13 +880,23 @@ class commentItem extends BaseObject if(!$source_file && $first_image) { - $source_file = $first_image; + $source_file = FileHandler::getRealPath($first_image); } } - // get an image file from the doc content if no file attached. + // get an image file from the doc content if no file attached. if(!$source_file && $config->thumbnail_target !== 'attachment') { + $external_image_min_width = is_numeric($trigger_obj->width) ? min(100, round(intval($trigger_obj->width) * 0.3)) : 100; + if($trigger_obj->height === 'auto') + { + $external_image_min_height = min(100, $external_image_min_width * 0.5); + } + else + { + $external_image_min_height = is_numeric($trigger_obj->height) ? min(100, round(intval($trigger_obj->height) * 0.3)) : 100; + } + preg_match_all("!]*?src=(\"|')([^\"' ]*?)(\"|')!is", $this->get('content'), $matches, PREG_SET_ORDER); foreach($matches as $match) { @@ -766,7 +927,7 @@ class commentItem extends BaseObject if($is_img = @getimagesize($tmp_file)) { list($_w, $_h, $_t, $_a) = $is_img; - if($_w < ($width * 0.3) && $_h < ($height * 0.3)) + if($_w < ($external_image_min_width) && ($height === 'auto' || $_h < ($external_image_min_height))) { continue; } @@ -785,7 +946,11 @@ class commentItem extends BaseObject if($source_file) { - $output = FileHandler::createImageFile($source_file, $thumbnail_file, $width, $height, 'jpg', $thumbnail_type); + $output_file = FileHandler::createImageFile($source_file, $thumbnail_file, $trigger_obj->width, $trigger_obj->height, $trigger_obj->image_type, $trigger_obj->type, $trigger_obj->quality); + } + else + { + $output_file = false; } // Remove source file if it was temporary @@ -798,7 +963,7 @@ class commentItem extends BaseObject FileHandler::removeFile($thumbnail_lockfile); // Return the thumbnail path if it was successfully generated - if($output) + if($output_file) { return $thumbnail_url . '?' . date('YmdHis'); } diff --git a/modules/comment/comment.model.php b/modules/comment/comment.model.php index 127454b10..3ac2dd1e1 100644 --- a/modules/comment/comment.model.php +++ b/modules/comment/comment.model.php @@ -9,14 +9,14 @@ * @package /modules/comment * @version 0.1 */ -class commentModel extends comment +class CommentModel extends Comment { /** * Initialization * @return void */ - function init() + public function init() { } @@ -26,13 +26,11 @@ class commentModel extends comment * Print, scrap, vote-up(recommen), vote-down(non-recommend), report features added * @return void */ - function getCommentMenu() + public function getCommentMenu() { - // get the post's id number and the current login information + // get the post's id number $comment_srl = Context::get('target_srl'); $mid = Context::get('cur_mid'); - $logged_info = Context::get('logged_info'); - $act = Context::get('cur_act'); // array values for menu_list, "comment post, target, url" $menu_list = array(); @@ -43,34 +41,55 @@ class commentModel extends comment $oCommentController = getController('comment'); // feature that only member can do - if($logged_info->member_srl) + if($this->user->member_srl) { - $oCommentModel = getModel('comment'); $columnList = array('comment_srl', 'module_srl', 'member_srl', 'ipaddress'); - $oComment = $oCommentModel->getComment($comment_srl, FALSE, $columnList); + $oComment = self::getComment($comment_srl, FALSE, $columnList); $module_srl = $oComment->get('module_srl'); - $member_srl = $oComment->get('member_srl'); + $member_srl = abs($oComment->get('member_srl')); + if(!$module_srl) throw new Rhymix\Framework\Exceptions\InvalidRequest; - $oModuleModel = getModel('module'); - $comment_config = $oModuleModel->getModulePartConfig('document', $module_srl); - - if($comment_config->use_vote_up != 'N' && $member_srl != $logged_info->member_srl) + $comment_config = ModuleModel::getModulePartConfig('comment', $module_srl); + $oCommentisVoted = $oComment->getMyVote(); + if($comment_config->use_vote_up != 'N' && $member_srl != $this->user->member_srl) { - // Add a vote-up button for positive feedback - $url = sprintf("doCallModuleAction('comment','procCommentVoteUp','%s')", $comment_srl); - $oCommentController->addCommentPopupMenu($url, 'cmd_vote', '', 'javascript'); + if($oCommentisVoted === false || $oCommentisVoted < 0) + { + $url = sprintf("doCallModuleAction('comment','procCommentVoteUp','%s')", $comment_srl); + $oCommentController->addCommentPopupMenu($url, 'cmd_vote', '', 'javascript'); + } + elseif($oCommentisVoted > 0) + { + $url = sprintf("doCallModuleAction('comment','procCommentVoteUpCancel','%s')", $comment_srl); + $oCommentController->addCommentPopupMenu($url, 'cmd_cancel_vote', '', 'javascript'); + } } - if($comment_config->use_vote_down != 'N' && $member_srl != $logged_info->member_srl) + if($comment_config->use_vote_down != 'N' && $member_srl != $this->user->member_srl) { - // Add a vote-down button for negative feedback - $url = sprintf("doCallModuleAction('comment','procCommentVoteDown','%s')", $comment_srl); - $oCommentController->addCommentPopupMenu($url, 'cmd_vote_down', '', 'javascript'); + if($oCommentisVoted === false || $oCommentisVoted > 0) + { + $url = sprintf("doCallModuleAction('comment','procCommentVoteDown','%s')", $comment_srl); + $oCommentController->addCommentPopupMenu($url, 'cmd_vote_down', '', 'javascript'); + } + else if($oCommentisVoted < 0) + { + $url = sprintf("doCallModuleAction('comment','procCommentVoteDownCancel','%s')", $comment_srl); + $oCommentController->addCommentPopupMenu($url,'cmd_cancel_vote_down','','javascript'); + } } // Add the report feature against abused posts - $url = getUrl('', 'act', 'dispCommentDeclare', 'target_srl', $comment_srl); - $oCommentController->addCommentPopupMenu($url, 'cmd_declare', '', 'popup'); + if($oComment->getDeclared()) + { + $url = getUrl('', 'mid', $mid, 'act', 'dispCommentDeclare', 'target_srl', $comment_srl, 'type', 'cancel'); + $oCommentController->addCommentPopupMenu($url,'cmd_cancel_declare','','popup'); + } + else + { + $url = getUrl('', 'mid', $mid, 'act', 'dispCommentDeclare', 'target_srl', $comment_srl); + $oCommentController->addCommentPopupMenu($url,'cmd_declare','','popup'); + } } // call a trigger (after) @@ -84,16 +103,15 @@ class commentModel extends comment } // find a comment by IP matching if an administrator. - if($logged_info->is_admin == 'Y') + if($this->user->is_admin == 'Y') { - $oCommentModel = getModel('comment'); - $oComment = $oCommentModel->getComment($comment_srl); + $oComment = self::getComment($comment_srl); if($oComment->isExists()) { // Find a post of the corresponding ip address $url = getUrl('', 'module', 'admin', 'act', 'dispCommentAdminList', 'search_target', 'ipaddress', 'search_keyword', $oComment->getIpAddress()); - $oCommentController->addCommentPopupMenu($url, 'cmd_search_by_ipaddress', $icon_path, 'TraceByIpaddress'); + $oCommentController->addCommentPopupMenu($url, 'cmd_search_by_ipaddress', '', 'TraceByIpaddress'); $url = sprintf("var params = new Array(); params['ipaddress_list']='%s'; exec_xml('spamfilter', 'procSpamfilterAdminInsertDeniedIP', params, completeCallModuleAction)", $oComment->getIpAddress()); $oCommentController->addCommentPopupMenu($url, 'cmd_add_ip_to_spamfilter', '', 'javascript'); @@ -101,12 +119,10 @@ class commentModel extends comment } // Changing a language of pop-up menu - $menus = Context::get('comment_popup_menu_list'); - $menus_count = count($menus); - - for($i = 0; $i < $menus_count; $i++) + $menus = Context::get('comment_popup_menu_list') ?: []; + foreach ($menus as $menu) { - $menus[$i]->str = lang($menus[$i]->str); + $menu->str = lang($menu->str ?? ''); } // get a list of final organized pop-up menus @@ -119,7 +135,7 @@ class commentModel extends comment * @param int $comment_srl * @return bool */ - function isGranted($comment_srl) + public static function isGranted($comment_srl) { return $_SESSION['granted_comment'][$comment_srl]; } @@ -127,26 +143,36 @@ class commentModel extends comment /** * Returns the number of child comments * @param int $comment_srl + * @param array $statusList * @return int */ - function getChildCommentCount($comment_srl) + public static function getChildCommentCount($comment_srl, $statusList = []) { $args = new stdClass(); $args->comment_srl = $comment_srl; - $output = executeQuery('comment.getChildCommentCount', $args, NULL, 'master'); + if ($statusList) + { + $args->statusList = $statusList; + } + $output = executeQuery('comment.getChildCommentCount', $args); return (int) $output->data->count; } /** * Returns the number of child comments * @param int $comment_srl - * @return int + * @param array $statusList + * @return array */ - function getChildComments($comment_srl) + public static function getChildComments($comment_srl, $statusList = []) { $args = new stdClass(); $args->comment_srl = $comment_srl; - $output = executeQueryArray('comment.getChildComments', $args, NULL, 'master'); + if ($statusList) + { + $args->statusList = $statusList; + } + $output = executeQueryArray('comment.getChildComments', $args); return $output->data; } @@ -157,11 +183,12 @@ class commentModel extends comment * @param array $columnList * @return commentItem */ - function getComment($comment_srl = 0, $is_admin = FALSE, $columnList = array()) + public static function getComment($comment_srl = 0, $is_admin = FALSE, $columnList = array()) { $oComment = new commentItem($comment_srl, $columnList); if($is_admin) { + trigger_error('Called CommentModel::getComment() with $is_admin = true', \E_USER_WARNING); $oComment->setGrant(); } @@ -174,16 +201,20 @@ class commentModel extends comment * @param array $columnList * @return array */ - function getComments($comment_srl_list, $columnList = array()) + public static function getComments($comment_srl_list, $columnList = array()) { - if(is_array($comment_srl_list)) + if (!is_array($comment_srl_list)) { - $comment_srls = implode(',', $comment_srl_list); + $comment_srl_list = $comment_srl_list ? explode(',', $comment_srl_list) : array(); + } + if (!count($comment_srl_list)) + { + return array(); } // fetch from a database $args = new stdClass(); - $args->comment_srls = $comment_srls; + $args->comment_srls = $comment_srl_list; $output = executeQuery('comment.getComments', $args, $columnList); if(!$output->toBool()) { @@ -200,7 +231,6 @@ class commentModel extends comment $comment_list = array($comment_list); } - $comment_count = count($comment_list); foreach($comment_list as $key => $attribute) { if(!$attribute->comment_srl) @@ -208,7 +238,6 @@ class commentModel extends comment continue; } - $oComment = NULL; $oComment = new commentItem(); $oComment->setAttribute($attribute); @@ -217,21 +246,32 @@ class commentModel extends comment return $result; } + /** + * Get comment depth. + * + * @param int $comment_srl + * @return ?int + */ + public static function getCommentDepth(int $comment_srl): ?int + { + $output = executeQuery('comment.getCommentDepth', ['comment_srl' => $comment_srl]); + return isset($output->data->depth) ? (int)$output->data->depth : null; + } + /** * Get the total number of comments in corresponding with document_srl. * @param int $document_srl + * @param array $statusList * @return int */ - function getCommentCount($document_srl) + public static function getCommentCount($document_srl, $statusList = []) { $args = new stdClass(); $args->document_srl = $document_srl; // get the number of comments on the document module - $oDocumentModel = getModel('document'); $columnList = array('document_srl', 'module_srl'); - - $oDocument = $oDocumentModel->getDocument($document_srl, FALSE, TRUE, $columnList); + $oDocument = DocumentModel::getDocument($document_srl, FALSE, TRUE, $columnList); // return if no doc exists. if(!$oDocument->isExists()) @@ -245,7 +285,7 @@ class commentModel extends comment //check if module is using validation system $oCommentController = getController('comment'); $using_validation = $oCommentController->isModuleUsingPublishValidation($module_srl); - $module_info = getModel('module')->getModuleInfoByDocumentSrl($document_srl); + $module_info = ModuleModel::getModuleInfoByDocumentSrl($document_srl); $use_comment_massage = $module_info->comment_delete_message; if($using_validation) @@ -256,20 +296,25 @@ class commentModel extends comment { $args->status = 1; } + if ($statusList) + { + $args->statusList = $statusList; + } - $output = executeQuery('comment.getCommentCount', $args, NULL); + $output = executeQuery('comment.getCommentCount', $args); $total_count = $output->data->count; return (int) $total_count; } /** - * Get the total number of comments in corresponding with document_srl. + * Get the total number of comments posted on the given date. * @param string $date - * @param array $moduleSrlList + * @param array $moduleList + * @param array $statusList * @return int */ - function getCommentCountByDate($date = '', $moduleSrlList = array()) + public static function getCommentCountByDate($date = '', $moduleList = [], $statusList = []) { $args = new stdClass(); if($date) @@ -277,9 +322,13 @@ class commentModel extends comment $args->regDate = date('Ymd', strtotime($date)); } - if(count($moduleSrlList) > 0) + if($moduleList) { - $args->module_srl = $moduleSrlList; + $args->module_srl = $moduleList; + } + if ($statusList) + { + $args->statusList = $statusList; } $output = executeQuery('comment.getCommentCount', $args); @@ -294,15 +343,15 @@ class commentModel extends comment /** * Get the total number of comments in corresponding with module_srl. * @param int $module_srl - * @param bool $published + * @param array|bool|null $statusList (Previously $published) * @return int */ - function getCommentAllCount($module_srl, $published = false) + public static function getCommentAllCount($module_srl, $statusList = false) { $args = new stdClass(); $args->module_srl = $module_srl; - if(is_null($published)) + if ($statusList === null) { // check if module is using comment validation system $oCommentController = getController("comment"); @@ -312,9 +361,13 @@ class commentModel extends comment $args->status = 1; } } + elseif (is_array($statusList) && count($statusList)) + { + $args->statusList = $statusList; + } else { - if($published) + if($statusList === 1 || $statusList === true) { $args->status = 1; } @@ -332,22 +385,24 @@ class commentModel extends comment /** * Get the module info without duplication + * + * @deprecated + * * @return array */ - function getDistinctModules() + public static function getDistinctModules() { return array(); /* $output = executeQueryArray('comment.getDistinctModules'); $module_srls = $output->data; - $oModuleModel = getModel('module'); $result = array(); if($module_srls) { foreach($module_srls as $module) { - $module_info = $oModuleModel->getModuleInfoByModuleSrl($module->module_srl); + $module_info = ModuleModel::getModuleInfoByModuleSrl($module->module_srl); $result[$module->module_srl] = $module_info->mid; } } @@ -362,7 +417,7 @@ class commentModel extends comment * @param array $columnList * @return array */ - function getNewestCommentList($obj, $columnList = array()) + public static function getNewestCommentList($obj, $columnList = []) { $args = new stdClass(); @@ -371,28 +426,30 @@ class commentModel extends comment $obj = new stdClass(); } - if($obj->mid) + if(isset($obj->mid) && $obj->mid) { - $oModuleModel = getModel('module'); - $obj->module_srl = $oModuleModel->getModuleSrlByMid($obj->mid); + $obj->module_srl = ModuleModel::getModuleSrlByMid($obj->mid); unset($obj->mid); } // check if module_srl is an arrary. - if(is_array($obj->module_srl)) + if(isset($obj->module_srl) && is_array($obj->module_srl)) { $args->module_srl = implode(',', $obj->module_srl); } else { - $args->module_srl = $obj->module_srl; + $args->module_srl = $obj->module_srl ?? null; } - - $args->is_secret = $obj->is_secret; - $args->document_srl = $obj->document_srl; - $args->list_count = $obj->list_count; + if (isset($obj->statusList) && is_array($obj->statusList) && count($obj->statusList)) + { + $args->statusList = $obj->statusList; + } + $args->is_secret = $obj->is_secret ?? null; + $args->document_srl = $obj->document_srl ?? null; + $args->list_count = $obj->list_count ?? null; - if(strpos($args->module_srl, ",") === false) + if(is_scalar($args->module_srl) && strpos($args->module_srl, ",") === false) { if($args->module_srl) { @@ -406,37 +463,21 @@ class commentModel extends comment } } - $output = executeQuery('comment.getNewestCommentList', $args, $columnList); - + $output = executeQueryArray('comment.getNewestCommentList', $args, $columnList); if(!$output->toBool()) { return $output; } - $comment_list = $output->data; - if($comment_list) + $result = []; + foreach ($output->data as $key => $attribute) { - if(!is_array($comment_list)) + if ($attribute->comment_srl) { - $comment_list = array($comment_list); - } - - $comment_count = count($comment_list); - - foreach($comment_list as $key => $attribute) - { - if(!$attribute->comment_srl) - { - continue; - } - - $oComment = NULL; - $oComment = new commentItem(); + $oComment = new CommentItem(); $oComment->setAttribute($attribute); - $result[$key] = $oComment; } - $output->data = $result; } return $result; @@ -446,21 +487,21 @@ class commentModel extends comment * Get a comment list of the doc in corresponding woth document_srl. * @param int $document_srl * @param int $page - * @param bool $is_admin + * @param int $unused (Previously $is_admin) * @param int $count + * @param array $statusList * @return object */ - function getCommentList($document_srl, $page = 0, $is_admin = FALSE, $count = 0) + public static function getCommentList($document_srl, $page = 0, $unused = 0, $count = 0, $statusList = []) { - if(!isset($document_srl)) + if(!$document_srl) { return; } // get the number of comments on the document module - $oDocumentModel = getModel('document'); $columnList = array('document_srl', 'module_srl', 'comment_count'); - $oDocument = $oDocumentModel->getDocument($document_srl, FALSE, TRUE, $columnList); + $oDocument = DocumentModel::getDocument($document_srl, false, false, $columnList); // return if no doc exists. if(!$oDocument->isExists()) @@ -469,7 +510,8 @@ class commentModel extends comment } // return if no comment exists - if($oDocument->getCommentCount() < 1) + $document_comment_count = $oDocument->getCommentCount(); + if($document_comment_count < 1) { return; } @@ -479,12 +521,9 @@ class commentModel extends comment if(!$count) { - $comment_config = $this->getCommentConfig($module_srl); + $comment_config = self::getCommentConfig($module_srl); $comment_count = $comment_config->comment_count; - if(!$comment_count) - { - $comment_count = 50; - } + $comment_page_count = $comment_config->comment_page_count; } else { @@ -492,17 +531,22 @@ class commentModel extends comment } // get a very last page if no page exists + $total_pages = max(1, ceil($document_comment_count / $comment_count)); if(!$page) { - $page = (int) ( ($oDocument->getCommentCount() - 1) / $comment_count) + 1; + $page = (isset($comment_config->default_page) && $comment_config->default_page === 'first') ? 1 : $total_pages; + } + if($page > $total_pages) + { + $page = $total_pages; } // get a list of comments $args = new stdClass(); $args->document_srl = $document_srl; $args->list_count = $comment_count; + $args->page_count = $comment_page_count; $args->page = $page; - $args->page_count = 10; //check if module is using validation system $oCommentController = getController('comment'); @@ -511,43 +555,130 @@ class commentModel extends comment { $args->status = 1; } + if($statusList) + { + $args->statusList = $statusList; + } - // call trigger (before) + // Call trigger (before) + // This trigger can be used to set an alternative output using a different search method + unset($args->use_alternate_output); $trigger_output = ModuleHandler::triggerCall('comment.getCommentList', 'before', $args); if($trigger_output instanceof BaseObject && !$trigger_output->toBool()) { - return $output; - } - - $output = executeQueryArray('comment.getCommentPageList', $args); - - // return if an error occurs in the query results - if(!$output->toBool()) - { - return; + return $trigger_output; } - // insert data into CommentPageList table if the number of results is different from stored comments - if(!$output->data) + // If an alternate output is set, use it instead of running the default queries + if (isset($args->use_alternate_output) && $args->use_alternate_output instanceof BaseObject) { - $this->fixCommentList($oDocument->get('module_srl'), $document_srl); + $output = $args->use_alternate_output; + } + else + { + // Execute normal query $output = executeQueryArray('comment.getCommentPageList', $args); if(!$output->toBool()) { return; } + + // insert data into CommentPageList table if the number of results is different from stored comments + if(!$output->data) + { + self::fixCommentList($oDocument->get('module_srl'), $document_srl); + $output = executeQueryArray('comment.getCommentPageList', $args); + if(!$output->toBool()) + { + return; + } + } } - // call trigger (after) - $trigger_output = ModuleHandler::triggerCall('comment.getCommentList', 'after', $output); - if($trigger_output instanceof BaseObject && !$trigger_output->toBool()) - { - return $trigger_output; - } - + // Call trigger (after) + // This trigger can be used to modify search results + ModuleHandler::triggerCall('comment.getCommentList', 'after', $output); return $output; } + /** + * Find out which page a comment is on + * @param int $document_srl + * @param int $comment_srl + * @param int $count + * @param array $statusList + * @return int + */ + public static function getCommentPage($document_srl, $comment_srl, $count = 0, $statusList = []) + { + // Check the document + $columnList = array('document_srl', 'module_srl', 'comment_count'); + $oDocument = DocumentModel::getDocument($document_srl, false, false, $columnList); + if(!$oDocument->isExists()) + { + return 0; + } + + // return if no comment exists + $document_comment_count = $oDocument->getCommentCount(); + if($document_comment_count < 1) + { + return 0; + } + + // Get the comment count per page + if(!$count) + { + $module_srl = $oDocument->get('module_srl'); + $comment_config = self::getCommentConfig($module_srl); + $comment_count = $comment_config->comment_count; + } + else + { + $comment_count = $count; + } + + // Get the number of pages + $total_pages = max(1, ceil($document_comment_count / $comment_count)); + if ($total_pages == 1) + { + return 1; + } + + // Find out which page the comment is on + $args = new stdClass(); + $args->document_srl = $document_srl; + $args->comment_srl = $comment_srl; + if ($statusList) + { + $args->statusList = $statusList; + } + $output = executeQuery('comment.getCommentPageItem', $args); + if (is_object($output->data)) + { + $item = $output->data; + $args->head = $item->head; + $args->arrange = $item->arrange; + if (getController('comment')->isModuleUsingPublishValidation($module_srl)) + { + $args->status = 1; + } + $output = executeQuery('comment.getCommentPage', $args); + if ($output->toBool() && $output->data->count) + { + return max(1, ceil($output->data->count / $comment_count)); + } + else + { + return 0; + } + } + else + { + return 0; + } + } + /** * Update a list of comments in corresponding with document_srl * Take care of previously used data than GA version @@ -555,7 +686,7 @@ class commentModel extends comment * @param int $document_srl * @return void */ - function fixCommentList($module_srl, $document_srl) + public static function fixCommentList($module_srl, $document_srl) { // create a lock file to prevent repeated work when performing a batch job $lock_file = "./files/cache/tmp/lock." . $document_srl; @@ -606,7 +737,7 @@ class commentModel extends comment // generate a list $list[$comment_srl] = $source_list[$i]; - if($parent_srl) + if($parent_srl && isset($list[$parent_srl])) { $list[$parent_srl]->child[] = &$list[$comment_srl]; } @@ -615,7 +746,7 @@ class commentModel extends comment $root->child[] = &$list[$comment_srl]; } } - $this->_arrangeComment($comment_list, $root->child, 0, NULL); + self::_arrangeComment($comment_list, $root->child, 0, NULL); // insert values to the database if(count($comment_list)) @@ -647,9 +778,9 @@ class commentModel extends comment * @param object $parent * @return void */ - function _arrangeComment(&$comment_list, $list, $depth, $parent = NULL) + public static function _arrangeComment(&$comment_list, $list, $depth, $parent = NULL) { - if(!count($list)) + if(!is_array($list) || !count($list)) { return; } @@ -671,7 +802,7 @@ class commentModel extends comment { $val->depth = $depth; $comment_list[$val->comment_srl] = $val; - $this->_arrangeComment($comment_list, $val->child, $depth + 1, $val); + self::_arrangeComment($comment_list, $val->child, $depth + 1, $val); unset($val->child); } else @@ -688,35 +819,37 @@ class commentModel extends comment * @param array $columnList * @return object */ - function getTotalCommentList($obj, $columnList = array()) + public static function getTotalCommentList($obj, $columnList = array()) { $query_id = 'comment.getTotalCommentList'; // Variables $args = new stdClass(); - $args->sort_index = 'list_order'; - $args->page = $obj->page ? $obj->page : 1; - $args->list_count = $obj->list_count ? $obj->list_count : 20; - $args->page_count = $obj->page_count ? $obj->page_count : 10; - $args->s_module_srl = $obj->module_srl; - $args->exclude_module_srl = $obj->exclude_module_srl; - $args->statusList = $obj->statusList; - if ($obj->is_secret) + $args->sort_index = 'comments.list_order'; + $args->page = $obj->page ?? 1; + $args->list_count = $obj->list_count ?? 20; + $args->page_count = $obj->page_count ?? 10; + $args->s_member_srl = $obj->member_srl ?? null; + $args->s_module_srl = $obj->module_srl ?? null; + $args->exclude_module_srl = $obj->exclude_module_srl ?? null; + $args->statusList = $obj->statusList ?? null; + $args->document_statusList = $obj->document_statusList ?? null; + if (isset($obj->is_secret) && $obj->is_secret) { $args->s_is_secret = $obj->is_secret; } // check if module is using comment validation system - $oCommentController = getController("comment"); - $is_using_validation = $oCommentController->isModuleUsingPublishValidation($obj->module_srl); + $oCommentController = CommentController::getInstance(); + $is_using_validation = $oCommentController->isModuleUsingPublishValidation($args->s_module_srl); if($is_using_validation) { $args->s_is_published = 1; } // Search options - $search_target = $obj->search_target ? $obj->search_target : trim(Context::get('search_target')); - $search_keyword = $obj->search_keyword ? $obj->search_keyword : trim(Context::get('search_keyword')); + $search_target = (isset($obj->search_target) && $obj->search_target) ? $obj->search_target : trim(Context::get('search_target') ?? ''); + $search_keyword = (isset($obj->search_keyword) && $obj->search_keyword) ? $obj->search_keyword : trim(Context::get('search_keyword') ?? ''); if($search_target && $search_keyword) { switch($search_target) @@ -724,7 +857,7 @@ class commentModel extends comment case 'content' : if($search_keyword) { - $search_keyword = str_replace(' ', '%', $search_keyword); + $search_keyword = trim(utf8_normalize_spaces($search_keyword)); } $args->s_content = $search_keyword; @@ -738,7 +871,6 @@ class commentModel extends comment $args->s_user_id = $search_keyword; $query_id = 'comment.getTotalCommentListWithinMember'; - $args->sort_index = 'comments.list_order'; break; case 'user_name' : @@ -816,8 +948,34 @@ class commentModel extends comment } } - // comment.getTotalCommentList query execution - $output = executeQueryArray($query_id, $args, $columnList); + // Call trigger (before) + // This trigger can be used to set an alternative output using a different search method + unset($args->use_alternate_output); + $output = ModuleHandler::triggerCall('comment.getTotalCommentList', 'before', $args); + if($output instanceof BaseObject && !$output->toBool()) + { + return $output; + } + + // Use simple query without JOIN if there are no document-related conditions. + if ($query_id === 'comment.getTotalCommentList') + { + if (!$args->s_module_srl && !$args->exclude_module_srl && !$args->document_statusList) + { + $query_id = 'comment.getTotalCommentListWithoutJoin'; + } + } + + // If an alternate output is set, use it instead of running the default queries + if (isset($args->use_alternate_output) && $args->use_alternate_output instanceof BaseObject) + { + $output = $args->use_alternate_output; + } + // Execute normal query + else + { + $output = executeQueryArray($query_id, $args, $columnList); + } // return when no result or error occurance if(!$output->toBool() || !count($output->data)) @@ -825,14 +983,48 @@ class commentModel extends comment return $output; } + // Fill in document-related fields if a simple query was used. + if ($query_id === 'comment.getTotalCommentListWithoutJoin') + { + $document_srl_list = []; + foreach ($output->data as $val) + { + $document_srl_list[$val->document_srl] = true; + } + if (count($document_srl_list)) + { + $document_list = DocumentModel::getDocuments(array_keys($document_srl_list), false, false, [ + 'document_srl', 'module_srl', 'member_srl', 'user_id', 'user_name', 'nick_name', 'title', + ]); + foreach ($document_list as $val) + { + $document_srl_list[$val->document_srl] = $val; + } + foreach ($output->data as $val) + { + if (isset($document_srl_list[$val->document_srl]) && is_object($document_srl_list[$val->document_srl])) + { + $val->module_srl = $document_srl_list[$val->document_srl]->get('module_srl'); + $val->document_member_srl = $document_srl_list[$val->document_srl]->get('member_srl'); + $val->document_user_id = $document_srl_list[$val->document_srl]->get('user_id'); + $val->document_user_name = $document_srl_list[$val->document_srl]->get('user_name'); + $val->document_nick_name = $document_srl_list[$val->document_srl]->get('nick_name'); + $val->document_title = $document_srl_list[$val->document_srl]->get('title'); + } + } + } + } + foreach($output->data as $key => $val) { - unset($_oComment); $_oComment = new CommentItem(0); $_oComment->setAttribute($val); $output->data[$key] = $_oComment; } + // Call trigger (after) + // This trigger can be used to modify search results + ModuleHandler::triggerCall('comment.getTotalCommentList', 'after', $output); return $output; } @@ -841,14 +1033,19 @@ class commentModel extends comment * @param object $obj * @return int */ - function getTotalCommentCount($obj) + public static function getTotalCommentCount($obj) { $query_id = 'comment.getTotalCommentCountByGroupStatus'; // Variables $args = new stdClass(); - $args->s_module_srl = $obj->module_srl; - $args->exclude_module_srl = $obj->exclude_module_srl; + $args->s_module_srl = $obj->module_srl ?? null; + $args->exclude_module_srl = $obj->exclude_module_srl ?? null; + $args->statusList = $obj->statusList ?? null; + if (isset($obj->is_secret) && $obj->is_secret) + { + $args->s_is_secret = $obj->is_secret; + } // Search options $search_target = $obj->search_target ? $obj->search_target : trim(Context::get('search_target')); @@ -861,7 +1058,7 @@ class commentModel extends comment case 'content' : if($search_keyword) { - $search_keyword = str_replace(' ', '%', $search_keyword); + $search_keyword = trim(utf8_normalize_spaces($search_keyword)); } $args->s_content = $search_keyword; @@ -951,28 +1148,31 @@ class commentModel extends comment * @param int $module_srl * @return object */ - function getCommentConfig($module_srl) + public static function getCommentConfig($module_srl) { - $oModuleModel = getModel('module'); - $comment_config = $oModuleModel->getModulePartConfig('comment', $module_srl); + $comment_config = ModuleModel::getModulePartConfig('comment', $module_srl); if(!is_object($comment_config)) { $comment_config = new stdClass(); } - if(!isset($comment_config->comment_count)) + if(!isset($comment_config->comment_count) || !$comment_config->comment_count) { $comment_config->comment_count = 50; } + if(!isset($comment_config->comment_page_count) || !$comment_config->comment_page_count) + { + $comment_config->comment_page_count = 10; + } return $comment_config; } - + /** * Return a list of voting member * @return void */ - function getCommentVotedMemberList() + public function getCommentVotedMemberList() { $comment_srl = Context::get('comment_srl'); if(!$comment_srl) @@ -986,16 +1186,14 @@ class commentModel extends comment $point = 1; } - $oCommentModel = getModel('comment'); - $oComment = $oCommentModel->getComment($comment_srl, FALSE, FALSE); + $oComment = self::getComment($comment_srl); $module_srl = $oComment->get('module_srl'); if(!$module_srl) { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - $oModuleModel = getModel('module'); - $comment_config = $oModuleModel->getModulePartConfig('comment', $module_srl); + $comment_config = ModuleModel::getModulePartConfig('comment', $module_srl); $args = new stdClass(); @@ -1025,12 +1223,11 @@ class commentModel extends comment return $output; } - $oMemberModel = getModel('member'); if($output->data) { foreach($output->data as $k => $d) { - $profile_image = $oMemberModel->getProfileImage($d->member_srl); + $profile_image = MemberModel::getProfileImage($d->member_srl); $output->data[$k]->src = $profile_image->src; } } @@ -1042,7 +1239,7 @@ class commentModel extends comment * Return a secret status by secret field * @return array */ - function getSecretNameList() + public static function getSecretNameList() { global $lang; @@ -1059,41 +1256,89 @@ class commentModel extends comment /** * Get the total number of comments in corresponding with member_srl. * @param int $member_srl + * @param array $statusList * @return int */ - function getCommentCountByMemberSrl($member_srl) + public static function getCommentCountByMemberSrl($member_srl, $statusList = []) { $args = new stdClass(); $args->member_srl = $member_srl; + if ($statusList) + { + $args->statusList = $statusList; + } $output = executeQuery('comment.getCommentCountByMemberSrl', $args); return (int) $output->data->count; } + /** + * Get the total number of comments posted on the given document by the given member. + * + * @param int $document_srl + * @param int $member_srl + * @param array $statusList + * @return int + */ + public static function getCommentCountByDocumentSrlAndMemberSrl($document_srl, $member_srl, $statusList = []) + { + $args = new stdClass(); + $args->document_srl = $document_srl; + $args->member_srl = $member_srl; + if ($statusList) + { + $args->statusList = $statusList; + } + $output = executeQuery('comment.getCommentCountByMemberSrl', $args); + return (int) $output->data->count; + } /** - * Get comment list of the doc in corresponding woth member_srl. + * Get the list of comments posted by the given member. + * * @param int $member_srl * @param array $columnList - * @param int $page - * @param bool $is_admin - * @param int $count - * @return object + * @param int $unused1 (Previously $page) + * @param int $unused2 (Previously $is_admin) + * @param int $list_count + * @param array $statusList + * @return array */ - function getCommentListByMemberSrl($member_srl, $columnList = array(), $page = 0, $is_admin = FALSE, $count = 0) + public static function getCommentListByMemberSrl($member_srl, $columnList = [], $unused1 = 0, $unused2 = 0, $list_count = 0, $statusList = []) { $args = new stdClass(); $args->member_srl = $member_srl; - $args->list_count = $count; - $output = executeQuery('comment.getCommentListByMemberSrl', $args, $columnList); - $comment_list = $output->data; - - if(!$comment_list) return array(); - if(!is_array($comment_list)) $comment_list = array($comment_list); - - return $comment_list; - + $args->list_count = $list_count; + if ($statusList) + { + $args->statusList = $statusList; + } + $output = executeQueryArray('comment.getCommentListByMemberSrl', $args, $columnList); + return $output->data ?? []; } + /** + * Get the list of comments posted on the given document by the given member. + * + * @param int $document_srl + * @param int $member_srl + * @param int $list_count + * @param array $columnList + * @param array $statusList + * @return array + */ + public static function getCommentListByDocumentSrlAndMemberSrl($document_srl, $member_srl, $list_count = 0, $columnList = [], $statusList = []) + { + $args = new stdClass(); + $args->document_srl = $document_srl; + $args->member_srl = $member_srl; + $args->list_count = $list_count; + if ($statusList) + { + $args->statusList = $statusList; + } + $output = executeQueryArray('comment.getCommentListByMemberSrl', $args, $columnList); + return $output->data ?? []; + } } /* End of file comment.model.php */ /* Location: ./modules/comment/comment.model.php */ diff --git a/modules/comment/comment.view.php b/modules/comment/comment.view.php index 85052b8f9..87f4226dd 100644 --- a/modules/comment/comment.view.php +++ b/modules/comment/comment.view.php @@ -9,7 +9,7 @@ * @package /modules/comment * @version 0.1 */ -class commentView extends comment +class CommentView extends Comment { /** @@ -43,19 +43,59 @@ class commentView extends comment } // get the comment configuration - $oCommentModel = getModel('comment'); - $comment_config = $oCommentModel->getCommentConfig($current_module_srl); + $comment_config = CommentModel::getCommentConfig($current_module_srl); + if(!$comment_config) + { + $comment_config = new stdClass(); + } + if(!isset($comment_config->cmd_comment_validation)) + { + $comment_config->cmd_comment_validation = 'N'; + } + if(!isset($comment_config->use_vote_up)) + { + $comment_config->use_vote_up = 'Y'; + } + if(!isset($comment_config->use_vote_down)) + { + $comment_config->use_vote_down = 'Y'; + } + if(!isset($comment_config->allow_vote_from_same_ip)) + { + $comment_config->allow_vote_from_same_ip = 'N'; + } + if(!isset($comment_config->allow_declare_from_same_ip)) + { + $comment_config->allow_declare_from_same_ip = 'N'; + } + + if ($current_module_srl) + { + $module_info = ModuleModel::getModuleInfoByModuleSrl($current_module_srl); + if (!isset($comment_config->allow_vote_cancel)) + { + $comment_config->allow_vote_cancel = (($module_info->cancel_vote ?? 'N') === 'Y') ? 'Y' : 'N'; + } + if (!isset($comment_config->allow_vote_non_member)) + { + $comment_config->allow_vote_non_member = (($module_info->non_login_vote ?? 'N') === 'Y') ? 'Y' : 'N'; + } + if (!isset($comment_config->allow_declare_cancel)) + { + $comment_config->allow_declare_cancel = (($module_info->cancel_vote ?? 'N') === 'Y') ? 'Y' : 'N'; + } + } + Context::set('comment_config', $comment_config); // get a group list - $oMemberModel = getModel('member'); - $group_list = $oMemberModel->getGroups(); + $group_list = MemberModel::getGroups(); Context::set('group_list', $group_list); // Set a template file $oTemplate = TemplateHandler::getInstance(); $tpl = $oTemplate->compile($this->module_path . 'tpl', 'comment_module_config'); - $obj .= $tpl; + $obj = $tpl . $obj; return new BaseObject(); } @@ -69,23 +109,20 @@ class commentView extends comment $this->setLayoutFile('popup_layout'); $comment_srl = Context::get('target_srl'); - $oMemberModel = getModel('member'); // A message appears if the user is not logged-in - if(!$oMemberModel->isLogged()) + if(!$this->user->member_srl) { throw new Rhymix\Framework\Exceptions\MustLogin; } - // Create the comment object. - $oCommentModel = getModel('comment'); // Creates an object for displaying the selected comment - $oComment = $oCommentModel->getComment($comment_srl); + $oComment = CommentModel::getComment($comment_srl); if(!$oComment->isExists()) { throw new Rhymix\Framework\Exceptions\TargetNotFound; } // Check permissions - if(!$oComment->isAccessible()) + if(!$oComment->isAccessible(true)) { throw new Rhymix\Framework\Exceptions\NotPermitted; } diff --git a/modules/comment/conf/info.xml b/modules/comment/conf/info.xml index 3f128ad65..ebd9fb6d9 100644 --- a/modules/comment/conf/info.xml +++ b/modules/comment/conf/info.xml @@ -18,8 +18,8 @@ Модуль для управления комментариями форума/блога. 管理討論板或部落格回覆的模組。 Pano ve blog yorumlarını yönetme modülü - 1.7 - 2013-11-27 + RX_VERSION + RX_CORE content diff --git a/modules/comment/conf/module.xml b/modules/comment/conf/module.xml index a30511528..00e6efbe0 100644 --- a/modules/comment/conf/module.xml +++ b/modules/comment/conf/module.xml @@ -3,29 +3,38 @@ - + - - - - - + + + + + - - - + + + + - + - - - + + + + + + + + + + + Comment diff --git a/modules/comment/lang/en.php b/modules/comment/lang/en.php index 4769af5c2..a7f24a429 100644 --- a/modules/comment/lang/en.php +++ b/modules/comment/lang/en.php @@ -2,13 +2,21 @@ $lang->cmd_comment_do = 'I want to'; $lang->comment_list = 'Comments List'; $lang->cmd_toggle_checked_comment = 'Invert Selection'; -$lang->cmd_delete_checked_comment = 'Delete selected item'; +$lang->cmd_delete_checked_comment = 'Delete Selected Item'; $lang->trash = 'Recycle Bin'; $lang->cmd_trash = 'Move to Recycle Bin'; -$lang->comment_count = 'Number of Comments'; -$lang->about_comment_count = 'Display comments and if the number of them is over a specified number, move to the comment list.'; +$lang->comment_count = 'Number of Comments per Page'; +$lang->comment_page_count = 'Number of Pages'; +$lang->comment_default_page = 'Default Page'; +$lang->comment_default_page_first = 'First Page'; +$lang->comment_default_page_last = 'Last Page'; +$lang->about_comment_count = 'Set the number of comments to show on each page.'; +$lang->about_comment_page_count = 'Set the number of pagination links to show at the bottom.'; +$lang->max_thread_depth = 'Maximum Thread Depth'; +$lang->about_max_thread_depth = '0: Unlimited, 1: No replies, 2 or more: Allow replies'; $lang->msg_cart_is_null = 'Please select an article to delete.'; $lang->msg_checked_comment_is_deleted = '%d comment(s) is(are) successfully deleted.'; +$lang->msg_exceeds_max_thread_depth = 'You have exceeded the maximum thread depth. Please leave a reply elsewhere in the thread.'; $lang->search_target_list['content'] = 'Content'; $lang->search_target_list['user_id'] = 'ID'; $lang->search_target_list['user_name'] = 'Name'; @@ -21,20 +29,33 @@ $lang->search_target_list['last_update'] = 'Last update'; $lang->search_target_list['ipaddress'] = 'IP Address'; $lang->search_target_list['is_secret'] = 'Status'; $lang->no_text_comment = 'No text in this comment.'; +$lang->no_text_document = 'No text in this post.'; $lang->secret_name_list['Y'] = 'Secret'; $lang->secret_name_list['N'] = 'Public'; +$lang->published_name_list[0] = 'Waiting'; +$lang->published_name_list[1] = 'Published'; +$lang->published_name_list[2] = 'Secret'; +$lang->published_name_list[3] = 'Embargo'; +$lang->published_name_list[4] = 'Recycle Bin'; +$lang->published_name_list[5] = 'Blocked'; +$lang->published_name_list[6] = 'Blocked'; +$lang->published_name_list[7] = 'Deleted'; +$lang->published_name_list[8] = 'Deleted'; +$lang->published_name_list[9] = 'Other'; $lang->published_name_list['Y'] = 'Published'; $lang->published_name_list['N'] = 'Unpublished'; $lang->comment_manager = 'Manage Selected Comment'; $lang->selected_comment = 'Selected Comment'; $lang->cmd_comment_validation = 'Use comment validation'; -$lang->about_comment_validation = 'If you want to use comment validation before displaying on your module frontend select USE, otherwise select NOT USE.'; -$lang->published = 'Publish status'; +$lang->about_comment_validation = 'If you want to use comment validation before displaying on your module frontend, select USE; otherwise, select NOT USE.'; +$lang->published = 'Publish Status'; $lang->cmd_publish = 'Publish'; $lang->cmd_unpublish = 'Unpublish'; $lang->select_module = 'Select Module'; $lang->page = 'Page'; -$lang->msg_not_selected_comment = 'There are no selected comment.'; +$lang->msg_not_selected_comment = 'There are no selected comments.'; +$lang->msg_admin_comment_no_delete = 'You cannot delete the superadmin\'s comments.'; +$lang->msg_admin_c_comment_no_delete = 'You cannot delete this comment due to the supperadmin reply.'; $lang->improper_comment_declare = 'Report an improper comment'; $lang->declaring_user = 'Reporter'; $lang->improper_comment_declare_reason = 'Reason'; @@ -47,7 +68,11 @@ $lang->improper_comment_reasons['pornography'] = 'Pornography.'; $lang->improper_comment_reasons['privacy'] = 'Privacy issue.'; $lang->improper_comment_reasons['others'] = 'Others (Write your own)'; $lang->about_improper_comment_declare = 'Write here why you report this comment as an improper thing.'; +$lang->msg_censored_comment = 'This comment has been hidden.'; +$lang->msg_admin_censored_comment = 'This comment has been hidden by an administrator.'; $lang->msg_deleted_comment = 'This comment has been deleted.'; $lang->msg_admin_deleted_comment = 'This comment has been deleted by an administrator.'; $lang->msg_no_text_comment = 'This comment contains no text.'; -$lang->msg_comment_notify_mail = "[%s] A new comment was posted on document: \" %s \""; +$lang->msg_comment_notify_mail = "[%s] A new comment on the post: \" %s \""; +$lang->msg_admin_comment_no_move_to_trash = 'You have no permission to move the superadmin\'s comments to the trash bin.'; +$lang->msg_module_srl_not_exists = 'Module serial number not found.'; diff --git a/modules/comment/lang/ko.php b/modules/comment/lang/ko.php index e4c71190c..a5a6005ed 100644 --- a/modules/comment/lang/ko.php +++ b/modules/comment/lang/ko.php @@ -6,9 +6,17 @@ $lang->cmd_delete_checked_comment = '선택항목 삭제'; $lang->trash = '휴지통'; $lang->cmd_trash = '휴지통으로 이동'; $lang->comment_count = '댓글 수'; +$lang->comment_page_count = '댓글 페이지 수'; +$lang->comment_default_page = '댓글 기본 페이지'; +$lang->comment_default_page_first = '첫 페이지'; +$lang->comment_default_page_last = '마지막 페이지'; $lang->about_comment_count = '댓글을 정해진 수 만큼만 표시하고, 그 이상일 경우 페이지 번호를 표시해서 이동할 수 있게 합니다.'; +$lang->about_comment_page_count = '하단에 표시할 페이지 링크 수를 설정할 수 있습니다.'; +$lang->max_thread_depth = '대댓글 최대 깊이'; +$lang->about_max_thread_depth = '0: 무제한, 1: 대댓글 금지, 2 이상: 대댓글 허용'; $lang->msg_cart_is_null = '삭제할 글을 선택해주세요.'; $lang->msg_checked_comment_is_deleted = '%d개의 댓글을 삭제했습니다.'; +$lang->msg_exceeds_max_thread_depth = '대댓글 최대 깊이를 초과했습니다. 다른 자리에 댓글을 남겨 주세요.'; $lang->search_target_list['content'] = '내용'; $lang->search_target_list['user_id'] = '아이디'; $lang->search_target_list['user_name'] = '이름'; @@ -21,10 +29,21 @@ $lang->search_target_list['last_update'] = '최근수정일 '; $lang->search_target_list['ipaddress'] = 'IP 주소'; $lang->search_target_list['is_secret'] = '상태'; $lang->no_text_comment = '글자가 없는 댓글입니다.'; +$lang->no_text_document = '글자가 없는 게시글입니다.'; $lang->secret_name_list['Y'] = '비밀'; $lang->secret_name_list['N'] = '공개'; -$lang->published_name_list['Y'] = '발행완료'; -$lang->published_name_list['N'] = '발행대기'; +$lang->published_name_list[0] = '대기'; +$lang->published_name_list[1] = '발행'; +$lang->published_name_list[2] = '비밀'; +$lang->published_name_list[3] = '엠바고'; +$lang->published_name_list[4] = '휴지통'; +$lang->published_name_list[5] = '차단됨'; +$lang->published_name_list[6] = '차단됨'; +$lang->published_name_list[7] = '삭제됨'; +$lang->published_name_list[8] = '삭제됨'; +$lang->published_name_list[9] = '기타'; +$lang->published_name_list['Y'] = '발행'; +$lang->published_name_list['N'] = '대기'; $lang->comment_manager = '선택한 댓글 관리'; $lang->selected_comment = '선택한 댓글'; $lang->cmd_comment_validation = '승인 후 공개'; diff --git a/modules/comment/lang/vi.php b/modules/comment/lang/vi.php index 52b1804b3..b4c17041f 100644 --- a/modules/comment/lang/vi.php +++ b/modules/comment/lang/vi.php @@ -6,6 +6,7 @@ $lang->comment_count = 'Số bình luận'; $lang->about_comment_count = 'Hiển thị số bình luận được gửi, và nó sẽ tạo một danh sách nếu có nhiều bình luận.'; $lang->msg_cart_is_null = 'Xin hãy chọn một bài viết để xóa.'; $lang->msg_checked_comment_is_deleted = '%d bình luận đã được xóa.'; +$lang->search_target_list['email_address'] = 'Địa chỉ email'; $lang->search_target_list['content'] = 'Nội dung'; $lang->search_target_list['user_id'] = 'ID người gửi'; $lang->search_target_list['user_name'] = 'Tên'; @@ -14,3 +15,43 @@ $lang->search_target_list['homepage'] = 'Trang chủ'; $lang->search_target_list['regdate'] = 'Ngày'; $lang->search_target_list['last_update'] = 'Cập nhật lần cuối'; $lang->search_target_list['ipaddress'] = 'IP'; +$lang->search_target_list['is_secret'] = 'Trạng thái'; +$lang->trash = 'Thùng rác'; +$lang->cmd_trash = 'Bỏ vào thùng rác'; +$lang->comment_page_count = 'Số trang bình luận'; +$lang->comment_default_page = 'Trang bình luận cơ bản'; +$lang->comment_default_page_first = 'Trang đầu tiên'; +$lang->comment_default_page_last = 'Trang cuối cùng'; +$lang->about_comment_page_count = 'Bạn có thể thiết lập số link của trang sẽ hiện ở phía dưới.'; +$lang->no_text_comment = 'Bình luận không có chữ.'; +$lang->no_text_document = 'Bài viết không có chữ.'; +$lang->comment_manager = 'Quản lý bình luận đã chọn'; +$lang->selected_comment = 'Bình luận đã chọn'; +$lang->cmd_comment_validation = 'Công bố sau khi phê duyệt'; +$lang->about_comment_validation = 'Bình luận sẽ hiển thị sau khi được quản lý phê duyệt.'; +$lang->published = 'Trạng thái phát hành'; +$lang->cmd_publish = 'Phát hành'; +$lang->cmd_unpublish = 'Ngưng phát hành'; +$lang->select_module = 'Chọn mô-đun'; +$lang->page = 'Trang'; +$lang->msg_not_selected_comment = 'Không có bình luận nào được chọn.'; +$lang->msg_admin_comment_no_delete = 'Bạn không thể xóa bình luận của quản lý cấp cao.'; +$lang->msg_admin_c_comment_no_delete = 'Bình luận này là của quản lý cấp cao nên không thể xóa được.'; +$lang->improper_comment_declare = 'Báo cáo bình luận không phù hợp'; +$lang->declaring_user = 'Người báo cáo'; +$lang->improper_comment_declare_reason = 'Lý do báo cáo'; +$lang->improper_comment_reasons['advertisement'] = 'Quảng cáo không phù hợp với chủ đề và nội dung của bài.'; +$lang->improper_comment_reasons['theme'] = 'Bài viết không phù hợp với chủ đề.'; +$lang->improper_comment_reasons['bad_word'] = 'Có những lời nói thô tục quá mức.'; +$lang->improper_comment_reasons['violence'] = 'Có nội dung mang tính bạo lực.'; +$lang->improper_comment_reasons['racism'] = 'Có nội dung phân biệt chủng tộc.'; +$lang->improper_comment_reasons['pornography'] = 'Có chứa tài liệu khiêu dâm.'; +$lang->improper_comment_reasons['privacy'] = 'Có thông tin cá nhân nhạy cảm.'; +$lang->improper_comment_reasons['others'] = 'Khác(Tự viết)'; +$lang->about_improper_comment_declare = 'Hãy viết lý do bạn báo cáo bình luận, chúng tôi sẽ xem xét và xử lý.'; +$lang->msg_deleted_comment = 'Bình luận bị xóa.'; +$lang->msg_admin_deleted_comment = 'Bình luận mà người quản lý đã xóa.'; +$lang->msg_no_text_comment = 'Bình luận không có chữ.'; +$lang->msg_comment_notify_mail = '[%s] Có bình luận mới được đăng : %s'; +$lang->msg_admin_comment_no_move_to_trash = 'Bạn không có quyền bỏ bình luận của người quản lý cấp cao vào thùng rác.'; +$lang->msg_module_srl_not_exists = 'Số mô-đun không tồn tại.'; diff --git a/modules/comment/queries/deleteCommentDeclaredLog.xml b/modules/comment/queries/deleteCommentDeclaredLog.xml deleted file mode 100644 index 153caf2d4..000000000 --- a/modules/comment/queries/deleteCommentDeclaredLog.xml +++ /dev/null @@ -1,8 +0,0 @@ - - -
    {$lang->no} {$lang->module_category}{$lang->mid}{$lang->domain} /{$lang->url} {$lang->browser_title}{$lang->board_special_features} {$lang->regdate} {$lang->cmd_edit}
    {$no}{$val->module_srl} {$lang->virtual_site} @@ -29,13 +31,32 @@ {$module_category[$val->module_category_srl]->title} + {@ + if (isset($val->domain_srl) && $val->domain_srl > -1 && !isset($val->domain)): + $val->domain = lang('deleted_domain'); + endif; + } + {$val->domain ?? ''}{\RX_BASEURL} + {$val->mid}{$val->browser_title}{$val->browser_title} + + + + + + + + + + {zdate($val->regdate,"Y-m-d")} - {$lang->cmd_setup} - {$lang->cmd_copy} - {$lang->cmd_delete} + {$lang->cmd_setup}   + {$lang->cmd_copy}   + {$lang->cmd_delete}
    - - - - - diff --git a/modules/comment/queries/deleteDeclaredCommentLog.xml b/modules/comment/queries/deleteDeclaredCommentLog.xml new file mode 100644 index 000000000..c3c812980 --- /dev/null +++ b/modules/comment/queries/deleteDeclaredCommentLog.xml @@ -0,0 +1,8 @@ + + +
    + + + + + diff --git a/modules/comment/queries/deleteModuleComments.xml b/modules/comment/queries/deleteModuleComments.xml index 809ec9294..40ebc8e94 100644 --- a/modules/comment/queries/deleteModuleComments.xml +++ b/modules/comment/queries/deleteModuleComments.xml @@ -1,4 +1,4 @@ - +
    diff --git a/modules/comment/queries/getAllComments.xml b/modules/comment/queries/getAllComments.xml index b220598a3..77e7df255 100644 --- a/modules/comment/queries/getAllComments.xml +++ b/modules/comment/queries/getAllComments.xml @@ -1,14 +1,15 @@ - -
    - - - - + +
    + + + + - - - - + + + + + diff --git a/modules/comment/queries/getChildCommentCount.xml b/modules/comment/queries/getChildCommentCount.xml index 7ea9ce02e..ce16af34b 100644 --- a/modules/comment/queries/getChildCommentCount.xml +++ b/modules/comment/queries/getChildCommentCount.xml @@ -7,5 +7,6 @@ + diff --git a/modules/comment/queries/getChildComments.xml b/modules/comment/queries/getChildComments.xml index 6a8ec8b89..d9fb5dc00 100644 --- a/modules/comment/queries/getChildComments.xml +++ b/modules/comment/queries/getChildComments.xml @@ -8,5 +8,6 @@ + diff --git a/modules/comment/queries/getCommentCount.xml b/modules/comment/queries/getCommentCount.xml index b426c47c6..2a9611f02 100644 --- a/modules/comment/queries/getCommentCount.xml +++ b/modules/comment/queries/getCommentCount.xml @@ -5,11 +5,11 @@ - - - - - + + + + + diff --git a/modules/comment/queries/getCommentCountByMemberSrl.xml b/modules/comment/queries/getCommentCountByMemberSrl.xml index f81db4647..4662a6d1d 100644 --- a/modules/comment/queries/getCommentCountByMemberSrl.xml +++ b/modules/comment/queries/getCommentCountByMemberSrl.xml @@ -6,6 +6,10 @@ - + + + + + diff --git a/modules/comment/queries/getCommentDepth.xml b/modules/comment/queries/getCommentDepth.xml new file mode 100644 index 000000000..9748581b8 --- /dev/null +++ b/modules/comment/queries/getCommentDepth.xml @@ -0,0 +1,11 @@ + + +
    + + + + + + + + diff --git a/modules/comment/queries/getCommentList.xml b/modules/comment/queries/getCommentList.xml index 490d40f0a..53590a402 100644 --- a/modules/comment/queries/getCommentList.xml +++ b/modules/comment/queries/getCommentList.xml @@ -9,6 +9,7 @@ + diff --git a/modules/comment/queries/getCommentListByMemberSrl.xml b/modules/comment/queries/getCommentListByMemberSrl.xml index 60c344eac..14c4fbd87 100644 --- a/modules/comment/queries/getCommentListByMemberSrl.xml +++ b/modules/comment/queries/getCommentListByMemberSrl.xml @@ -1,7 +1,10 @@ - +
    + + + @@ -9,9 +12,13 @@ + + + + - + diff --git a/modules/comment/queries/getCommentListItem.xml b/modules/comment/queries/getCommentListItem.xml index c46b514cc..ba0374863 100644 --- a/modules/comment/queries/getCommentListItem.xml +++ b/modules/comment/queries/getCommentListItem.xml @@ -10,5 +10,6 @@ + diff --git a/modules/comment/queries/getCommentPage.xml b/modules/comment/queries/getCommentPage.xml new file mode 100644 index 000000000..db42d5076 --- /dev/null +++ b/modules/comment/queries/getCommentPage.xml @@ -0,0 +1,22 @@ + + +
    +
    + + + + + + + + + + + + + + + + + + diff --git a/modules/comment/queries/getCommentPageItem.xml b/modules/comment/queries/getCommentPageItem.xml new file mode 100644 index 000000000..89af8d69a --- /dev/null +++ b/modules/comment/queries/getCommentPageItem.xml @@ -0,0 +1,13 @@ + + +
    + + + + + + + + + + diff --git a/modules/comment/queries/getCommentPageList.xml b/modules/comment/queries/getCommentPageList.xml index e7b650b7a..68065ef8b 100644 --- a/modules/comment/queries/getCommentPageList.xml +++ b/modules/comment/queries/getCommentPageList.xml @@ -9,7 +9,8 @@ - + + diff --git a/modules/comment/queries/getCommentVotedLogInfo.xml b/modules/comment/queries/getCommentVotedLogInfo.xml index 6bb3cd9c7..4ab7ec8d2 100644 --- a/modules/comment/queries/getCommentVotedLogInfo.xml +++ b/modules/comment/queries/getCommentVotedLogInfo.xml @@ -4,6 +4,7 @@ + diff --git a/modules/comment/queries/getCommentVotedLogMulti.xml b/modules/comment/queries/getCommentVotedLogMulti.xml index 389d35b3d..348fd89f8 100644 --- a/modules/comment/queries/getCommentVotedLogMulti.xml +++ b/modules/comment/queries/getCommentVotedLogMulti.xml @@ -1,4 +1,4 @@ - +
    diff --git a/modules/comment/queries/getCommentsByDocumentSrls.xml b/modules/comment/queries/getCommentsByDocumentSrls.xml index b7e2c0d68..fd9d92495 100644 --- a/modules/comment/queries/getCommentsByDocumentSrls.xml +++ b/modules/comment/queries/getCommentsByDocumentSrls.xml @@ -7,6 +7,7 @@ + diff --git a/modules/comment/queries/getModuleList.xml b/modules/comment/queries/getModuleList.xml new file mode 100644 index 000000000..3e380a1dd --- /dev/null +++ b/modules/comment/queries/getModuleList.xml @@ -0,0 +1,21 @@ + + +
    + + + + + + + + + + +
    + + + + + + + diff --git a/modules/comment/queries/getNewestCommentList.xml b/modules/comment/queries/getNewestCommentList.xml index 90f74da8f..8e92b0e0b 100644 --- a/modules/comment/queries/getNewestCommentList.xml +++ b/modules/comment/queries/getNewestCommentList.xml @@ -6,10 +6,12 @@ - - + + + + diff --git a/modules/comment/queries/getTotalCommentCountByGroupStatus.xml b/modules/comment/queries/getTotalCommentCountByGroupStatus.xml index 6516a1f64..bc7b38598 100644 --- a/modules/comment/queries/getTotalCommentCountByGroupStatus.xml +++ b/modules/comment/queries/getTotalCommentCountByGroupStatus.xml @@ -1,4 +1,4 @@ - +
    @@ -10,8 +10,9 @@ + - + @@ -22,7 +23,7 @@ - - - + + + diff --git a/modules/comment/queries/getTotalCommentCountWithinMemberByGroupStatus.xml b/modules/comment/queries/getTotalCommentCountWithinMemberByGroupStatus.xml index dabd55ab1..86b511086 100644 --- a/modules/comment/queries/getTotalCommentCountWithinMemberByGroupStatus.xml +++ b/modules/comment/queries/getTotalCommentCountWithinMemberByGroupStatus.xml @@ -1,4 +1,4 @@ - +
    @@ -11,8 +11,9 @@ + - + @@ -22,7 +23,7 @@ - - - + + + diff --git a/modules/comment/queries/getTotalCommentList.xml b/modules/comment/queries/getTotalCommentList.xml index 13dfa0dd5..17590bb39 100644 --- a/modules/comment/queries/getTotalCommentList.xml +++ b/modules/comment/queries/getTotalCommentList.xml @@ -1,30 +1,39 @@ -
    +
    +
    - + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + diff --git a/modules/comment/queries/getTotalCommentListWithinMember.xml b/modules/comment/queries/getTotalCommentListWithinMember.xml index 820f1ab27..00705ace5 100644 --- a/modules/comment/queries/getTotalCommentListWithinMember.xml +++ b/modules/comment/queries/getTotalCommentListWithinMember.xml @@ -1,19 +1,22 @@ -
    -
    +
    +
    +
    - + + + - + diff --git a/modules/comment/queries/getTotalCommentListWithoutJoin.xml b/modules/comment/queries/getTotalCommentListWithoutJoin.xml new file mode 100644 index 000000000..4be8095ad --- /dev/null +++ b/modules/comment/queries/getTotalCommentListWithoutJoin.xml @@ -0,0 +1,30 @@ + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/comment/queries/insertComment.xml b/modules/comment/queries/insertComment.xml index 3277f9051..b3fcf9717 100644 --- a/modules/comment/queries/insertComment.xml +++ b/modules/comment/queries/insertComment.xml @@ -12,7 +12,7 @@ - + diff --git a/modules/comment/queries/updateComment.xml b/modules/comment/queries/updateComment.xml index 373fd25f7..5d16dff9e 100644 --- a/modules/comment/queries/updateComment.xml +++ b/modules/comment/queries/updateComment.xml @@ -3,19 +3,19 @@
    - + - - - + + + - + - + - + diff --git a/modules/comment/queries/updateCommentByDelete.xml b/modules/comment/queries/updateCommentByDelete.xml index 208ecc397..c9baf873d 100644 --- a/modules/comment/queries/updateCommentByDelete.xml +++ b/modules/comment/queries/updateCommentByDelete.xml @@ -5,6 +5,7 @@ + diff --git a/modules/comment/queries/updateDeclaredCommentCancel.xml b/modules/comment/queries/updateDeclaredCommentCancel.xml new file mode 100644 index 000000000..1d538510a --- /dev/null +++ b/modules/comment/queries/updateDeclaredCommentCancel.xml @@ -0,0 +1,11 @@ + + +
    + + + + + + + + diff --git a/modules/comment/queries/updatePublishedStatus.xml b/modules/comment/queries/updatePublishedStatus.xml index e1602d469..2984e770a 100644 --- a/modules/comment/queries/updatePublishedStatus.xml +++ b/modules/comment/queries/updatePublishedStatus.xml @@ -6,7 +6,10 @@ - - + + + + + diff --git a/modules/comment/queries/updateUploadedCount.xml b/modules/comment/queries/updateUploadedCount.xml new file mode 100644 index 000000000..9a9bfab15 --- /dev/null +++ b/modules/comment/queries/updateUploadedCount.xml @@ -0,0 +1,11 @@ + + +
    + + + + + + + + diff --git a/modules/comment/schemas/comments.xml b/modules/comment/schemas/comments.xml index 6c6a6f34a..b689a2fe9 100644 --- a/modules/comment/schemas/comments.xml +++ b/modules/comment/schemas/comments.xml @@ -8,7 +8,7 @@ - + diff --git a/modules/comment/tpl/comment_list.html b/modules/comment/tpl/comment_list.html index b93b21850..71d9b2c87 100644 --- a/modules/comment/tpl/comment_list.html +++ b/modules/comment/tpl/comment_list.html @@ -18,8 +18,6 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}'; {$lang->published_name_list['N']}({number_format($total_count)}) | {$lang->published_name_list['Y']}({number_format($total_count)}) - | - {$lang->cmd_declared_list} | {$lang->ipaddress}:{$search_keyword}({number_format($total_count)}) @@ -40,11 +38,10 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}'; - + - @@ -52,10 +49,16 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}'; {@ $comment = $val->getContentText(200)} - - + @@ -87,7 +89,6 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}'; -
    @@ -129,10 +130,15 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}'; - + diff --git a/modules/comment/tpl/comment_module_config.html b/modules/comment/tpl/comment_module_config.html index 8181eb570..ace3077e9 100644 --- a/modules/comment/tpl/comment_module_config.html +++ b/modules/comment/tpl/comment_module_config.html @@ -4,7 +4,7 @@ - +
    @@ -13,9 +13,46 @@
    - +
    - +

    {$lang->about_comment_page_count}

    +
    +
    +
    + +
    + +

    {$lang->about_max_thread_depth}

    +
    +
    +
    + +
    + +
    +
    +
    + +
    + + +

    {$lang->about_comment_validation}

    +
    +
    +
    + +
    + + - - - -

    {$lang->about_comment_validation}

    + + + +
    +
    +
    + +
    + +
    - - + +
    diff --git a/modules/comment/tpl/css/declare_comment.css b/modules/comment/tpl/css/declare_comment.css index 5e6e52041..983d0d1af 100644 --- a/modules/comment/tpl/css/declare_comment.css +++ b/modules/comment/tpl/css/declare_comment.css @@ -2,18 +2,16 @@ div.xe_mobile { display:none!important; } -section.declare_comment{ - display:block; +select#message_option { + clear: both; + width: 90%; + margin-bottom: 8px; } -section.declare_comment label{ - font-weight: bold; -} -section.declare_comment select,section.declare_comment textarea{ - box-sizing:border-box; - height:auto; + +textarea#declare_message { width: 100%; - padding:7px; - font-size: 11pt; - line-height: normal; - display:block; + height: 80px; + margin-bottom: 8px; + box-sizing: border-box; + display: none; } \ No newline at end of file diff --git a/modules/comment/tpl/declare_comment.html b/modules/comment/tpl/declare_comment.html index 2740be0e1..a44b7baf0 100644 --- a/modules/comment/tpl/declare_comment.html +++ b/modules/comment/tpl/declare_comment.html @@ -1,56 +1,64 @@ {@Context::addMetaTag('viewport', 'width=device-width, user-scalable=no', FALSE);} - -
    -

    {$lang->improper_comment_declare}

    - - - - - - + + + + + + + + + + +
    +

    {$lang->improper_comment_declare} {$lang->cmd_cancel}

    + × +
    +

    {$lang->replies}

    {$target_comment->getSummary(200)}

    - -
    - - -

    {$lang->about_improper_comment_declare}

    -

    -
    -
    - + +
    + +
    + + +

    {$lang->about_improper_comment_declare}

    - -
    + +
    + + + \ No newline at end of file + $('select[name="message_option"]').change(function(){ + if ($(this).val()==='others') { + $('#declare_message').show(); + } else { + $('#declare_message').hide(); + } + setFixedPopupSize(); + }); + }); + })(jQuery); + diff --git a/modules/comment/tpl/declared_list.html b/modules/comment/tpl/declared_list.html index 7b0190036..03ecff765 100644 --- a/modules/comment/tpl/declared_list.html +++ b/modules/comment/tpl/declared_list.html @@ -9,7 +9,7 @@ - +
    {$lang->comment} {$lang->writer}{$lang->cmd_vote}(+/-){$lang->cmd_vote} / {$lang->cmd_vote_down} {$lang->date} {$lang->ipaddress} {$lang->status}{$lang->published}
    - - {$module_list[$val->module_srl]->browser_title} - - - {$comment}{$lang->no_text_comment} + {@ $comment_url = getUrl(['mid' => $module_list[$val->module_srl]->mid ?? null, 'document_srl' => $val->document_srl, 'comment_srl' => $val->comment_srl]) . '#comment_' . $val->comment_srl} + + + {$module_list[$val->module_srl]->browser_title} - + + {@ $module_url = ModuleModel::getDomainByModuleSrl($val->module_srl); $comment_url = $module_url . $comment_url; } + {$module_list[$val->module_srl]->browser_title} - + + + {$comment}{$lang->no_text_comment} {$val->getNickName()} @@ -68,8 +71,7 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}'; {$val->getRegdate($val->getRegdateTime() > time() - 86400 ? 'H:i' : 'm-d')} {$val->ipaddress}{$secret_name_list['Y']}{$secret_name_list['N']}{$lang->published_name_list['Y']}{$lang->published_name_list['N']}{$val->getStatusText()}
    @@ -58,11 +59,11 @@
    @@ -26,8 +26,9 @@ {$lang->cmd_declared_list}({number_format($total_count)})
    - - + + +
    -
    - - + + +
    diff --git a/modules/comment/tpl/header.html b/modules/comment/tpl/header.html index 76a1e8cbd..addd0e7c7 100644 --- a/modules/comment/tpl/header.html +++ b/modules/comment/tpl/header.html @@ -2,3 +2,17 @@

    {$lang->comment} {$lang->cmd_management}

    + + + + diff --git a/modules/comment/tpl/js/comment_admin.js b/modules/comment/tpl/js/comment_admin.js index 776b44a2a..6102a9afc 100644 --- a/modules/comment/tpl/js/comment_admin.js +++ b/modules/comment/tpl/js/comment_admin.js @@ -1,16 +1,17 @@ function doCancelDeclare() { var comment_srl = new Array(); - jQuery('#fo_list input[name="cart[]"]:checked').each(function() { - comment_srl[comment_srl.length] = jQuery(this).val(); + $('#fo_list input[name="cart[]"]:checked').each(function() { + comment_srl.push($(this).val()); }); + if (comment_srl.length < 1) { + return; + } - if(comment_srl.length<1) return; - - var params = new Array(); - params['comment_srl'] = comment_srl.join(','); - - exec_xml('comment','procCommentAdminCancelDeclare', params, function() { location.reload(); }); + var params = { comment_srl: comment_srl.join(',') }; + Rhymix.ajax('comment.procCommentAdminCancelDeclare', params, function() { + location.reload(); + }); } function insertSelectedModule(id, module_srl, mid, browser_title) { @@ -26,7 +27,7 @@ function getCommentList() var commentListTable = jQuery('#commentListTable'); var cartList = []; commentListTable.find(':checkbox[name=cart]').each(function(){ - if(this.checked) cartList.push(this.value); + if(this.checked) cartList.push(this.value); }); var params = new Array(); @@ -39,8 +40,6 @@ function getCommentList() function completeGetCommentList(ret_obj, response_tags) { var htmlListBuffer = ''; - var statusNameList = {"N":"Public", "Y":"Secret"}; - var publishedStatusList = {0:'Unpublished', 1:'Published'}; if(ret_obj['comment_list'] == null) { htmlListBuffer = '' + @@ -57,8 +56,8 @@ function completeGetCommentList(ret_obj, response_tags) htmlListBuffer += '' + ''+ objComment.content +'' + ''+ objComment.nick_name +'' + - ''+ statusNameList[objComment.is_secret] +'' + - ''+ publishedStatusList[objComment.status] + '' + '' + + ''+ secret_name_list[objComment.is_secret] +'' + + ''+ published_name_list[String(objComment.status)] + '' + '' + ''; } jQuery('#selectedCommentCount').html(comment_list.length); diff --git a/modules/communication/communication.admin.controller.php b/modules/communication/communication.admin.controller.php index fd05dcb35..8c66e34ba 100644 --- a/modules/communication/communication.admin.controller.php +++ b/modules/communication/communication.admin.controller.php @@ -6,17 +6,8 @@ * @author NAVER (developers@xpressengine.com) * @brief communication module of the admin controller class */ -class communicationAdminController extends communication +class CommunicationAdminController extends communication { - - /** - * Initialization - */ - function init() - { - - } - /** * save configurations of the communication module * @return void|Object (success : void, fail : Object) @@ -24,7 +15,7 @@ class communicationAdminController extends communication function procCommunicationAdminInsertConfig() { // get the default information - $args = Context::gets('enable_message', 'enable_friend', 'skin', 'colorset', 'editor_skin', 'sel_editor_colorset', 'mskin', 'mcolorset', 'layout_srl', 'mlayout_srl', 'grant_send_default','grant_send_group'); + $args = Context::gets('enable_message', 'enable_friend', 'enable_attachment', 'attachment_size_limit', 'skin', 'colorset', 'editor_skin', 'sel_editor_colorset', 'mskin', 'mcolorset', 'layout_srl', 'mlayout_srl', 'grant_send_default','grant_send_group'); $args->editor_colorset = $args->sel_editor_colorset; unset($args->sel_editor_colorset); @@ -32,13 +23,14 @@ class communicationAdminController extends communication $args->grant_send = $oCommunicationModel->getGrantArray($args->grant_send_default, $args->grant_send_group); unset($args->grant_send_default); unset($args->grant_send_group); + $args->attachment_size_limit = intval($args->attachment_size_limit) ?? 0; // create the module module Controller object $oModuleController = getController('module'); $output = $oModuleController->insertModuleConfig('communication', $args); $returnUrl = Context::get('success_return_url') ? Context::get('success_return_url') : getNotEncodedUrl('', 'module', 'admin', 'act', 'dispCommunicationAdminConfig'); - + return $this->setRedirectUrl($returnUrl, $output); } diff --git a/modules/communication/communication.admin.model.php b/modules/communication/communication.admin.model.php index 5aac79dd5..5fa97a7a4 100644 --- a/modules/communication/communication.admin.model.php +++ b/modules/communication/communication.admin.model.php @@ -6,17 +6,8 @@ * @author NAVER (developers@xpressengine.com) * @brief communication module of the admin model class */ -class communicationAdminModel extends communication +class CommunicationAdminModel extends communication { - - /** - * Initialization - */ - function init() - { - - } - /** * the html to select colorset of the skin * @return void @@ -45,15 +36,7 @@ class communicationAdminModel extends communication $oModuleModel = getModel('module'); $skin_info = $oModuleModel->loadSkinInfo($this->module_path, $skin, $dir); Context::set('skin_info', $skin_info); - - $oModuleModel = getModel('module'); - $communication_config = $oModuleModel->getModuleConfig('communication'); - if(!is_object($communication_config)) $communication_config = new stdClass; - if(!$communication_config->colorset) - { - $communication_config->colorset = "white"; - } - Context::set('communication_config', $communication_config); + Context::set('communication_config', CommunicationModel::getConfig()); $security = new Security(); $security->encodeHTML('skin_info.colorset..title', 'skin_info.colorset..name'); diff --git a/modules/communication/communication.admin.view.php b/modules/communication/communication.admin.view.php index 89dd8f498..5ca172eff 100644 --- a/modules/communication/communication.admin.view.php +++ b/modules/communication/communication.admin.view.php @@ -6,17 +6,8 @@ * @author NAVER (developers@xpressengine.com) * communication module of the admin view class */ -class communicationAdminView extends communication +class CommunicationAdminView extends communication { - - /** - * Initialization - */ - function init() - { - - } - /** * configuration to manage messages and friends * @return void diff --git a/modules/communication/communication.class.php b/modules/communication/communication.class.php index ba79041a6..49bcb1d50 100644 --- a/modules/communication/communication.class.php +++ b/modules/communication/communication.class.php @@ -2,32 +2,18 @@ /* Copyright (C) NAVER */ /** - * @class communication + * @class communication * @author NAVER (developers@xpressengine.com) * communication module of the high class */ -class communication extends ModuleObject +class Communication extends ModuleObject { - private $triggers = array( - array('moduleHandler.init', 'communication', 'controller', 'triggerModuleHandlerBefore', 'before'), - array('member.getMemberMenu', 'communication', 'controller', 'triggerMemberMenu', 'before') - ); - private $delete_triggers = array( - array('moduleObject.proc', 'communication', 'controller', 'triggerModuleProcAfter', 'after') - ); /** * Implement if additional tasks are necessary when installing * @return Object */ function moduleInstall() { - $oModuleController = getController('module'); - - foreach($this->triggers as $trigger) - { - $oModuleController->insertTrigger($trigger[0], $trigger[1], $trigger[2], $trigger[3], $trigger[4]); - } - // Create a temporary file storage for one new private message notification FileHandler::makeDir('./files/member_extra_info/new_message_flags'); } @@ -38,30 +24,15 @@ class communication extends ModuleObject */ function checkUpdate() { - $oModuleModel = getModel('module'); - - foreach($this->triggers as $trigger) - { - if(!$oModuleModel->getTrigger($trigger[0], $trigger[1], $trigger[2], $trigger[3], $trigger[4])) - { - return TRUE; - } - } - - foreach($this->delete_triggers as $trigger) - { - - if($oModuleModel->getTrigger($trigger[0], $trigger[1], $trigger[2], $trigger[3], $trigger[4])) - { - return TRUE; - } - } - if(!is_dir("./files/member_extra_info/new_message_flags")) { - return TRUE; + FileHandler::makeDir('./files/member_extra_info/new_message_flags'); + if(!is_dir("./files/member_extra_info/new_message_flags")) + { + return TRUE; + } } - + return FALSE; } @@ -71,40 +42,11 @@ class communication extends ModuleObject */ function moduleUpdate() { - $oModuleModel = getModel('module'); - $oModuleController = getController('module'); - - foreach($this->triggers as $trigger) - { - if(!$oModuleModel->getTrigger($trigger[0], $trigger[1], $trigger[2], $trigger[3], $trigger[4])) - { - $oModuleController->insertTrigger($trigger[0], $trigger[1], $trigger[2], $trigger[3], $trigger[4]); - } - } - - foreach($this->delete_triggers as $trigger) - { - if($oModuleModel->getTrigger($trigger[0], $trigger[1], $trigger[2], $trigger[3], $trigger[4])) - { - $oModuleController->deleteTrigger($trigger[0], $trigger[1], $trigger[2], $trigger[3], $trigger[4]); - } - } - if(!is_dir("./files/member_extra_info/new_message_flags")) { FileHandler::makeDir('./files/member_extra_info/new_message_flags'); } } - - /** - * Re-generate the cache file - * @return void - */ - function recompileCache() - { - - } - } /* End of file communication.class.php */ /* Location: ./modules/comment/communication.class.php */ diff --git a/modules/communication/communication.controller.php b/modules/communication/communication.controller.php index a1c228db7..72ff49fa9 100644 --- a/modules/communication/communication.controller.php +++ b/modules/communication/communication.controller.php @@ -6,9 +6,8 @@ * @author NAVER (developers@xpressengine.com) * communication module of the Controller class */ -class communicationController extends communication +class CommunicationController extends communication { - /** * Initialization */ @@ -35,10 +34,15 @@ class communicationController extends communication $args->allow_message = 'Y'; } - $logged_info = Context::get('logged_info'); - $args->member_srl = $logged_info->member_srl; + $args->member_srl = $this->user->member_srl; $output = executeQuery('communication.updateAllowMessage', $args); + if(!$output->toBool()) + { + return $output; + } + + MemberController::clearMemberCache($args->member_srl); $returnUrl = Context::get('success_return_url') ? Context::get('success_return_url') : getNotEncodedUrl('', 'mid', Context::get('mid'), 'act', 'dispCommunicationMessages', 'message_type', Context::get('message_type')); @@ -78,6 +82,18 @@ class communicationController extends communication throw new Rhymix\Framework\Exception('msg_content_is_null'); } + $temp_srl = intval(Context::get('temp_srl')) ?: null; + if($temp_srl && !$_SESSION['upload_info'][$temp_srl]->enabled) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + + $send_mail = Context::get('send_mail') === 'Y' ? 'Y' : 'N'; + if ($send_mail === 'Y' && !$this->user->isAdmin()) + { + throw new Rhymix\Framework\Exception('msg_send_mail_admin_only'); + } + // Check if there is a member to receive a message $oMemberModel = getModel('member'); $oCommunicationModel = getModel('communication'); @@ -89,7 +105,7 @@ class communicationController extends communication } $receiver_member_info = $oMemberModel->getMemberInfoByMemberSrl($receiver_srl); - if($receiver_member_info->member_srl != $receiver_srl) + if(!$receiver_member_info->member_srl) { throw new Rhymix\Framework\Exception('msg_not_exists_member'); } @@ -111,16 +127,22 @@ class communicationController extends communication } // send a message - $output = $this->sendMessage($logged_info->member_srl, $receiver_srl, $title, $content); + $output = $this->sendMessage($logged_info->member_srl, $receiver_srl, $title, $content, true, $temp_srl); if(!$output->toBool()) { return $output; } + // send an e-mail (admin only) + if($send_mail === 'Y') + { + $this->sendMessageByEmail($logged_info, $receiver_member_info, $title, $content); + } + if(!in_array(Context::getRequestMethod(), array('XMLRPC', 'JSON'))) { - if(Context::get('is_popup') != 'Y') + if(Context::get('is_popup') != 'Y' && Context::get('window_type') != 'self') { global $lang; htmlHeader(); @@ -133,8 +155,14 @@ class communicationController extends communication else { $this->setMessage('success_sended'); - $returnUrl = Context::get('success_return_url') ? Context::get('success_return_url') : getNotEncodedUrl('','act', 'dispCommunicationMessages', 'message_type', 'S', 'receiver_srl', $receiver_srl, 'message_srl', ''); - $this->setRedirectUrl($returnUrl); + if (Context::get('success_return_url')) + { + $this->setRedirectUrl(Context::get('success_return_url')); + } + else + { + $this->setRedirectUrl(getNotEncodedUrl('', 'mid', Context::get('mid'), 'act', 'dispCommunicationMessages')); + } } } @@ -147,20 +175,49 @@ class communicationController extends communication * @param int $receiver_srl member_srl of receiver_srl * @param string $title * @param string $content - * @param boolean $sender_log (default true) + * @param bool $sender_log (default true) + * @param int|null $temp_srl (default null) + * @param bool $use_spamfilter (default true) * @return Object */ - function sendMessage($sender_srl, $receiver_srl, $title, $content, $sender_log = TRUE) + function sendMessage($sender_srl, $receiver_srl, $title, $content, $sender_log = true, $temp_srl = null, $use_spamfilter = true) { // Encode the title and content. $title = escape($title, false); - $content = removeHackTag($content); - $title = utf8_mbencode($title); - $content = utf8_mbencode($content); + $content = Rhymix\Framework\Filters\HTMLFilter::clean((string)$content); + if (config('db.master.charset') !== 'utf8mb4') + { + $title = utf8_mbencode($title); + $content = utf8_mbencode($content); + } - $message_srl = getNextSequence(); + $message_srl = $temp_srl ?: getNextSequence(); $related_srl = getNextSequence(); + // Call a trigger (before) + $trigger_obj = new stdClass(); + $trigger_obj->sender_srl = $sender_srl; + $trigger_obj->receiver_srl = $receiver_srl; + $trigger_obj->message_srl = $message_srl; + $trigger_obj->related_srl = $related_srl; + $trigger_obj->title = $title; + $trigger_obj->content = $content; + $trigger_obj->sender_log = $sender_log; + $trigger_obj->use_spamfilter = $use_spamfilter; + $trigger_output = ModuleHandler::triggerCall('communication.sendMessage', 'before', $trigger_obj); + if(!$trigger_output->toBool()) + { + return $trigger_output; + } + + // Copy trigger result + $sender_srl = $trigger_obj->sender_srl ?? $sender_srl; + $receiver_srl = $trigger_obj->receiver_srl ?? $receiver_srl; + $title = $trigger_obj->title ?? $title; + $content = $trigger_obj->content ?? $content; + $sender_log = boolval($trigger_obj->sender_log ?? $sender_log); + $use_spamfilter = boolval($trigger_obj->use_spamfilter ?? $use_spamfilter); + // messages to save in the sendor's message box $sender_args = new stdClass(); $sender_args->sender_srl = $sender_srl; @@ -177,7 +234,7 @@ class communicationController extends communication // messages to save in the receiver's message box $receiver_args = new stdClass(); $receiver_args->message_srl = $related_srl; - $receiver_args->related_srl = 0; + $receiver_args->related_srl = $message_srl; $receiver_args->list_order = $related_srl * -1; $receiver_args->sender_srl = $sender_srl; if(!$receiver_args->sender_srl) @@ -191,21 +248,6 @@ class communicationController extends communication $receiver_args->readed = 'N'; $receiver_args->regdate = date("YmdHis"); - // Call a trigger (before) - $trigger_obj = new stdClass(); - $trigger_obj->sender_srl = $sender_srl; - $trigger_obj->receiver_srl = $receiver_srl; - $trigger_obj->message_srl = $message_srl; - $trigger_obj->related_srl = $related_srl; - $trigger_obj->title = $title; - $trigger_obj->content = $content; - $trigger_obj->sender_log = $sender_log; - $trigger_output = ModuleHandler::triggerCall('communication.sendMessage', 'before', $trigger_obj); - if(!$trigger_output->toBool()) - { - return $trigger_output; - } - $oDB = DB::getInstance(); $oDB->begin(); @@ -228,20 +270,64 @@ class communicationController extends communication return $output; } + // update attached files + if ($temp_srl) + { + $oFileController = getController('file'); + $oFileController->setFilesValid($message_srl, 'msg'); + } + // Call a trigger (after) ModuleHandler::triggerCall('communication.sendMessage', 'after', $trigger_obj); - + $oDB->commit(); - - // create a flag that message is sent (in file format) + + // create a flag that message is sent (in file format) $this->updateFlagFile($receiver_srl); return new BaseObject(0, 'success_sended'); } /** - * store a specific message into the archive - * @return void|Object (success : void, fail : Object) + * Send a message by email. + * + * @param object $sender + * @param object $recipient + * @param string $title + * @param string $content + * @return bool + */ + public function sendMessageByEmail($sender, $recipient, $title, $content): bool + { + if (empty($recipient->email_address) || !config('mail.default_from')) + { + return false; + } + + $view_url = Context::getRequestUri(); + $mail_title = vsprintf('%s - %s', [ + Context::getSiteTitle(), + $title, + ]); + + $mail_content = vsprintf('From: %s


    %s


    %s
    %s', [ + $sender->nick_name, + utf8_mbencode(Rhymix\Framework\Filters\HTMLFilter::clean((string)$content)), + Context::getSiteTitle(), + $view_url, $view_url, + ]); + + $oMail = new \Rhymix\Framework\Mail(); + $oMail->setFrom(config('mail.default_from'), config('mail.default_name')); + $oMail->addTo($recipient->email_address, $recipient->nick_name ?: null); + $oMail->setSubject($mail_title); + $oMail->setBody($mail_content); + $output = $oMail->send(); + return (bool)$output; + } + + /** + * Move a message to the archive. */ function procCommunicationStoreMessage() { @@ -262,7 +348,7 @@ class communicationController extends communication // get the message $oCommunicationModel = getModel('communication'); $message = $oCommunicationModel->getSelectedMessage($message_srl); - if(!$message || $message->message_type != 'R') + if(!$message || $message->receiver_srl !== $logged_info->member_srl || $message->message_type != 'R') { throw new Rhymix\Framework\Exceptions\InvalidRequest; } @@ -276,7 +362,47 @@ class communicationController extends communication return $output; } $this->updateFlagFile($logged_info->member_srl); - $this->setMessage('success_registed'); + $this->setMessage('msg_success_moved'); + } + + /** + * Restore an archived message to the inbox. + */ + function procCommunicationRestoreMessage() + { + // Check login information + if(!Context::get('is_logged')) + { + throw new Rhymix\Framework\Exceptions\MustLogin; + } + $logged_info = Context::get('logged_info'); + + // Check variable + $message_srl = Context::get('message_srl'); + if(!$message_srl) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + + // get the message + $oCommunicationModel = getModel('communication'); + $message = $oCommunicationModel->getSelectedMessage($message_srl); + if(!$message || $message->receiver_srl !== $logged_info->member_srl || $message->message_type != 'T') + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + + $args = new stdClass(); + $args->message_srl = $message_srl; + $args->receiver_srl = $logged_info->member_srl; + $args->message_type = 'R'; + $output = executeQuery('communication.setMessageStored', $args); + if(!$output->toBool()) + { + return $output; + } + $this->updateFlagFile($logged_info->member_srl); + $this->setMessage('msg_success_moved'); } /** @@ -295,15 +421,14 @@ class communicationController extends communication $member_srl = $logged_info->member_srl; // Check the variable - $message_srl = Context::get('message_srl'); + $message_srl = intval(Context::get('message_srl')); if(!$message_srl) { throw new Rhymix\Framework\Exceptions\InvalidRequest; } // Get the message - $oCommunicationModel = getModel('communication'); - $message = $oCommunicationModel->getSelectedMessage($message_srl); + $message = CommunicationModel::getSelectedMessage($message_srl); if(!$message) { throw new Rhymix\Framework\Exceptions\InvalidRequest; @@ -327,14 +452,34 @@ class communicationController extends communication break; } - // Delete + // Call trigger (before) $args = new stdClass(); $args->message_srl = $message_srl; + $trigger_output = ModuleHandler::triggerCall('communication.deleteMessage', 'before', $args); + if(!$trigger_output->toBool()) + { + return $trigger_output; + } + + // Delete $output = executeQuery('communication.deleteMessage', $args); if(!$output->toBool()) { return $output; } + + // Delete attachment, only if related message has also been deleted + $related = $message->related_srl ? CommunicationModel::getSelectedMessage($message->related_srl) : true; + if (!$related) + { + $oFileController = FileController::getInstance(); + $oFileController->deleteFiles($message->message_srl); + $oFileController->deleteFiles($message->related_srl); + } + + // Call trigger (after) + ModuleHandler::triggerCall('communication.deleteMessage', 'after', $args); + $this->updateFlagFile($member_srl); $this->setMessage('success_deleted'); } @@ -354,19 +499,27 @@ class communicationController extends communication $logged_info = Context::get('logged_info'); $member_srl = $logged_info->member_srl; - // check variables - if(!Context::get('message_srl_list')) + // Get list of messages + $message_srl_list = Context::get('message_srl_list'); + if (!$message_srl_list) { throw new Rhymix\Framework\Exception('msg_cart_is_null'); } - - $message_srl_list = Context::get('message_srl_list'); - if(!is_array($message_srl_list)) + if (!is_array($message_srl_list)) { $message_srl_list = explode('|@|', trim($message_srl_list)); } + $target = array(); + foreach ($message_srl_list as $message_srl) + { + $message_srl = intval($message_srl, 10); + if ($message_srl > 0) + { + $target[] = $message_srl; + } - if(!count($message_srl_list)) + } + if (!count($target)) { throw new Rhymix\Framework\Exception('msg_cart_is_null'); } @@ -377,27 +530,10 @@ class communicationController extends communication throw new Rhymix\Framework\Exceptions\InvalidRequest; } - $message_count = count($message_srl_list); - $target = array(); - for($i = 0; $i < $message_count; $i++) - { - $message_srl = (int) trim($message_srl_list[$i]); - if(!$message_srl) - { - continue; - } - - $target[] = $message_srl; - } - if(!count($target)) - { - throw new Rhymix\Framework\Exception('msg_cart_is_null'); - } - - // Delete + // Organize variables $args = new stdClass(); - $args->message_srls = implode(',', $target); - + $args->message_srls = $target; + if ($message_type === 'N') { $args->message_type = 'R'; @@ -406,7 +542,7 @@ class communicationController extends communication { $args->message_type = $message_type; } - + if($message_type == 'S') { $args->sender_srl = $member_srl; @@ -416,11 +552,49 @@ class communicationController extends communication $args->receiver_srl = $member_srl; } + // Call trigger (before) + $trigger_output = ModuleHandler::triggerCall('communication.deleteMessages', 'before', $args); + if(!$trigger_output->toBool()) + { + return $trigger_output; + } + + // Find related messages + $related = array(); + $output = executeQueryArray('communication.getRelatedMessages', $args); + foreach ($output->data as $item) + { + $related[$item->related_srl] = $item->message_srl; + } + if (count($related)) + { + $output = executeQueryArray('communication.getMessages', (object)array( + 'message_srl_list' => array_keys($related) + ), array('message_srl')); + foreach ($output->data as $item) + { + unset($related[$item->message_srl]); + } + } + + // Delete $output = executeQuery('communication.deleteMessages', $args); if(!$output->toBool()) { return $output; } + + // Delete attachment, only if related message has also been deleted + $oFileController = getController('file'); + foreach ($related as $message_srl => $related_srl) + { + $oFileController->deleteFiles($message_srl); + $oFileController->deleteFiles($related_srl); + } + + // Call trigger (after) + ModuleHandler::triggerCall('communication.deleteMessages', 'after', $args); + $this->updateFlagFile($member_srl); $this->setMessage('success_deleted'); @@ -451,7 +625,7 @@ class communicationController extends communication { throw new Rhymix\Framework\Exception('msg_no_self_friend'); } - + // Check duplicate friend $args = new stdClass(); $args->member_srl = $logged_info->member_srl; @@ -462,31 +636,45 @@ class communicationController extends communication throw new Rhymix\Framework\Exception('msg_already_friend'); } + // Call trigger (before) + $args->friend_group_srl = intval(Context::get('friend_group_srl')); + $trigger_output = ModuleHandler::triggerCall('communication.addFriend', 'before', $args); + if(!$trigger_output->toBool()) + { + return $trigger_output; + } + // Variable - $args = new stdClass(); $args->friend_srl = getNextSequence(); $args->list_order = $args->friend_srl * -1; - $args->friend_group_srl = Context::get('friend_group_srl'); - $args->member_srl = $logged_info->member_srl; - $args->target_srl = $target_srl; $output = executeQuery('communication.addFriend', $args); if(!$output->toBool()) { return $output; } + // Call trigger (after) + $trigger_output = ModuleHandler::triggerCall('communication.addFriend', 'after', $args); + $this->add('member_srl', $target_srl); $this->setMessage('success_registed'); if(!in_array(Context::getRequestMethod(), array('XMLRPC', 'JSON'))) { - global $lang; - htmlHeader(); - alertScript($lang->success_registed); - closePopupScript(); - htmlFooter(); - Context::close(); - exit; + if (Context::get('window_type') === 'self') + { + $this->setRedirectUrl(getNotEncodedUrl('', 'act', 'dispCommunicationFriend')); + } + else + { + global $lang; + htmlHeader(); + alertScript($lang->success_registed); + closePopupScript(); + htmlFooter(); + Context::close(); + exit; + } } } @@ -558,7 +746,7 @@ class communicationController extends communication } /** - * Delete a friend + * Delete a friend * @return void|Object (success : void, fail : Object) */ function procCommunicationDeleteFriend() @@ -570,50 +758,42 @@ class communicationController extends communication } $logged_info = Context::get('logged_info'); - $member_srl = $logged_info->member_srl; // Check variables $friend_srl_list = Context::get('friend_srl_list'); - if(!is_array($friend_srl_list)) { $friend_srl_list = explode('|@|', $friend_srl_list); } - + $friend_srl_list = array_map(function($str) { return intval(trim($str)); }, $friend_srl_list); + $friend_srl_list = array_filter($friend_srl_list, function($friend_srl) { return $friend_srl > 0; }); if(!count($friend_srl_list)) { throw new Rhymix\Framework\Exception('msg_cart_is_null'); } - $friend_count = count($friend_srl_list); - $target = array(); + // Prepare arguments + $args = new stdClass(); + $args->member_srl = $logged_info->member_srl; + $args->friend_srl_list = $friend_srl_list; - for($i = 0; $i < $friend_count; $i++) + // Call trigger (before) + $trigger_output = ModuleHandler::triggerCall('communication.deleteFriend', 'before', $args); + if(!$trigger_output->toBool()) { - $friend_srl = (int) trim($friend_srl_list[$i]); - if(!$friend_srl) - { - continue; - } - - $target[] = $friend_srl; - } - - if(!count($target)) - { - throw new Rhymix\Framework\Exception('msg_cart_is_null'); + return $trigger_output; } // Delete - $args = new stdClass(); - $args->friend_srls = implode(',', $target); - $args->member_srl = $logged_info->member_srl; $output = executeQuery('communication.deleteFriend', $args); if(!$output->toBool()) { return $output; } + // Call trigger (after) + $trigger_output = ModuleHandler::triggerCall('communication.deleteFriend', 'after', $args); + $this->setMessage('success_deleted'); $returnUrl = Context::get('success_return_url') ? Context::get('success_return_url') : getNotEncodedUrl('', 'mid', Context::get('mid'), 'act', 'dispCommunicationFriend'); @@ -632,12 +812,11 @@ class communicationController extends communication throw new Rhymix\Framework\Exceptions\MustLogin; } - $logged_info = Context::get('logged_info'); + $friend_group_srl = intval(trim(Context::get('friend_group_srl'))); // Variables $args = new stdClass(); - $args->friend_group_srl = trim(Context::get('friend_group_srl')); - $args->member_srl = $logged_info->member_srl; + $args->member_srl = $this->user->member_srl; $args->title = escape(Context::get('title')); if(!$args->title) @@ -646,12 +825,13 @@ class communicationController extends communication } // modify if friend_group_srl exists. - if($args->friend_group_srl) + if($friend_group_srl) { + $args->friend_group_srl = $friend_group_srl; $output = executeQuery('communication.renameFriendGroup', $args); $msg_code = 'success_updated'; - // add if not exists } + // add if not exists else { $output = executeQuery('communication.addFriendGroup', $args); @@ -662,13 +842,20 @@ class communicationController extends communication { if(!in_array(Context::getRequestMethod(), array('XMLRPC', 'JSON'))) { - global $lang; - htmlHeader(); - alertScript($lang->fail_to_registed); - closePopupScript(); - htmlFooter(); - Context::close(); - exit; + if (Context::get('window_type') === 'self') + { + $this->setRedirectUrl(getNotEncodedUrl('', 'act', 'dispCommunicationFriend')); + } + else + { + global $lang; + htmlHeader(); + alertScript($lang->fail_to_registed); + closePopupScript(); + htmlFooter(); + Context::close(); + exit; + } } else { @@ -679,14 +866,21 @@ class communicationController extends communication { if(!in_array(Context::getRequestMethod(), array('XMLRPC', 'JSON'))) { - global $lang; - htmlHeader(); - alertScript($lang->success_registed); - reload(true); - closePopupScript(); - htmlFooter(); - Context::close(); - exit; + if (Context::get('window_type') === 'self') + { + $this->setRedirectUrl(getNotEncodedUrl('', 'act', 'dispCommunicationFriend')); + } + else + { + global $lang; + htmlHeader(); + alertScript($lang->success_registed); + reload(true); + closePopupScript(); + htmlFooter(); + Context::close(); + exit; + } } else { @@ -758,7 +952,7 @@ class communicationController extends communication /** * set a message status to be 'already read' - * @param int $message_srl + * @param int $message_srl * @return Object */ function setMessageReaded($message_srl) @@ -767,14 +961,14 @@ class communicationController extends communication $args->message_srl = $message_srl; $args->related_srl = $message_srl; $output = executeQuery('communication.setMessageReaded', $args); - + // Update flag file $logged_info = Context::get('logged_info'); $this->updateFlagFile($logged_info->member_srl); - + return $output; } - + /** * Update flag file * @param int $member_srl @@ -800,12 +994,12 @@ class communicationController extends communication // Add menus on the member login information $config = getModel('communication')->getConfig(); $oMemberController = getController('member'); - + if($config->enable_message == 'Y') { $oMemberController->addMemberMenu('dispCommunicationMessages', 'cmd_view_message_box'); } - + if($config->enable_friend == 'Y') { $oMemberController->addMemberMenu('dispCommunicationFriend', 'cmd_view_friend'); @@ -824,10 +1018,10 @@ class communicationController extends communication { return; } - + $oCommunicationModel = getModel('communication'); $config = $oCommunicationModel->getConfig(); - + if($config->enable_message == 'N' && $config->enable_friend == 'N') { return; @@ -836,12 +1030,12 @@ class communicationController extends communication { return; } - + $mid = Context::get('cur_mid'); $member_srl = Context::get('target_srl'); $logged_info = Context::get('logged_info'); $oMemberController = getController('member'); - + // Add a feature to display own message box. if($logged_info->member_srl == $member_srl) { @@ -850,7 +1044,7 @@ class communicationController extends communication { $oMemberController->addMemberPopupMenu(getUrl('', 'mid', $mid, 'act', 'dispCommunicationMessages'), 'cmd_view_message_box', '', 'self'); } - + // Display a list of friends if($config->enable_friend == 'Y') { @@ -871,13 +1065,15 @@ class communicationController extends communication // Add a menu for sending message if($config->enable_message == 'Y' && ($logged_info->is_admin == 'Y' || $target_member_info->allow_message == 'Y' || ($target_member_info->allow_message == 'F' && $oCommunicationModel->isFriend($member_srl)))) { - $oMemberController->addMemberPopupMenu(getUrl('', 'mid', $mid, 'act', 'dispCommunicationSendMessage', 'receiver_srl', $member_srl), 'cmd_send_message', '', 'popup'); + $url = getUrl('', 'mid', $mid, 'act', 'dispCommunicationSendMessage', 'receiver_srl', $member_srl, 'window_type', 'self'); + $oMemberController->addMemberPopupMenu($url, 'cmd_send_message', '', 'self'); } - + // Add a menu for listing friends (if a friend is new) if($config->enable_friend == 'Y' && !$oCommunicationModel->isAddedFriend($member_srl)) { - $oMemberController->addMemberPopupMenu(getUrl('', 'mid', $mid, 'act', 'dispCommunicationAddFriend', 'target_srl', $member_srl), 'cmd_add_friend', '', 'popup'); + $url = getUrl('', 'mid', $mid, 'act', 'dispCommunicationAddFriend', 'target_srl', $member_srl, 'window_type', 'self'); + $oMemberController->addMemberPopupMenu($url, 'cmd_add_friend', '', 'self'); } } } diff --git a/modules/communication/communication.mobile.php b/modules/communication/communication.mobile.php index 2d2f96b05..e2905c732 100644 --- a/modules/communication/communication.mobile.php +++ b/modules/communication/communication.mobile.php @@ -6,43 +6,13 @@ * @author NAVER (developers@xpressengine.com) * Mobile class of communication module */ -class communicationMobile extends communicationView +class CommunicationMobile extends communicationView { - function init() { - $oCommunicationModel = getModel('communication'); - - $this->config = $oCommunicationModel->getConfig(); + $this->config = CommunicationModel::getConfig(); Context::set('communication_config', $this->config); - - $mskin = $this->config->mskin; - if(!$mskin) - { - $template_path = sprintf('%sm.skins/%s/', $this->module_path, 'default'); - } - elseif($mskin === '/USE_RESPONSIVE/') - { - $template_path = sprintf("%sskins/%s/", $this->module_path, $this->config->skin); - if(!is_dir($template_path) || !$this->config->skin) - { - $template_path = sprintf("%sskins/%s/", $this->module_path, 'default'); - } - } - else - { - $template_path = sprintf('%sm.skins/%s', $this->module_path, $mskin); - } - - $oLayoutModel = getModel('layout'); - $layout_info = $oLayoutModel->getLayout($this->config->mlayout_srl); - if($layout_info) - { - $this->module_info->mlayout_srl = $this->config->mlayout_srl; - $this->setLayoutPath($layout_info->path); - } - - $this->setTemplatePath($template_path); + $this->setLayoutAndTemplatePaths('M', $this->config); } /** @@ -51,6 +21,14 @@ class communicationMobile extends communicationView */ function dispCommunicationMessageBoxList() { + // Check member mid + $oMemberView = MemberView::getInstance(); + if (!$oMemberView->checkMidAndRedirect()) + { + $this->setRedirectUrl($oMemberView->getRedirectUrl()); + return; + } + $this->setTemplateFile('message_box'); } } diff --git a/modules/communication/communication.model.php b/modules/communication/communication.model.php index 5cc34f982..4a50b46c9 100644 --- a/modules/communication/communication.model.php +++ b/modules/communication/communication.model.php @@ -6,9 +6,8 @@ * @author NAVER (developers@xpressengine.com) * communication module of the Model class */ -class communicationModel extends communication +class CommunicationModel extends communication { - /** * Initialization * @return void @@ -22,73 +21,54 @@ class communicationModel extends communication * get the configuration * @return object config of communication module */ - function getConfig() + public static function getConfig() { $oModuleModel = getModel('module'); $config = $oModuleModel->getModuleConfig('communication'); - - if(!$config->skin) + if(!$config) { - $config->skin = 'default'; + $config = new stdClass(); } - if(!$config->colorset) - { - $config->colorset = 'white'; - } - - if(!$config->editor_skin) - { - $config->editor_skin = 'ckeditor'; - } - - if(!$config->mskin) - { - $config->mskin = 'default'; - } - - if(!$config->grant_send) - { - $config->grant_send = array('default' => 'member'); - } - - if(!$config->enable_message) - { - $config->enable_message = 'Y'; - } - - if(!$config->enable_friend) - { - $config->enable_friend = 'Y'; - } + $config->enable_message = $config->enable_message ?? 'Y'; + $config->enable_friend = $config->enable_friend ?? 'Y'; + $config->enable_attachment = $config->enable_attachment ?? 'N'; + $config->editor_skin = $config->editor_skin ?? 'ckeditor'; + $config->layout_srl = $config->layout_srl ?? 0; + $config->skin = $config->skin ?? 'default'; + $config->colorset = $config->colorset ?? 'white'; + $config->mlayout_srl = $config->mlayout_srl ?? 0; + $config->mskin = $config->mskin ?? 'default'; + $config->mcolorset = $config->mcolorset ?? 'white'; + $config->grant_send = $config->grant_send ?? array('default' => 'member'); return $config; } /** - * @brief get grant array for insert to database. table module_config's config field + * @brief get grant array for insert to database. table module_config's config field * @param string $default * @param array $group * @return array */ - function getGrantArray($default, $group) + public static function getGrantArray($default, $group) { $grant = array(); if($default) { $grant = array('default' => $default); } - else if(is_array($group)) + else if(is_array($group)) { $grant_group = array(); foreach($group as $group_srl) { $grant_group[$group_srl] = true; } - + $grant = array('group' => $grant_group); - } - + } + return $grant; } @@ -97,10 +77,10 @@ class communicationModel extends communication * @param array $arrGrant * @return boolean */ - function checkGrant($arrGrant) + public static function checkGrant($arrGrant) { if(!$arrGrant) return false; - + $logged_info = Context::get('logged_info'); if($logged_info->is_admin == 'Y') return true; @@ -110,10 +90,6 @@ class communicationModel extends communication { if(Context::get('is_logged')) return true; } - else if($arrGrant['default'] == 'site') - { - if($this->site_srl == $logged_info->site_srl) return true; - } else if($arrGrant['default'] == 'manager') { if($logged_info->is_admin == 'Y') return true; @@ -136,7 +112,7 @@ class communicationModel extends communication * @param array $columnList * @return object message information */ - function getSelectedMessage($message_srl, $columnList = array()) + public static function getSelectedMessage($message_srl, $columnList = array()) { $logged_info = Context::get('logged_info'); @@ -163,7 +139,7 @@ class communicationModel extends communication $member_info = $oMemberModel->getMemberInfoByMemberSrl($message->sender_srl); } - if($member_info->member_srl) + if($member_info && isset($member_info->member_srl)) { foreach($member_info as $key => $val) { @@ -199,7 +175,7 @@ class communicationModel extends communication * @param array $columnList * @return object message information */ - function getNewMessage($columnList = array()) + public static function getNewMessage($columnList = array()) { $logged_info = Context::get('logged_info'); @@ -217,7 +193,7 @@ class communicationModel extends communication $oCommunicationController = getController('communication'); $oCommunicationController->setMessageReaded($message->message_srl); - + if (!$message->member_srl) { $message->member_srl = $message->sender_srl; @@ -229,7 +205,7 @@ class communicationModel extends communication return $message; } - function getNewMessageCount($member_srl = null) + public static function getNewMessageCount($member_srl = null) { if(!$member_srl) { @@ -249,9 +225,12 @@ class communicationModel extends communication * get a message list * @param string $message_type (R: Received Message, S: Sent Message, T: Archive) * @param array $columnList + * @param string $search_target + * @param string $search_keyword + * @param int $page * @return Object */ - function getMessages($message_type = "R", $columnList = array()) + public static function getMessages($message_type = 'R', $columnList = [], $search_target = null, $search_keyword = null, $page = null) { $logged_info = Context::get('logged_info'); $args = new stdClass(); @@ -283,9 +262,15 @@ class communicationModel extends communication break; } + // Search conditions + if ($search_target && $search_keyword) + { + $args->{'s_' . $search_target} = $search_keyword; + } + // Other variables $args->sort_index = 'message.list_order'; - $args->page = Context::get('page'); + $args->page = $page ?? max(1, intval(Context::get('page'))); $args->list_count = 20; $args->page_count = 10; @@ -303,22 +288,35 @@ class communicationModel extends communication $message->user_name = $message->nick_name; } } - + return $output; } + /** + * Get a list of files attached to a message. + * + * @param object $message + * @return array + */ + public static function getMessageFiles($message) + { + $upload_target_srl = $message->message_type === 'S' ? $message->message_srl : $message->related_srl; + $file_list = getModel('file')->getFiles($upload_target_srl); + return $file_list ?: []; + } + /** * Get a list of friends * @param int $friend_group_srl (default 0) * @param array $columnList * @return Object */ - function getFriends($friend_group_srl = 0, $columnList = array()) + public static function getFriends($friend_group_srl = 0, $columnList = array()) { $logged_info = Context::get('logged_info'); $args = new stdClass(); - $args->friend_group_srl = $friend_group_srl; + $args->friend_group_srl = $friend_group_srl ?: null; $args->member_srl = $logged_info->member_srl; // Other variables @@ -328,7 +326,7 @@ class communicationModel extends communication $args->page_count = 10; $output = executeQuery('communication.getFriends', $args, $columnList); - + return $output; } @@ -337,14 +335,14 @@ class communicationModel extends communication * @param int $member_srl * @return int */ - function isAddedFriend($member_srl) + public static function isAddedFriend($member_srl) { $logged_info = Context::get('logged_info'); $args = new stdClass(); $args->member_srl = $logged_info->member_srl; $args->target_srl = $member_srl; - + $output = executeQuery('communication.isAddedFriend', $args); return $output->data->count; @@ -355,7 +353,7 @@ class communicationModel extends communication * @param int $friend_group_srl * @return object */ - function getFriendGroupInfo($friend_group_srl) + public static function getFriendGroupInfo($friend_group_srl) { $logged_info = Context::get('logged_info'); @@ -364,7 +362,7 @@ class communicationModel extends communication $args->friend_group_srl = $friend_group_srl; $output = executeQuery('communication.getFriendGroup', $args); - + return $output->data; } @@ -372,30 +370,27 @@ class communicationModel extends communication * Get a list of groups * @return array */ - function getFriendGroups() + public static function getFriendGroups() { $logged_info = Context::get('logged_info'); $args = new stdClass(); $args->member_srl = $logged_info->member_srl; - $output = executeQueryArray('communication.getFriendGroups', $args); - - $group_list = $output->data; - if(!$group_list) + $friend_group_list = array(); + foreach ($output->data as $item) { - return; + $friend_group_list[$item->friend_group_srl] = $item; } - - return $group_list; + return $friend_group_list; } /** * check whether to be added in the friend list - * @param int $target_srl - * @return boolean (true : friend, false : not friend) + * @param int $target_srl + * @return boolean (true : friend, false : not friend) */ - function isFriend($target_srl) + public static function isFriend($target_srl) { $logged_info = Context::get('logged_info'); diff --git a/modules/communication/communication.view.php b/modules/communication/communication.view.php index 8a43b1c48..22b90a54c 100644 --- a/modules/communication/communication.view.php +++ b/modules/communication/communication.view.php @@ -6,42 +6,17 @@ * @author NAVER (developers@xpressengine.com) * View class of communication module */ -class communicationView extends communication +class CommunicationView extends communication { - /** * Initialization * @return void */ function init() { - $oCommunicationModel = getModel('communication'); - - $this->config = $oCommunicationModel->getConfig(); - $skin = $this->config->skin; - + $this->config = CommunicationModel::getConfig(); Context::set('communication_config', $this->config); - - $config_parse = explode('|@|', $skin); - - if(count($config_parse) > 1) - { - $tpl_path = sprintf('./themes/%s/modules/communication/', $config_parse[0]); - } - else - { - $tpl_path = sprintf('%sskins/%s', $this->module_path, $skin); - } - - $this->setTemplatePath($tpl_path); - - $oLayoutModel = getModel('layout'); - $layout_info = $oLayoutModel->getLayout($this->config->layout_srl); - if($layout_info) - { - $this->module_info->layout_srl = $this->config->layout_srl; - $this->setLayoutPath($layout_info->path); - } + $this->setLayoutAndTemplatePaths('P', $this->config); } /** @@ -54,17 +29,25 @@ class communicationView extends communication { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - + // Error appears if not logged-in if(!Context::get('is_logged')) { throw new Rhymix\Framework\Exceptions\MustLogin; } + // Check member mid + $oMemberView = MemberView::getInstance(); + if (!$oMemberView->checkMidAndRedirect()) + { + $this->setRedirectUrl($oMemberView->getRedirectUrl()); + return; + } + $logged_info = Context::get('logged_info'); // Set the variables - $message_srl = Context::get('message_srl'); + $message_srl = intval(Context::get('message_srl')); $message_type = Context::get('message_type'); if(!in_array($message_type, array('R', 'S', 'T', 'N'))) @@ -78,7 +61,7 @@ class communicationView extends communication $template_filename = 'messages'; if($message_srl) { - $columnList = array('message_srl', 'sender_srl', 'receiver_srl', 'message_type', 'title', 'content', 'readed', 'regdate'); + $columnList = array('message_srl', 'message_type', 'related_srl', 'sender_srl', 'receiver_srl', 'title', 'content', 'readed', 'regdate'); $message = $oCommunicationModel->getSelectedMessage($message_srl, $columnList); switch($message->message_type) @@ -86,28 +69,28 @@ class communicationView extends communication case 'R': if($message->receiver_srl != $logged_info->member_srl) { - throw new Rhymix\Framework\Exceptions\InvalidRequest; + throw new Rhymix\Framework\Exceptions\TargetNotFound; } break; case 'S': if($message->sender_srl != $logged_info->member_srl) { - throw new Rhymix\Framework\Exceptions\InvalidRequest; + throw new Rhymix\Framework\Exceptions\TargetNotFound; } break; case 'T': if($message->receiver_srl != $logged_info->member_srl && $message->sender_srl != $logged_info->member_srl) { - throw new Rhymix\Framework\Exceptions\InvalidRequest; + throw new Rhymix\Framework\Exceptions\TargetNotFound; } break; case 'N': if($message->receiver_srl != $logged_info->member_srl) { - throw new Rhymix\Framework\Exceptions\InvalidRequest; + throw new Rhymix\Framework\Exceptions\TargetNotFound; } break; } @@ -116,17 +99,34 @@ class communicationView extends communication { stripEmbedTagForAdmin($message->content, $message->sender_srl); Context::set('message', $message); - if(Mobile::isFromMobilePhone()) + Context::set('message_files', CommunicationModel::getMessageFiles($message)); + + if(Mobile::isFromMobilePhone() && file_exists($this->getTemplatePath() . 'read_message.html')) { $template_filename = 'read_message'; } } + else + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } } + // Set search conditions + $search_target = Context::get('search_target'); + if (!in_array($search_target, ['title', 'title_content', 'content'])) + { + $search_target = null; + } + $search_keyword = utf8_clean(Context::get('search_keyword')); + Context::set('search_target', $search_target); + Context::set('search_keyword', $search_keyword !== '' ? $search_keyword : null); + // Extract a list - $columnList = array('message_srl', 'readed', 'title', 'member.member_srl', 'member.nick_name', 'message.regdate', 'readed_date'); - $output = $oCommunicationModel->getMessages($message_type, $columnList); - + $columnList = array('message_srl', 'message_type', 'related_srl', 'sender_srl', 'receiver_srl', 'title', 'member.member_srl', 'member.nick_name', 'message.regdate', 'readed', 'readed_date'); + $page = max(1, intval(Context::get('page'))); + $output = $oCommunicationModel->getMessages($message_type, $columnList, $search_target, $search_keyword, $page); + // set a template file Context::set('total_count', $output->total_count); Context::set('total_page', $output->total_page); @@ -137,6 +137,15 @@ class communicationView extends communication $oSecurity = new Security(); $oSecurity->encodeHTML('message_list..nick_name'); + if ($message) + { + MemberView::setMemberPageBrowserTitle($message->title); + } + else + { + MemberView::setMemberPageBrowserTitle(lang('communication.message_box.' . $message_type)); + } + $this->setTemplateFile($template_filename); } @@ -153,26 +162,31 @@ class communicationView extends communication { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - + // Error appears if not logged-in if(!Context::get('is_logged')) { throw new Rhymix\Framework\Exceptions\MustLogin; } - $logged_info = Context::get('logged_info'); - - $oCommunicationModel = getModel('communication'); + // Check member mid + $oMemberView = MemberView::getInstance(); + if (!$oMemberView->checkMidAndRedirect()) + { + $this->setRedirectUrl($oMemberView->getRedirectUrl()); + return; + } // get a new message $columnList = array('message_srl', 'member_srl', 'nick_name', 'title', 'content', 'sender_srl'); - $message = $oCommunicationModel->getNewMessage($columnList); + $message = CommunicationModel::getInstance()->getNewMessage($columnList); if($message) { stripEmbedTagForAdmin($message->content, $message->sender_srl); Context::set('message', $message); } + MemberView::setMemberPageBrowserTitle($message->title ?? lang('cmd_view_message_box')); $this->setTemplateFile('new_message'); } @@ -182,7 +196,18 @@ class communicationView extends communication */ function dispCommunicationSendMessage() { - if(!Context::get('m')) + // If window type is self, use member module layout. + // Otherwise, assume it's a popup window on PC for backward compatibility. + if(Context::get('window_type') === 'self') + { + $oMemberView = MemberView::getInstance(); + if (!$oMemberView->checkMidAndRedirect()) + { + $this->setRedirectUrl($oMemberView->getRedirectUrl()); + return; + } + } + elseif(!Context::get('m')) { $this->setLayoutPath('./common/tpl/'); $this->setLayoutFile("popup_layout"); @@ -192,42 +217,58 @@ class communicationView extends communication { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - if(!getModel('communication')->checkGrant($this->config->grant_send)) - { - throw new Rhymix\Framework\Exceptions\NotPermitted; - } - + // Error appears if not logged-in if(!Context::get('is_logged')) { throw new Rhymix\Framework\Exceptions\MustLogin; } - $logged_info = Context::get('logged_info'); + // Check permission + if(!getModel('communication')->checkGrant($this->config->grant_send)) + { + throw new Rhymix\Framework\Exceptions\NotPermitted; + } - // get receipient's information - // check inalid request + // Check member mid + $oMemberView = MemberView::getInstance(); + if (!$oMemberView->checkMidAndRedirect()) + { + $this->setRedirectUrl($oMemberView->getRedirectUrl()); + return; + } + + // Fix missing mid (it causes errors when uploading) + if(!Context::get('mid')) + { + Context::set('mid', Context::get('site_module_info')->mid); + } + + // Check receipient info $receiver_srl = Context::get('receiver_srl'); if(!$receiver_srl) { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - - // check receiver and sender are same + $logged_info = Context::get('logged_info'); if($logged_info->member_srl == $receiver_srl) { throw new Rhymix\Framework\Exception('msg_cannot_send_to_yourself'); } + $receiver_info = MemberModel::getMemberInfoByMemberSrl($receiver_srl); + if(!$receiver_info || empty($receiver_info->member_srl)) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest('msg_invalid_recipient'); + } + + Context::set('receiver_info', $receiver_info); - $oCommunicationModel = getModel('communication'); - $oMemberModel = getModel('member'); - // get message_srl of the original message if it is a reply $message_srl = Context::get('message_srl'); if($message_srl) { - $source_message = $oCommunicationModel->getSelectedMessage($message_srl); - if($source_message->message_srl == $message_srl && $source_message->sender_srl == $receiver_srl) + $source_message = CommunicationModel::getSelectedMessage($message_srl); + if($source_message->message_srl == $message_srl && $source_message->sender_srl == $receiver_srl && $source_message->receiver_srl == $logged_info->member_srl) { if(strncasecmp('[re]', $source_message->title, 4) !== 0) { @@ -236,35 +277,50 @@ class communicationView extends communication $source_message->content = "\r\n
    \r\n
    " . trim($source_message->content) . "
    "; Context::set('source_message', $source_message); } + else + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } } - $receiver_info = $oMemberModel->getMemberInfoByMemberSrl($receiver_srl); - if(!$receiver_info) - { - throw new Rhymix\Framework\Exceptions\InvalidRequest; - } - - Context::set('receiver_info', $receiver_info); - // set a signiture by calling getEditor of the editor module $oEditorModel = getModel('editor'); $option = $oEditorModel->getEditorConfig(); - $option->primary_key_name = 'receiver_srl'; + $option->default_editor_settings = false; + $option->primary_key_name = 'temp_srl'; $option->content_key_name = 'content'; - $option->allow_fileupload = FALSE; + $option->allow_fileupload = $this->config->enable_attachment === 'Y'; + $option->allowed_filesize = ($this->config->attachment_size_limit ?? 0) * 1024; $option->enable_autosave = FALSE; $option->enable_default_component = TRUE; // FALSE; $option->enable_component = FALSE; $option->resizable = FALSE; $option->disable_html = TRUE; $option->height = 300; - $option->skin = $this->config->editor_skin; - $option->colorset = $this->config->editor_colorset; + $option->editor_skin = $this->config->editor_skin; + $option->sel_editor_colorset = $this->config->editor_colorset; $option->editor_focus = Context::get('source_message') ? 'Y' : 'N'; - $editor = $oEditorModel->getEditor($logged_info->member_srl, $option); + if(Context::get('m') || stripos($_SERVER['HTTP_USER_AGENT'] ?? '', 'mobile') !== false) + { + $option->editor_toolbar = 'simple'; + $option->editor_toolbar_hide = 'Y'; + } + if ($option->allow_fileupload) + { + $option->module_srl = MemberView::getInstance()->getMemberModuleSrl(); + $option->upload_target_type = 'msg'; + } + $editor = $oEditorModel->getEditor(getNextSequence(), $option); + $editor = $editor . "\n" . '' . "\n"; Context::set('editor', $editor); - + MemberView::setMemberPageBrowserTitle(lang('cmd_send_message')); $this->setTemplateFile('send_message'); + + // Fix for skins that don't support window_type=self + if(Context::get('window_type') === 'self') + { + Context::loadFile([$this->module_path . 'tpl/js/window_type.js', 'body']); + } } /** @@ -277,46 +333,52 @@ class communicationView extends communication { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - + // Error appears if not logged-in if(!Context::get('is_logged')) { throw new Rhymix\Framework\Exceptions\MustLogin; } - + + // Check member mid + $oMemberView = MemberView::getInstance(); + if (!$oMemberView->checkMidAndRedirect()) + { + $this->setRedirectUrl($oMemberView->getRedirectUrl()); + return; + } + $oCommunicationModel = getModel('communication'); // get a group list - $tmp_group_list = $oCommunicationModel->getFriendGroups(); - $group_count = count($tmp_group_list); - - for($i = 0; $i < $group_count; $i++) - { - $friend_group_list[$tmp_group_list[$i]->friend_group_srl] = $tmp_group_list[$i]; - } - + $friend_group_list = $oCommunicationModel->getFriendGroups(); Context::set('friend_group_list', $friend_group_list); // get a list of friends - $friend_group_srl = Context::get('friend_group_srl'); + $friend_group_srl = Context::get('friend_group_srl') ?: 0; + Context::set('friend_group_srl', $friend_group_srl); $columnList = array('friend_srl', 'friend_group_srl', 'target_srl', 'member.nick_name', 'friend.regdate'); $output = $oCommunicationModel->getFriends($friend_group_srl, $columnList); - $friend_count = count($output->data); - - if($friend_count) + if($output->data) { foreach($output->data as $key => $val) { $group_srl = $val->friend_group_srl; - $group_title = $friend_group_list[$group_srl]->title; - if(!$group_title) + if (isset($friend_group_list[$group_srl]->title)) { - $group_title = Context::get('default_friend_group'); + $output->data[$key]->group_title = $friend_group_list[$group_srl]->title; + } + else + { + $output->data[$key]->group_title = lang('default_friend_group'); } - $output->data[$key]->group_title = $group_title; } } + else + { + $output->data = []; + } // set a template file Context::set('total_count', $output->total_count); @@ -325,6 +387,7 @@ class communicationView extends communication Context::set('friend_list', $output->data); Context::set('page_navigation', $output->page_navigation); + MemberView::setMemberPageBrowserTitle(lang('cmd_view_friend')); $this->setTemplateFile('friends'); } @@ -334,20 +397,42 @@ class communicationView extends communication */ function dispCommunicationAddFriend() { - $this->setLayoutPath('./common/tpl/'); - $this->setLayoutFile("popup_layout"); - + // If window type is self, use member module layout. + // Otherwise, assume it's a popup window on PC for backward compatibility. + if(Context::get('window_type') === 'self') + { + $oMemberView = MemberView::getInstance(); + if (!$oMemberView->checkMidAndRedirect()) + { + $this->setRedirectUrl($oMemberView->getRedirectUrl()); + return; + } + } + elseif(!Context::get('m')) + { + $this->setLayoutPath('./common/tpl/'); + $this->setLayoutFile("popup_layout"); + } + if($this->config->enable_friend == 'N') { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - + // error appears if not logged-in if(!Context::get('is_logged')) { throw new Rhymix\Framework\Exceptions\MustLogin; } + // Check member mid + $oMemberView = MemberView::getInstance(); + if (!$oMemberView->checkMidAndRedirect()) + { + $this->setRedirectUrl($oMemberView->getRedirectUrl()); + return; + } + $logged_info = Context::get('logged_info'); $target_srl = Context::get('target_srl'); @@ -364,8 +449,7 @@ class communicationView extends communication $oMemberModel = getModel('member'); $oCommunicationModel = getModel('communication'); $communication_info = $oMemberModel->getMemberInfoByMemberSrl($target_srl); - - if($communication_info->member_srl != $target_srl) + if(!$communication_info || !$communication_info->member_srl) { throw new Rhymix\Framework\Exceptions\InvalidRequest; } @@ -376,7 +460,14 @@ class communicationView extends communication $friend_group_list = $oCommunicationModel->getFriendGroups(); Context::set('friend_group_list', $friend_group_list); + MemberView::setMemberPageBrowserTitle(lang('cmd_add_friend')); $this->setTemplateFile('add_friend'); + + // Fix for skins that don't support window_type=self + if(Context::get('window_type') === 'self') + { + Context::loadFile([$this->module_path . 'tpl/js/window_type.js', 'body']); + } } /** @@ -385,21 +476,41 @@ class communicationView extends communication */ function dispCommunicationAddFriendGroup() { - $this->setLayoutPath('./common/tpl/'); - $this->setLayoutFile("popup_layout"); - + // If window type is self, use member module layout. + // Otherwise, assume it's a popup window on PC for backward compatibility. + if(Context::get('window_type') === 'self') + { + $oMemberView = MemberView::getInstance(); + if (!$oMemberView->checkMidAndRedirect()) + { + $this->setRedirectUrl($oMemberView->getRedirectUrl()); + return; + } + } + elseif(!Context::get('m')) + { + $this->setLayoutPath('./common/tpl/'); + $this->setLayoutFile("popup_layout"); + } + if($this->config->enable_friend == 'N') { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - + // error apprears if not logged-in if(!Context::get('is_logged')) { throw new Rhymix\Framework\Exceptions\MustLogin; } - $logged_info = Context::get('logged_info'); + // Check member mid + $oMemberView = MemberView::getInstance(); + if (!$oMemberView->checkMidAndRedirect()) + { + $this->setRedirectUrl($oMemberView->getRedirectUrl()); + return; + } // change to edit mode when getting the group_srl $friend_group_srl = Context::get('friend_group_srl'); @@ -413,7 +524,14 @@ class communicationView extends communication } } + MemberView::setMemberPageBrowserTitle(lang('cmd_add_friend_group')); $this->setTemplateFile('add_friend_group'); + + // Fix for skins that don't support window_type=self + if(Context::get('window_type') === 'self') + { + Context::loadFile([$this->module_path . 'tpl/js/window_type.js', 'body']); + } } } /* End of file communication.view.php */ diff --git a/modules/communication/conf/info.xml b/modules/communication/conf/info.xml index 16665b579..d593062e2 100644 --- a/modules/communication/conf/info.xml +++ b/modules/communication/conf/info.xml @@ -16,8 +16,8 @@ This module is for managing message, friend functions. 管理線上會員間短訊及好友功能的模組。 Bu modül mesaj ve arkadaşlık özelliklerini yönetmek içindir. - 1.7 - 2013-11-27 + RX_VERSION + RX_CORE member diff --git a/modules/communication/conf/module.xml b/modules/communication/conf/module.xml index 51fe0c009..7209531c1 100644 --- a/modules/communication/conf/module.xml +++ b/modules/communication/conf/module.xml @@ -2,17 +2,23 @@ - - - - - - - - + + + + + + + + + + + + + + @@ -21,9 +27,13 @@ - + + + + + diff --git a/modules/communication/lang/en.php b/modules/communication/lang/en.php index 2394e894c..3e0b5cfe5 100644 --- a/modules/communication/lang/en.php +++ b/modules/communication/lang/en.php @@ -21,7 +21,8 @@ $lang->cmd_view_friend = 'Friends'; $lang->cmd_add_friend = 'Add Friend'; $lang->cmd_message_box = 'Message Box'; $lang->cmd_view_message_box = 'Message Box'; -$lang->cmd_store = 'Save'; +$lang->cmd_store = 'Move to Archive'; +$lang->cmd_restore_to_inbox = 'Move to Inbox'; $lang->cmd_view_selected_frend_group = 'View only selected group'; $lang->cmd_add_friend_group = 'Add Friend Group'; $lang->cmd_rename_friend_group = 'Rename Friend Group'; @@ -31,16 +32,25 @@ $lang->msg_no_self_friend = 'You cannot add yourself as a friend.'; $lang->msg_no_message = 'There is no message.'; $lang->msg_cannot_send_to_yourself = 'Cannot send a message to yourself.'; $lang->message_received = 'You have a new message.'; +$lang->msg_success_moved = 'Successfully moved.'; $lang->msg_title_is_null = 'Please enter the title of message.'; $lang->msg_content_is_null = 'Please enter the content.'; $lang->msg_allow_message_to_friend = 'Failed to send a message because the recipient accepts messages from friends only.'; $lang->msg_disallow_message = 'Failed to send a message because the recipient blocked receiving messages.'; +$lang->msg_invalid_recipient = 'Failed to find the recipient\'s member information.'; $lang->about_allow_message = 'You can set whether to receive messages or not.'; $lang->msg_send_mail_privacy = 'Your email address will be exposed to the recipient.'; +$lang->msg_send_mail_admin_only = 'The email option can only be used by the administrator.'; $lang->message_notice = 'Send a message to the author about this. If you don\'t write a message, it is not sent.'; $lang->friends_page_does_not_support = 'Friends in a mobile environment is not supported. Please go to the PC page.'; $lang->communication_send_message_grant = 'Send Message Grant'; -$lang->cmd_manage_base = 'Basic infomation'; +$lang->cmd_manage_base = 'Basic information'; $lang->alert_new_message_arrived = 'You have %d new message(s). Do you want to check it now?'; $lang->enable_communication_friend = 'Friend Enable'; $lang->enable_communication_message = 'Message Enable'; +$lang->enable_attachment = 'Allow Attachment'; +$lang->attachment_size_limit = 'Attachment Size Limit'; +$lang->warning = 'Caution!'; +$lang->msg_allow_message_friend = 'You can only receive direct messages from your friends.'; +$lang->msg_allow_meesage_Block = 'Your direct message box is turned off.'; +$lang->msg_allow_message_please = 'You should allow direct messages from everyone in order to receive the replies for this message.'; diff --git a/modules/communication/lang/ko.php b/modules/communication/lang/ko.php index 05267f8e8..7b0e5c3d9 100644 --- a/modules/communication/lang/ko.php +++ b/modules/communication/lang/ko.php @@ -22,6 +22,7 @@ $lang->cmd_add_friend = '친구 등록'; $lang->cmd_message_box = '쪽지함'; $lang->cmd_view_message_box = '쪽지함 보기'; $lang->cmd_store = '보관함 이동'; +$lang->cmd_restore_to_inbox = '받은 쪽지함 이동'; $lang->cmd_view_selected_frend_group = '선택된 그룹만 보기'; $lang->cmd_add_friend_group = '친구 그룹 생성'; $lang->cmd_rename_friend_group = '친구 그룹 이름 변경'; @@ -31,11 +32,14 @@ $lang->msg_no_self_friend = '자신을 친구로 등록할 수 없습니다.'; $lang->msg_no_message = '쪽지가 없습니다.'; $lang->msg_cannot_send_to_yourself = '자기 자신에게 쪽지를 보낼 수 없습니다.'; $lang->message_received = '쪽지가 왔습니다.'; +$lang->msg_success_moved = '이동했습니다.'; $lang->msg_title_is_null = '쪽지 제목을 입력해주세요.'; $lang->msg_content_is_null = '내용을 입력해주세요.'; $lang->msg_allow_message_to_friend = '친구에게만 쪽지 발송을 허용한 사용자라서 쪽지 발송을 하지 못했습니다.'; $lang->msg_disallow_message = '쪽지 수신을 거부한 사용자라서 쪽지 발송을 하지 못했습니다.'; +$lang->msg_invalid_recipient = '수신자 정보를 찾을 수 없습니다.'; $lang->msg_send_mail_privacy = '보낸이의 메일 주소가 받는이에게 노출될 수 있습니다.'; +$lang->msg_send_mail_admin_only = '메일 발송 기능은 관리자만 사용할 수 있습니다.'; $lang->about_allow_message = '쪽지 수신 여부를 결정할 수 있습니다.'; $lang->message_notice = '저작자에게 쪽지를 발송해서 이 사실을 알립니다. 작성하지 않으면 발송하지 않습니다.'; $lang->friends_page_does_not_support = '모바일 환경에서는 친구 보기 페이지를 지원하지 않습니다. PC 화면으로 이동하세요.'; @@ -44,6 +48,8 @@ $lang->cmd_manage_base = '기본 정보'; $lang->alert_new_message_arrived = '%d개의 새로운 메시지가 도착하였습니다. 확인하시겠습니까?'; $lang->enable_communication_friend = '친구기능 사용'; $lang->enable_communication_message = '쪽지기능 사용'; +$lang->enable_attachment = '파일첨부 허용'; +$lang->attachment_size_limit = '첨부 용량 제한'; $lang->warning = '알려드립니다!'; $lang->msg_allow_message_friend = '현재 회원님은 친구에게만 수신 가능한 상태입니다.'; $lang->msg_allow_meesage_Block = '현재 회원님은 수신거부 상태입니다.'; diff --git a/modules/communication/m.skins/default/css/mcommunication.css b/modules/communication/m.skins/default/css/mcommunication.css index 9b0f55dab..3ab85106f 100644 --- a/modules/communication/m.skins/default/css/mcommunication.css +++ b/modules/communication/m.skins/default/css/mcommunication.css @@ -19,6 +19,8 @@ input[type=radio]{width:13px;height:13px;margin:0;padding:0} .bd{background:#f8f8f8;padding:1px 0} .co{margin:10px;line-height:1.4;font-size:14px;color:#333} .co:after{content:"";display:block;clear:both;zoom:1} +.co .attachments { border-top: 1px solid #ccc8be; margin: 16px 0 0 0; padding: 16px 0 16px 24px; } +.co .attachments span.file_size { color: #666; } /* Hx */ .hx{position:relative;border-bottom:1px solid #ccc8be;padding:8px 10px;margin:0} .hx:after{content:"";margin:0 -10px;position:relative;top:10px;display:block;clear:both;height:1px;background:#fff} @@ -107,13 +109,16 @@ input[type=radio]{width:13px;height:13px;margin:0;padding:0} .bna{text-align:center;padding:0 10px;margin:10px 0;zoom:1} .bna:after{content:"";display:block;clear:both} .bn{display:inline-block;line-height:26px !important;padding:0 10px;font-size:12px;font-weight:bold;border:1px solid;text-decoration:none;border-radius:5px;-moz-border-radius:5px;-webkit-border-radius:5px;cursor:pointer;vertical-align:middle} -.bn[type=submit], +.bn[type=submit], .bn[type=button]{height:28px} .bn[href]{height:26px} .bn.dark{border-color:#666;background:-webkit-linear-gradient(top,#7e7c78,#5c5b58);background:-moz-linear-gradient(top,#7e7c78,#5c5b58);background:-o-linear-gradient(top,#7e7c78,#5c5b58);background:-ms-linear-gradient(top,#7e7c78,#5c5b58);background:linear-gradient(top,#7e7c78,#5c5b58);background-color:#777;color:#fff;box-shadow:0 0 1px #fff inset;-moz-box-shadow:0 0 1px #fff inset;-webkit-box-shadow:0 0 1px #fff inset} .bn.white{border-color:#b5b5b5;background:-webkit-linear-gradient(top,#fff,#f0f0f0 50%,#e4e4e4 50%,#f6f6f6 100%);background:-moz-linear-gradient(top,#fff,#f0f0f0 50%,#e4e4e4 50%,#f6f6f6 100%);background:-o-linear-gradient(top,#fff,#f0f0f0 50%,#e4e4e4 50%,#f6f6f6 100%);background:-ms-linear-gradient(top,#fff,#f0f0f0 50%,#e4e4e4 50%,#f6f6f6 100%);background:linear-gradient(top,#fff,#f0f0f0 50%,#e4e4e4 50%,#f6f6f6 100%);background-color:#fff;color:#000;margin-right:2px} +/* Search */ +.search { text-align: center; text-align:center;background:#f2f0ec;padding:15px 0 0 0; } +.search select { height: 26px; } /* Pagination */ -.pn{font-size:12px;text-align:center;background:#f2f0ec;padding:15px 0;border-top:1px solid #fff} +.pn{font-size:12px;text-align:center;background:#f2f0ec;padding:15px 0;} .pn a{color:#333;text-decoration:none} .pn strong{margin:0 10px} .pn .prev:before{content:"";display:inline-block;width:0;height:0;margin:0 4px 0 0;border:4px solid;border-color:transparent;border-right-color:#999} diff --git a/modules/communication/m.skins/default/message_box.html b/modules/communication/m.skins/default/message_box.html index e3cc0c69a..fe4bc9ba2 100644 --- a/modules/communication/m.skins/default/message_box.html +++ b/modules/communication/m.skins/default/message_box.html @@ -1,11 +1,15 @@ + + + + diff --git a/modules/communication/m.skins/default/messages.html b/modules/communication/m.skins/default/messages.html index 19b20461c..a5c20039f 100644 --- a/modules/communication/m.skins/default/messages.html +++ b/modules/communication/m.skins/default/messages.html @@ -1,15 +1,31 @@ + +

    {$lang->message_box[$message_type]}{$lang->cmd_message_box}

    +
    - + {$page} / {$page_navigation->last_page}
    + + diff --git a/modules/communication/m.skins/default/read_message.html b/modules/communication/m.skins/default/read_message.html index baa758265..b6e1225d6 100644 --- a/modules/communication/m.skins/default/read_message.html +++ b/modules/communication/m.skins/default/read_message.html @@ -1,13 +1,22 @@ + +

    {$message->title}

    {$message->nick_name} | {zdate($message->regdate, "Y.m.d H:i")}
    -
    {$message->content|noescape}
    +
    {$message->content|noescape}
    + + +
    @@ -15,4 +24,7 @@ var confirm_delete_msg = "{$lang->confirm_delete}"; +{@ $messages_skip_header_footer = true} + + diff --git a/modules/communication/m.skins/default/send_message.html b/modules/communication/m.skins/default/send_message.html index 125f3c295..545c96307 100644 --- a/modules/communication/m.skins/default/send_message.html +++ b/modules/communication/m.skins/default/send_message.html @@ -19,13 +19,13 @@
    - - + +
    • {$receiver_info->nick_name} @@ -34,13 +34,18 @@
    • -
    • +
    • {$source_message->content|noescape}
    • - +
    • + +
    • + {$lang->cmd_send_mail} +
    • +
    {$lang->cmd_back} diff --git a/modules/communication/m.skins/rx_prn/add_friend.html b/modules/communication/m.skins/rx_prn/add_friend.html deleted file mode 100644 index eff48afda..000000000 --- a/modules/communication/m.skins/rx_prn/add_friend.html +++ /dev/null @@ -1,28 +0,0 @@ -{@ $communication_popup = TRUE;} - - -
    -

    {$lang->cmd_add_friend}

    -
    -

    {$XE_VALIDATOR_MESSAGE}

    -
    - - - - - -
    - - - - -
    -
    - -
    - -
    - \ No newline at end of file diff --git a/modules/communication/m.skins/rx_prn/add_friend_group.html b/modules/communication/m.skins/rx_prn/add_friend_group.html deleted file mode 100644 index 1667e31c5..000000000 --- a/modules/communication/m.skins/rx_prn/add_friend_group.html +++ /dev/null @@ -1,29 +0,0 @@ -{@ $communication_popup = TRUE;} - - -
    -

    - - {$lang->cmd_rename_friend_group} - - {$lang->cmd_add_friend_group} - -

    -
    -

    {$XE_VALIDATOR_MESSAGE}

    -
    -
    - - - - -
    - - -
    -
    - - -
    -
    -
    \ No newline at end of file diff --git a/modules/communication/m.skins/rx_prn/common_footer.html b/modules/communication/m.skins/rx_prn/common_footer.html deleted file mode 100644 index c63a94a2b..000000000 --- a/modules/communication/m.skins/rx_prn/common_footer.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/modules/communication/m.skins/rx_prn/common_header.html b/modules/communication/m.skins/rx_prn/common_header.html deleted file mode 100644 index 3ea8c6bad..000000000 --- a/modules/communication/m.skins/rx_prn/common_header.html +++ /dev/null @@ -1,81 +0,0 @@ - - - {@Context::addMetaTag("viewport", "width=device-width, user-scalable=yes")} - - - - - - {@ - if(!$layout_info->primary_color) - $layout_info->primary_color = 'red'; - if(!$layout_info->primary_color && $layout_info->customized_primary_color) - $layout_info->primary_color = 'customized'; - if(!$layout_info->customized_primary_color) - $layout_info->customized_primary_color = '#f44336'; - } - - - {@$layout_info->primary_color = 'red';} - -{@ - $material_colors = array( - 'red' => '#f44336', - 'crimson' => '#aa0000', - 'pink' => '#e91e63', - 'purple' => '#9c27b0', - 'deep-purple' => '#673ab7', - 'indigo' => '#3f51b5', - 'deep-blue' => '#00397f', - 'blue' => '#2196f3', - 'light-blue' => '#03a9f4', - 'cyan' => '#00bcd4', - 'teal' => '#009688', - 'green' => '#4caf50', - 'light-green' => '#8bc34a', - 'lime' => '#cddc39', - 'yellow' => '#ffeb3b', - 'amber' => '#ffc107', - 'orange' => '#ff9800', - 'deep-orange' => '#ff5722', - 'brown' => '#795548', - 'grey' => '#9e9e9e', - 'blue-grey' => '#607d8b', - 'black' => '#000000', - 'white' => '#ffffff', - 'customized' => $layout_info->customized_primary_color, - ); -} - -{@$colorset = $material_colors[$member_config->colorset];} -{@$skin_color = $material_colors[$layout_info->primary_color];} - - - {@$skin_color = trim($colorset)} - - {@$skin_color = trim($colorset)[1].trim($colorset)[1].trim($colorset)[2].trim($colorset)[2].trim($colorset)[3].trim($colorset)[3]} - - - {@$skin_color = '#f44336'} - - - - - {@$skin_color = $layout_info->primary_color} - - {@$skin_color = $layout_info->primary_color[1].$layout_info->primary_color[1].$layout_info->primary_color[2].$layout_info->primary_color[2].$layout_info->primary_color[3].$layout_info->primary_color[3]} - - - {@$skin_color = '#f44336'} - - -{@Context::set('prn_less_value', array('red' => hexdec(substr($skin_color, 1, 2)), 'green' => hexdec(substr($skin_color, 3, 2)), 'blue' => hexdec(substr($skin_color, 5, 2)) ))} - -
    -
    - -
    \ No newline at end of file diff --git a/modules/communication/m.skins/rx_prn/css/css.less b/modules/communication/m.skins/rx_prn/css/css.less deleted file mode 100644 index d84d29207..000000000 --- a/modules/communication/m.skins/rx_prn/css/css.less +++ /dev/null @@ -1,485 +0,0 @@ -@charset "UTF-8"; -/* - @method .text-contrast() - @author misol - @brief Select a text color according to WCAG 2.0 contrast guideline. The calcualtion of contrast follows the formula on the guideline. -*/ -.text-contrast(@bg_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i >= 100) and ( ( (luma(@bg_color) + 0.05) / (luma(@dark_color) + 0.05) ) =< ( ( luma(@bright_color) + 0.05) / ( luma(@bg_color) + 0.05) )) { - color: @bright_color; -} -.text-contrast(@bg_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i >= 100) and ( ( (luma(@bg_color) + 0.05) / (luma(@dark_color) + 0.05) ) > ( ( luma(@bright_color) + 0.05) / ( luma(@bg_color) + 0.05) )) { - color: @dark_color; -} -.text-contrast(@bg_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i < 100) and ( ( (luma(@bg_color) + 0.05) / (luma(@dark_color) + 0.05) ) =< ( ( luma(@bright_color) + 0.05) / ( luma(@bg_color) + 0.05) )) and ( ( ( luma(@bright_color) + 0.05) / ( luma(@bg_color) + 0.05) ) > 4.5 * @multi ) { - color: @bright_color; -} -.text-contrast(@bg_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i < 100) and ( ( (luma(@bg_color) + 0.05) / (luma(@dark_color) + 0.05) ) =< ( ( luma(@bright_color) + 0.05) / ( luma(@bg_color) + 0.05) ) ) and ( ( ( luma(@bright_color) + 0.05) / ( luma(@bg_color) + 0.05) ) =< 4.5 * @multi ) { - .text-contrast(@bg_color; lighten(@bright_color, 5%); @dark_color; @multi; @i + 1); -} -.text-contrast(@bg_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i < 100) and ( ( (luma(@bg_color) + 0.05) / (luma(@dark_color) + 0.05) ) > ( ( luma(@bright_color) + 0.05) / ( luma(@bg_color) + 0.05) )) and ( ( (luma(@bg_color) + 0.05) / (luma(@dark_color) + 0.05) ) > 4.5 * @multi ) { - color: @dark_color; -} -.text-contrast(@bg_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i < 100) and (( (luma(@bg_color) + 0.05) / (luma(@dark_color) + 0.05) ) > ( ( luma(@bright_color) + 0.05) / ( luma(@bg_color) + 0.05) )) and ( ( (luma(@bg_color) + 0.05) / (luma(@dark_color) + 0.05) ) =< 4.5 * @multi ) { - .text-contrast(@bg_color; @bright_color; darken(@dark_color, 5%); @multi; @i + 1); -} - -/* - @method .bg-contrast() - @author misol - @brief Select a background color, which has less contrast background color than WCAG 2.0 contrast guideline. On the WCAG 2.0 guideline, bigger string can have less contrast as 3.0. -*/ -.bg-contrast(@text_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i >= 100) and ( ( (luma(@text_color) + 0.05) / (luma(@dark_color) + 0.05) ) =< ( ( luma(@bright_color) + 0.05) / ( luma(@text_color) + 0.05) )) { - background: @bright_color; -} -.bg-contrast(@text_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i >= 100) and ( ( (luma(@text_color) + 0.05) / (luma(@dark_color) + 0.05) ) > ( ( luma(@bright_color) + 0.05) / ( luma(@text_color) + 0.05) )) { - background: @dark_color; -} - -.bg-contrast(@text_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i < 100) and ( ( (luma(@text_color) + 0.05) / (luma(@dark_color) + 0.05) ) =< ( ( luma(@bright_color) + 0.05) / ( luma(@text_color) + 0.05) )) and ( ( ( luma(@bright_color) + 0.05) / ( luma(@text_color) + 0.05) ) > 3 * @multi ) { - background: @bright_color; -} -.bg-contrast(@text_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i < 100) and ( ( (luma(@text_color) + 0.05) / (luma(@dark_color) + 0.05) ) =< ( ( luma(@bright_color) + 0.05) / ( luma(@text_color) + 0.05) ) ) and ( ( ( luma(@bright_color) + 0.05) / ( luma(@text_color) + 0.05) ) =< 3 * @multi ) { - .bg-contrast(@text_color; lighten(@bright_color,3%); @dark_color; @multi; @i + 1); -} -.bg-contrast(@text_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i < 100) and ( ( (luma(@text_color) + 0.05) / (luma(@dark_color) + 0.05) ) > ( ( luma(@bright_color) + 0.05) / ( luma(@text_color) + 0.05) )) and ( ( (luma(@text_color) + 0.05) / (luma(@dark_color) + 0.05) ) > 3 * @multi ) { - background: @dark_color; -} -.bg-contrast(@text_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i < 100) and (( (luma(@text_color) + 0.05) / (luma(@dark_color) + 0.05) ) > ( ( luma(@bright_color) + 0.05) / ( luma(@text_color) + 0.05) )) and ( ( (luma(@text_color) + 0.05) / (luma(@dark_color) + 0.05) ) =< 3 * @multi ) { - .bg-contrast(@text_color; @bright_color; darken(@dark_color, 3%); @multi; @i + 1); -} - -/* As LESS library in Rhymix substitude variables as an strings, convert colors as the color objects of LESS. */ -@color: rgb(@red, @green, @blue); - -/* As this file handle some wild-selectors to control display settings, hide inline script and style codes. */ -script, style -{ - display:none!important; -} - -/* Member skin container, default settings */ - -.rx_prn_communication -{ - /* font-size */ - .font-xl() - { - /* font-size: 45; 1 */ - font-size: 1.25em; - } - .font-l() - { - /* font-size: 40; 1_2, 1_3, 2_1, 4, 5_1 */ - font-size: 1.11em; - } - .font-m() - { - /* font-size: 36; 1_4, 2_2, 4_1, 5_2 */ - font-size: 1em; - } - .font-s() - { - /* font-size: 30; 2_3, 3_1, 5_3 */ - font-size: 0.833em; - } - - - /* font-weight */ - .font-bold() - { - font-weight: 700; - } - - /* font-color */ - .font-point() - { - /* 4, 4_1 */ - color: lighten(@color, 5%) - } - - .font-dark() - { - color: #484848 - } - .font-gray() - { - color: #757575 - } - .font-light() - { - color: #bdbdbd - } - - - .background-lightgray() - { - background-color: #fafafa - } - .background-gray() - { - background-color: #bdbdbd - } - .background-lightpoint() - { - color: lighten(@color, 10%) - } - .background-point() - { - color: @color - } - - - font-family: "맑은 고딕", "Apple SD Gothic Neo","나눔고딕",NanumGothic,'Nanum Gothic',Arial,Helvetica,sans-serif; - font-size: 14px; - text-align: justify; - margin: 8px 0px; - padding: 0 5px; - .font-dark(); - - - a - { - .font-dark(); - text-decoration: none; - } - - .pos-right - { - position:absolute; - right:0 - } - - /* Tab over the main content. */ - div.rx_prn_tab{ - background: #ffffff; - margin: 5px 0 0; - box-sizing: border-box; - overflow: hidden; - width:100%; - ul.rx_prn_tab{ - margin: 0; - padding: 0; - list-style: outside none none; - display: block; - text-decoration: none; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - &>li { - display:inline-block; - line-height: 1.5; - position: relative; - padding: 0 10px; - a { - display:inline-block; - letter-spacing: -1px; - line-height: 3; - border-bottom: solid 3px #fff; - padding: 0 5px; - .font-l(); - .font-bold(); - } - a:hover, a:focus, &.active a{ - border-bottom: solid 3px @color; - } - } - } - } - - - /* member forms */ - .prn-narrow - { - max-width:400px; - margin:0px auto; - box-sizing:border-box; - } - .prn-body - { - box-sizing:border-box; - border-radius: 25px; - .background-lightgray(); - margin: 0 0 17px; - padding: 1px 0; - &>* - { - padding: 5px 20px; - margin:0; - border: 0; - &:first-child { - padding-top: 20px; - } - &:last-child { - padding-bottom: 20px; - } - } - h1 - { - padding: 15px 5px; - margin: 5px 15px; - border-bottom: 1px solid #d1d1d1; - .font-l(); - .font-bold(); - } - .prn-anchor-buttons - { - margin: 5px 0px; - &>a, &>label, &>button, &>select, &>input[type="submit"] - { - display: block; - width: 100%; - -webkit-appearance: none; - box-sizing: border-box; - border-top: none; - border-right: none; - border-bottom: 1px solid #d1d1d1; - border-left: none; - border-radius: 0px; - background-color: #fff; - padding: 10px 15px; - text-decoration: none; - text-align: center; - .font-m(); - .font-point(); - &:hover, &:focus - { - .bg-contrast(#000; lighten(@color,5%); darken(@color,5%); 4.0); - } - &:first-child - { - border-top: 1px solid #d1d1d1; - } - } - } - } - form - { - overflow: hidden; - width:100%; - white-space: normal; - box-sizing: border-box; - div.control-group - { - &>* - { - display:block; - position:relative; - box-sizing: border-box; - width:100%; - height: auto; - margin:0px; - margin-top: 10px; - } - &>*:first-child - { - margin-top: 0; - } - &>input, &>select, button, #prn_profile_imagetag label.prn_button - { - border: 1px solid #d1d1d1; - border-radius: 25px; - margin-top: 0; - padding: 8px 15px; - -webkit-appearance: none; - line-height: 2; - height: auto; - .font-gray(); - .font-m(); - } - &>label, &>div.control-label - { - font-weight: bold; - border:0; - } - } - } - form input[type="submit"], form button - { - padding: 10px; - vertical-align: bottom; - .bg-contrast(#000; lighten(@color,5%); darken(@color,5%); 3.0); - font-weight: bold; - text-shadow: none; - border:0; - color: #000; - &:hover, &:focus - { - .bg-contrast(#000; lighten(@color,5%); darken(@color,5%); 4.0); - } - } - form div.control-group>input[type="submit"], .prn-footer{ - margin: 20px 0 0; - } - .prn-footer.prn-anchor-buttons { - text-align:right; - &>a { - display:inline-block; - } - } - - /* message; error, info, update */ - .rx_prn-notice, .rx_prn-notice.info - { - .bg-contrast(#000; lighten(@color, 5%); darken(@color, 5%); 5.0); - color: #000; - border-radius: 15px; - padding: 15px; - margin: 10px; - margin-top:0; - text-align: justify; - .font-gray(); - .font-m(); - } - .rx_prn-notice.error - { - background: #fff3e0; - .text-contrast(#fff3e0); - } - .rx_prn-notice.update - { - background: #e8f5e9; - .text-contrast(#e8f5e9); - } - .rx_prn-notice>* - { - padding: 0; - margin:0; - } - - /* The list of document style */ - .content_basic{ - position:relative; - display:inline-block; - max-width:100%; - vertical-align: middle; - overflow:hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - .content_subinfo - { - color: #9e9e9e; - font-size: 12px; - margin: 0 3px 0 7px; - overflow:hidden; - } - ul.rx_sw_list { - list-style: outside none none; - margin: 20px 0px; - padding: 0; - border-bottom: 1px solid #e0e0e0; - li { - position:relative; - border-top: 1px solid #e0e0e0; - overflow: hidden; - padding:0; - } - .cont_a { - color: #222; - display: block; - letter-spacing: -1px; - line-height: 18px; - margin: 0; - overflow: hidden; - padding: 0.667em 70px 0.733em 15px; - text-overflow: ellipsis; - white-space: nowrap; - text-decoration: none; - &.no_delete { - padding-right: 15px; - } - &:hover, &:focus { - .text-contrast(#fff; darken(@color,5%); lighten(@color,5%)); - } - } - .content_delete{ - position:absolute; - top:0; - right:2px; - button{ - background: lighten(@color, 5%); - font-size: 14px; - border:0; - border-radius: 25px; - .text-contrast(lighten(@color, 5%)); - padding: 10px 15px; - vertical-align: bottom; - &:hover, &:focus - { - .bg-contrast(#000; lighten(@color,5%); darken(@color,5%); 4.0); - } - } - input[type="checkbox"] - { - width: 30px; - height: 30px; - } - } - } - .pagination ul - { - display:block; - list-style: outside none none; - text-align:center; - padding: 0; - margin: 0; - li{ - display:inline-block; - &>a - { - display: inline-block; - border-radius: 50%; - background-color: #fff; - padding: 10px 15px; - text-decoration: none; - .font-m(); - &:hover, &:focus - { - .bg-contrast(#000; lighten(@color,5%); darken(@color,5%); 4.0); - } - } - &.active>a - { - background-color: #d1d1d1; - font-weight:bold; - color:#000; - } - } - } - .prn-footer - { - &>a - { - display: block; - border-radius: 25px; - border: 1px solid #d1d1d1; - background-color: #fff; - padding: 10px 15px; - text-decoration: none; - text-align: center; - .font-m(); - &:hover, &:focus - { - .bg-contrast(#000; lighten(@color,5%); darken(@color,5%); 4.0); - } - } - } -} - -/** - * warning label from modules\admin\tpl\css\admin.bootstrap.css - */ -.warning_label{ - display:inline-block; - margin:5px 15px !important; - padding:2px 4px; - font-size:11.844px; - font-weight:bold; - line-height:14px; - color:#ffffff; - text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25); - -webkit-border-radius:3px; - -moz-border-radius:3px; - border-radius:3px; - box-sizing:border-box; -} -.warning_label-important{background-color:#b94a48} diff --git a/modules/communication/m.skins/rx_prn/filter/add_friend.xml b/modules/communication/m.skins/rx_prn/filter/add_friend.xml deleted file mode 100644 index b1ec11d0d..000000000 --- a/modules/communication/m.skins/rx_prn/filter/add_friend.xml +++ /dev/null @@ -1,9 +0,0 @@ - -
    - - - - - - - diff --git a/modules/communication/m.skins/rx_prn/filter/add_friend_group.xml b/modules/communication/m.skins/rx_prn/filter/add_friend_group.xml deleted file mode 100644 index cadf2644f..000000000 --- a/modules/communication/m.skins/rx_prn/filter/add_friend_group.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/modules/communication/m.skins/rx_prn/filter/delete_checked_friend.xml b/modules/communication/m.skins/rx_prn/filter/delete_checked_friend.xml deleted file mode 100644 index 4d1466994..000000000 --- a/modules/communication/m.skins/rx_prn/filter/delete_checked_friend.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/modules/communication/m.skins/rx_prn/filter/delete_checked_message.xml b/modules/communication/m.skins/rx_prn/filter/delete_checked_message.xml deleted file mode 100644 index 1d21f33c8..000000000 --- a/modules/communication/m.skins/rx_prn/filter/delete_checked_message.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/modules/communication/m.skins/rx_prn/filter/delete_friend_group.xml b/modules/communication/m.skins/rx_prn/filter/delete_friend_group.xml deleted file mode 100644 index 5d269abaa..000000000 --- a/modules/communication/m.skins/rx_prn/filter/delete_friend_group.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/modules/communication/m.skins/rx_prn/filter/move_friend.xml b/modules/communication/m.skins/rx_prn/filter/move_friend.xml deleted file mode 100644 index 8b1eef8cd..000000000 --- a/modules/communication/m.skins/rx_prn/filter/move_friend.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/modules/communication/m.skins/rx_prn/filter/send_message.xml b/modules/communication/m.skins/rx_prn/filter/send_message.xml deleted file mode 100644 index 418965112..000000000 --- a/modules/communication/m.skins/rx_prn/filter/send_message.xml +++ /dev/null @@ -1,12 +0,0 @@ - -
    - - - - - - - - - -
    diff --git a/modules/communication/m.skins/rx_prn/filter/update_allow_message.xml b/modules/communication/m.skins/rx_prn/filter/update_allow_message.xml deleted file mode 100644 index d005b43f5..000000000 --- a/modules/communication/m.skins/rx_prn/filter/update_allow_message.xml +++ /dev/null @@ -1,5 +0,0 @@ - -
    - - - diff --git a/modules/communication/m.skins/rx_prn/friends.html b/modules/communication/m.skins/rx_prn/friends.html deleted file mode 100644 index 70f91812f..000000000 --- a/modules/communication/m.skins/rx_prn/friends.html +++ /dev/null @@ -1,80 +0,0 @@ - - - -
    -
    -

    {$member_title = $lang->cmd_view_friend}

    -
    {lang('common.total')}: {number_format($total_count)}
    -
    -

    {$XE_VALIDATOR_MESSAGE}

    -
    -
    - - - - -
    - - -
    - -
    -
    - - - - -
    - - -
    -
    - - - - - -
    -
    -
    -
    - -
    - - -
    - diff --git a/modules/communication/m.skins/rx_prn/js/communication.js b/modules/communication/m.skins/rx_prn/js/communication.js deleted file mode 100644 index ec0a495a9..000000000 --- a/modules/communication/m.skins/rx_prn/js/communication.js +++ /dev/null @@ -1,119 +0,0 @@ -/* 쪽지 발송 */ -function completeSendMessage(ret_obj) { - alert(ret_obj['message']); - window.close(); -} - -function doSendMessage(member_srl, message_srl) { - if(typeof(message_srl)=='undefined') message_srl = 0; - var url = request_uri.setQuery('module','communication').setQuery('act','dispCommunicationSendMessage').setQuery('receiver_srl',member_srl).setQuery('message_srl',message_srl); - popopen(url, 'sendMessage'); -} - -/* 개별 쪽지 삭제 */ -function doDeleteMessage(message_srl) { - if(!message_srl) return; - - var params = new Array(); - params['message_srl'] = message_srl; - exec_xml('communication', 'procCommunicationDeleteMessage', params, completeDeleteMessage); -} - -function completeDeleteMessage(ret_obj) { - alert(ret_obj['message']); - location.href = current_url.setQuery('message_srl',''); -} - -/* 개별 쪽지 보관 */ -function doStoreMessage(message_srl) { - if(!message_srl) return; - - var params = new Array(); - params['message_srl'] = message_srl; - exec_xml('communication', 'procCommunicationStoreMessage', params, completeStoreMessage); -} - -function completeStoreMessage(ret_obj) { - alert(ret_obj['message']); - location.href = current_url.setQuery('message_srl',''); -} - -/* 친구 추가 후 */ -function completeAddFriend(ret_obj) { - alert(ret_obj['message']); - var member_srl = ret_obj['member_srl']; - if(opener && opener.loaded_member_menu_list) { - opener.loaded_member_menu_list[ret_obj['member_srl']] = ''; - } - window.close(); -} - -/* 친구 그룹 추가 후 */ -function completeAddFriendGroup(ret_obj) { - alert(ret_obj['message']); - if(opener) opener.location.href = opener.location.href; - window.close(); -} - -/* 친구 그룹 삭제 */ -function doDeleteFriendGroup() { - var friend_group_srl = jQuery('#target_friend_group_srl option:selected').val(); - if(!friend_group_srl) return; - - var fo_obj = jQuery('#for_delete_group').get(0); - fo_obj.friend_group_srl.value = friend_group_srl; - - procFilter(fo_obj, delete_friend_group); -} - -function completeDeleteFriendGroup(ret_obj) { - alert(ret_obj['message']); - location.href = current_url.setQuery('friend_group_srl',''); -} - -/* 친구 그룹의 이름 변경 */ -function doRenameFriendGroup() { - var friend_group_srl = jQuery('#target_friend_group_srl option:selected').val(); - if(!friend_group_srl) return; - - popopen("./?module=communication&act=dispCommunicationAddFriendGroup&friend_group_srl="+friend_group_srl); -} - -/* 친구 그룹 이동 */ -function doMoveFriend() { - var fo_obj = jQuery('#fo_friend_list').get(0); - procFilter(fo_obj, move_friend); -} - -/* 친구 그룹 선택 */ -function doJumpFriendGroup() { - var sel_val = jQuery('#jumpMenu option:selected').val(); - location.href = current_url.setQuery('friend_group_srl', sel_val); -} - -function isRxPrnTouchable() { - var el = document.createElement('div'); - el.setAttribute('ontouchstart', 'return;'); // or try "ontouchstart" - return typeof el.ontouchstart === "function"; -} - -$(document).ready(function() { - $('.__submit_group button[type=submit]').click(function(e){ - var sel_val = $('input[name="friend_srl_list[]"]:checked').length; - if(sel_val == 0) - { - e.preventDefault(); - return false; - } - }); - if(isRxPrnTouchable()) { - $(".rx_prn_communication div.rx_prn_tab ul.rx_prn_tab").css('white-space', 'nowrap'); - try - { - $(".rx_prn_communication div.rx_prn_tab ul.rx_prn_tab").animate({ - scrollLeft: $(".rx_prn_communication div.rx_prn_tab ul.rx_prn_tab li.active").offset().left - }, 300); - } catch (e) { - } - } -}); \ No newline at end of file diff --git a/modules/communication/m.skins/rx_prn/messages.html b/modules/communication/m.skins/rx_prn/messages.html deleted file mode 100644 index 0210ae738..000000000 --- a/modules/communication/m.skins/rx_prn/messages.html +++ /dev/null @@ -1,84 +0,0 @@ - - - -
    -
    -

    {escape($message->title)}

    -
    - {$message->nick_name} / {zdate($message->regdate, "Y-m-d H:i")} -
    -
    - {$message->content} -
    - -
    -
    -

    {$val}

    - - - -
    - - - - - -
    - - -
    -
    -
    - {$val} -
    -
    -
    - - diff --git a/modules/communication/m.skins/rx_prn/new_message.html b/modules/communication/m.skins/rx_prn/new_message.html deleted file mode 100644 index 24ea7870e..000000000 --- a/modules/communication/m.skins/rx_prn/new_message.html +++ /dev/null @@ -1,19 +0,0 @@ -{@ $communication_popup = TRUE} - -
    -
    -

    {escape($message->title)}

    -
    - {$message->nick_name} / {zdate($message->regdate, "Y-m-d H:i")} -
    -
    - {$message->content|noescape} -
    - -
    -
    - diff --git a/modules/communication/m.skins/rx_prn/send_message.html b/modules/communication/m.skins/rx_prn/send_message.html deleted file mode 100644 index 019bbf969..000000000 --- a/modules/communication/m.skins/rx_prn/send_message.html +++ /dev/null @@ -1,38 +0,0 @@ -{@ $communication_popup = TRUE} - -
    -
    -

    {$lang->cmd_send_message}

    -
    -

    {$XE_VALIDATOR_MESSAGE}

    -
    -
    - {$lang->warning} -
    - - {$lang->msg_allow_message_friend} - - {$lang->msg_allow_meesage_Block} - -
    {$lang->msg_allow_message_please} -
    -
    - - - - - -
    - - {$receiver_info->nick_name} - - -
    - {$editor|noescape} -
    - -
    -
    -
    -
    - diff --git a/modules/communication/m.skins/rx_prn/skin.xml b/modules/communication/m.skins/rx_prn/skin.xml deleted file mode 100644 index e323e0a0c..000000000 --- a/modules/communication/m.skins/rx_prn/skin.xml +++ /dev/null @@ -1,108 +0,0 @@ - - - 필요할 때 라이믹스 - Rhymix PRN - 필요할 때 꺼내보는 라이믹스. 캡슐 모양의 둥근 라이믹스 커뮤니케이션 모듈 스킨입니다. - You need Rhymix. A skin of the communication module of Rhymix with round, capsule-like shapes. - 0.1 - 2017-08-09 - - - misol - misol - - - 마더캣 - Mothercat - - - - 사이트 테마 색 - The site theme color - - - 붉은 색 - Red - - - 크림슨 - Crimson - - - 분홍 - Pink - - - 보라 - Purple - - - 진보라 - Deep Purple - - - 인디고 - Indigo - - - 짙은 파랑 - Deep Blue - - - 파랑 - Blue - - - 밝은 파랑 - Light Blue - - - 시안 - Cyan - - - - Teal - - - 초록 - Green - - - 연한 초록 - Light Green - - - 라임 - Lime - - - 노랑 - Yellow - - - 앰버 - Amber - - - 주황 - Orange - - - 진한 주황 - Deep Orange - - - 갈색 - Brown - - - 회색 - Grey - - - 푸른 회색 - Blue Grey - - - diff --git a/modules/communication/queries/deleteFriend.xml b/modules/communication/queries/deleteFriend.xml index 32f5f5ba4..3e524e395 100644 --- a/modules/communication/queries/deleteFriend.xml +++ b/modules/communication/queries/deleteFriend.xml @@ -4,6 +4,7 @@ - + + diff --git a/modules/communication/queries/getMessages.xml b/modules/communication/queries/getMessages.xml new file mode 100644 index 000000000..cde6b67ce --- /dev/null +++ b/modules/communication/queries/getMessages.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/modules/communication/queries/getReadedMessages.xml b/modules/communication/queries/getReadedMessages.xml index e8465ebf8..604b5dbe8 100644 --- a/modules/communication/queries/getReadedMessages.xml +++ b/modules/communication/queries/getReadedMessages.xml @@ -1,4 +1,4 @@ - +
    @@ -16,8 +16,14 @@ - - + + + + + + + + diff --git a/modules/communication/queries/getReceivedMessages.xml b/modules/communication/queries/getReceivedMessages.xml index 09ea8a7be..fae24aff1 100644 --- a/modules/communication/queries/getReceivedMessages.xml +++ b/modules/communication/queries/getReceivedMessages.xml @@ -16,7 +16,13 @@ - + + + + + + + diff --git a/modules/communication/queries/getRelatedMessages.xml b/modules/communication/queries/getRelatedMessages.xml new file mode 100644 index 000000000..0967d7783 --- /dev/null +++ b/modules/communication/queries/getRelatedMessages.xml @@ -0,0 +1,15 @@ + + +
    + + + + + + + + + + + + diff --git a/modules/communication/queries/getSendedMessages.xml b/modules/communication/queries/getSendedMessages.xml index aa7a0239b..ccc948bcd 100644 --- a/modules/communication/queries/getSendedMessages.xml +++ b/modules/communication/queries/getSendedMessages.xml @@ -16,7 +16,13 @@ - + + + + + + + diff --git a/modules/communication/queries/getStoredMessages.xml b/modules/communication/queries/getStoredMessages.xml index 553d3c23a..f9d99a3b0 100644 --- a/modules/communication/queries/getStoredMessages.xml +++ b/modules/communication/queries/getStoredMessages.xml @@ -17,6 +17,12 @@ + + + + + + diff --git a/modules/communication/queries/setMessageStored.xml b/modules/communication/queries/setMessageStored.xml index 440ba29af..b2170d00c 100644 --- a/modules/communication/queries/setMessageStored.xml +++ b/modules/communication/queries/setMessageStored.xml @@ -3,7 +3,7 @@
    - + diff --git a/modules/communication/scripts/cleanMessageFiles.php b/modules/communication/scripts/cleanMessageFiles.php new file mode 100644 index 000000000..c56ee3094 --- /dev/null +++ b/modules/communication/scripts/cleanMessageFiles.php @@ -0,0 +1,67 @@ + 'msg', + 'list_count' => 50, + 'regdate_before' => date('YmdHis', time() - ($days * 86400)), + ]); + + if ($output->toBool()) + { + if ($output->data) + { + $oDB->begin(); + foreach ($output->data as $file_info) + { + $oFileController->deleteFile($file_info->file_srl); + } + $oDB->commit(); + + if ($output->page_navigation && $output->page_navigation->total_count == count($output->data)) + { + break; + } + } + else + { + break; + } + } + else + { + echo "Error while deleting message attachments older than $days days.\n"; + echo $output->getMessage() . "\n"; + $exit_status = 11; + break; + } +} +if ($exit_status == 0) +{ + echo "Successfully deleted all message attachments older than $days days.\n"; +} diff --git a/modules/communication/skins/default/add_friend.html b/modules/communication/skins/default/add_friend.html index cd70b776f..a53acc693 100644 --- a/modules/communication/skins/default/add_friend.html +++ b/modules/communication/skins/default/add_friend.html @@ -1,6 +1,11 @@ + + + + +

    {$lang->cmd_add_friend}

    @@ -11,6 +16,7 @@ +
    @@ -22,7 +28,7 @@ + {$lang->cmd_add_friend_group} diff --git a/modules/communication/skins/default/add_friend_group.html b/modules/communication/skins/default/add_friend_group.html index 0de2f5afb..2a3b7a344 100644 --- a/modules/communication/skins/default/add_friend_group.html +++ b/modules/communication/skins/default/add_friend_group.html @@ -1,5 +1,10 @@ + + + + +

    @@ -16,6 +21,7 @@ +
    diff --git a/modules/communication/skins/default/common_header.html b/modules/communication/skins/default/common_header.html index 8e504c290..4b29bf587 100644 --- a/modules/communication/skins/default/common_header.html +++ b/modules/communication/skins/default/common_header.html @@ -1,8 +1,8 @@
    -
    @@ -37,7 +37,7 @@

    - + diff --git a/modules/communication/skins/default/js/communication.js b/modules/communication/skins/default/js/communication.js index 99033abc7..84a712330 100644 --- a/modules/communication/skins/default/js/communication.js +++ b/modules/communication/skins/default/js/communication.js @@ -6,8 +6,8 @@ function completeSendMessage(ret_obj) { function doSendMessage(member_srl, message_srl) { if(typeof(message_srl)=='undefined') message_srl = 0; - var url = request_uri.setQuery('module','communication').setQuery('act','dispCommunicationSendMessage').setQuery('receiver_srl',member_srl).setQuery('message_srl',message_srl); - popopen(url, 'sendMessage'); + var url = request_uri.setQuery('mid',current_mid).setQuery('act','dispCommunicationSendMessage').setQuery('receiver_srl',member_srl).setQuery('message_srl',message_srl).setQuery('window_type', 'self'); + window.location = url; } /* 개별 쪽지 삭제 */ @@ -27,10 +27,20 @@ function completeDeleteMessage(ret_obj) { /* 개별 쪽지 보관 */ function doStoreMessage(message_srl) { if(!message_srl) return; + var params = { message_srl: message_srl }; + exec_json('communication.procCommunicationStoreMessage', params, function(data) { + alert(data.message); + location.href = current_url.setQuery('message_type', 'T'); + }); +} - var params = new Array(); - params['message_srl'] = message_srl; - exec_xml('communication', 'procCommunicationStoreMessage', params, completeStoreMessage); +function doRestoreMessage(message_srl) { + if(!message_srl) return; + var params = { message_srl: message_srl }; + exec_json('communication.procCommunicationRestoreMessage', params, function(data) { + alert(data.message); + location.href = current_url.setQuery('message_type', 'R'); + }); } function completeStoreMessage(ret_obj) { diff --git a/modules/communication/skins/default/messages.html b/modules/communication/skins/default/messages.html index 773248ca5..b7ab60b33 100644 --- a/modules/communication/skins/default/messages.html +++ b/modules/communication/skins/default/messages.html @@ -2,9 +2,13 @@ +
    +

    {$XE_VALIDATOR_MESSAGE}

    +
    +
    - + {$val}
    @@ -17,7 +21,7 @@
    -
    {$lang->nick_name}
    {$lang->friend_group} {$lang->nick_name} {$lang->regdate}
    +
    @@ -28,14 +32,27 @@ - + + + + +
    {$message->title}
    + {$message->content|noescape}
    + +
    +
    +
    @@ -80,13 +97,26 @@
    -
    @@ -83,6 +83,12 @@
    +
    + +
    + +
    +
    + + + \ No newline at end of file + $('select[name="message_option"]').change(function(){ + if ($(this).val()==='others') { + $('#declare_message').show(); + } else { + $('#declare_message').hide(); + } + setFixedPopupSize(); + }); + }); + })(jQuery); + diff --git a/modules/document/tpl/declared_list.html b/modules/document/tpl/declared_list.html index f5b038b47..3a0f5e953 100644 --- a/modules/document/tpl/declared_list.html +++ b/modules/document/tpl/declared_list.html @@ -2,7 +2,6 @@ xe.lang.msg_empty_search_target = '{$lang->msg_empty_search_target}'; xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}'; -

    {$XE_VALIDATOR_MESSAGE}

    @@ -18,7 +17,7 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}'; | {$status_name_list['SECRET']} | - {$status_name_list['TEMP']} + {$status_name_list['TEMP']} | {$lang->cmd_declared_list}({number_format($total_count)}) @@ -35,7 +34,7 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}'; {$lang->title} {$lang->writer} {$lang->readed_count} - {$lang->cmd_vote}(+/-) + {$lang->cmd_vote} / {$lang->cmd_vote_down} {$lang->declared_count} {$lang->original_date} {$lang->latest_declared_date} @@ -135,6 +134,12 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}';
    +
    + +

    {$lang->msg_select_menu}

    @@ -179,7 +184,7 @@ jQuery(function($){ $modalBody.removeClass('showTree'); } else if(thisValue == 'move' || thisValue == 'copy') { $.xeShowMenuSelectorIn($('._menuSelector')); - + $modalBody.addClass('showTree'); $tree = $('._menuSelector .tree'); $tree.bind('select_node.jstree', function(a,b){ @@ -196,7 +201,12 @@ jQuery(function($){ $('#manageForm input[name=module_srl]').val(''); }); } + if (thisValue == 'cancelDeclare') { + $modalBody.find('.prevent_redeclare').show(); + } else { + $modalBody.find('.prevent_redeclare').hide(); + } } }); }); - \ No newline at end of file + diff --git a/modules/document/tpl/document_config.html b/modules/document/tpl/document_config.html index b8a7d0137..88f1507c0 100644 --- a/modules/document/tpl/document_config.html +++ b/modules/document/tpl/document_config.html @@ -1,5 +1,9 @@ +
    +

    {$XE_VALIDATOR_MESSAGE}

    +
    +
    @@ -37,7 +41,20 @@

    {$lang->about_cmd_mobile_icon_setting}

    - +
    + +
    + +

    {$lang->about_search_division}

    +
    +
    +
    + +
    +

    +

    {$lang->about_recalculate_category_counts}

    +
    +
    diff --git a/modules/document/tpl/document_list.html b/modules/document/tpl/document_list.html index 79a682371..d93c38115 100644 --- a/modules/document/tpl/document_list.html +++ b/modules/document/tpl/document_list.html @@ -2,7 +2,6 @@ xe.lang.msg_empty_search_target = '{$lang->msg_empty_search_target}'; xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}'; -

    {$XE_VALIDATOR_MESSAGE}

    @@ -18,7 +17,7 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}'; | {$status_name_list['SECRET']}({number_format($total_count)}) | - {$status_name_list['TEMP']}({number_format($total_count)}) + {$status_name_list['TEMP']}({number_format($total_count)}) | {$lang->cmd_declared_list} | @@ -33,7 +32,7 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}';
    -
    +
    @@ -51,11 +50,18 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}'; - {htmlspecialchars($oDocument->getTitleText())}{$lang->no_title_document} - - - - {$module_list[$oDocument->get('module_srl')]->browser_title} - + + + {escape($oDocument->getTitleText(), false)}{$lang->no_title_document} + - {$module_list[$oDocument->get('module_srl')]->browser_title} + + {escape($oDocument->getTitleText(), false)}{$lang->no_title_document} + - {$module_list[$oDocument->get('module_srl')]->browser_title} + + + {escape($oDocument->getTitleText(), false)}{$lang->no_title_document} + + {$oDocument->getNickName()} ({$member_nick_name[abs($oDocument->get('member_srl'))]}) @@ -68,21 +74,20 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}'; {$oDocument->getRegdate($oDocument->getRegdateTime() > time() - 86400 ? 'H:i' : 'm-d')} {$oDocument->get('ipaddress')} - {$status_name_list[$oDocument->get('status')]} + {$oDocument->getStatusText()} -
    - + -
    + + @@ -122,14 +127,19 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}'; - + - + {$lang->cmd_cancel}
    @@ -157,8 +167,20 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}'; -
    - +
    + + + +
    @@ -205,7 +227,7 @@ jQuery(function($){ $modalBody.removeClass('showTree'); } else if(thisValue == 'move' || thisValue == 'copy') { $.xeShowMenuSelectorIn($('._menuSelector')); - + $modalBody.addClass('showTree'); $tree = $('._menuSelector .tree'); $tree.bind('select_node.jstree', function(a,b){ @@ -221,5 +243,13 @@ jQuery(function($){ } } }); + $('#message').prop('disabled', true); + $('#message_options').on('change', 'input[name="send_message"]', function(){ + if($('#send_custom_message').is(':checked')) { + $('#message').prop("disabled", false); + } else { + $('#message').prop("disabled", true); + } + }); }); diff --git a/modules/document/tpl/document_module_config.html b/modules/document/tpl/document_module_config.html index e578c5116..7c44ade7d 100644 --- a/modules/document/tpl/document_module_config.html +++ b/modules/document/tpl/document_module_config.html @@ -4,7 +4,7 @@ - +
    @@ -18,9 +18,9 @@
    - +
    - @@ -28,20 +28,50 @@
    - +
    -
    +
    + +
    + + + +
    +
    +
    + +
    + + +
    +
    - - + +
    diff --git a/modules/document/tpl/document_trash_list.html b/modules/document/tpl/document_trash_list.html index 9b13e8a1a..6f9803e40 100644 --- a/modules/document/tpl/document_trash_list.html +++ b/modules/document/tpl/document_trash_list.html @@ -44,7 +44,7 @@ [{$oDocument->getTrackbackCount()}] - {htmlspecialchars($oDocument->get('trash_nick_name'), ENT_COMPAT | ENT_HTML401, 'UTF-8', false)} + {escape($oDocument->get('trash_nick_name'), false)} {zdate($oDocument->get('trash_date'), "Y-m-d H:i:s")} {$oDocument->get('ipaddress')} {$oDocument->get('trash_description')} diff --git a/modules/document/tpl/extra_keys.html b/modules/document/tpl/extra_keys.html index d0614ec71..d9a9f9268 100644 --- a/modules/document/tpl/extra_keys.html +++ b/modules/document/tpl/extra_keys.html @@ -1,15 +1,16 @@ + - + {@ $selected_var = $extra_keys[$selected_var_idx] }

    {$XE_VALIDATOR_MESSAGE}

    - +

    {$lang->extra_vars}

    @@ -23,14 +24,15 @@
    - -

    {$lang->about_extra_vars_eid_value}

    + +

    {$lang->about_extra_vars_eid_value}

    - + +

    {$lang->about_extra_vars_column_name}

    @@ -48,17 +50,33 @@
    -
    +
    + +
    + + +

    {$lang->about_extra_vars_is_strict}

    +
    +
    +
    - -

    {$lang->about_extra_vars_default_value}

    + +

    {$lang->about_extra_vars_default_value}

    +
    +
    +
    + +
    + +

    {$lang->about_extra_vars_options}

    - + +

    {$lang->about_extra_vars_description}

    @@ -68,6 +86,13 @@
    +
    + +
    + + +
    +
    @@ -92,7 +117,7 @@ {$lang->cmd_insert}
    - +
    @@ -102,19 +127,26 @@ + - + - - + + + - - -
    {$lang->no}{$lang->default_value} {$lang->is_required} {$lang->cmd_search}{$lang->cmd_sort}  
    {$val->idx}
    +
    + + {$val->idx} +
    +
    {$val->eid} {$val->name} {$lang->column_type_list[$val->type]} {$val->default}  YN YNYN @@ -126,9 +158,6 @@
    {$val->desc}
    diff --git a/modules/document/tpl/header.html b/modules/document/tpl/header.html index d8f0029c1..b0fb3f53f 100644 --- a/modules/document/tpl/header.html +++ b/modules/document/tpl/header.html @@ -1,8 +1,10 @@ +

    {$lang->document}

    +
    • {$lang->document_list} diff --git a/modules/document/tpl/js/document_admin.js b/modules/document/tpl/js/document_admin.js index 7fb71fccb..b22aa82bf 100644 --- a/modules/document/tpl/js/document_admin.js +++ b/modules/document/tpl/js/document_admin.js @@ -3,12 +3,10 @@ * @brief 모든 생성된 섬네일 삭제하는 액션 호출 **/ function doDeleteAllThumbnail() { - exec_xml('document','procDocumentAdminDeleteAllThumbnail', [], completeDeleteAllThumbnail); -} - -function completeDeleteAllThumbnail(ret_obj) { - alert(ret_obj['message']); - location.reload(); + Rhymix.ajax('document.procDocumentAdminDeleteAllThumbnail', {}, function(ret_obj) { + alert(ret_obj['message']); + location.reload(); + }); } /* 선택된 글의 삭제 또는 이동 */ @@ -21,35 +19,27 @@ function doManageDocument(type) { /* 선택된 글의 삭제 또는 이동 후 */ function completeManageDocument(ret_obj) { - if(opener) { + if(opener) { opener.window.location.href = opener.window.current_url.setQuery('document_srl', ''); } alert(ret_obj['message']); window.close(); } - +/* 신고 취소 */ function doCancelDeclare() { var document_srl = []; - jQuery('#fo_list input[name=cart]:checked').each(function() { - document_srl[document_srl.length] = jQuery(this).val(); + $('#fo_list input[name=cart]:checked').each(function() { + document_srl.push($(this).val()); }); + if (document_srl.length < 1) { + return; + } - if(document_srl.length<1) return; - - var params = {document_srl : document_srl.join(',')}; - - exec_xml('document','procDocumentAdminCancelDeclare', params, completeCancelDeclare); -} - -function completeCancelDeclare(ret_obj) { - location.reload(); -} - -function insertSelectedModule(id, module_srl, mid, browser_title) { - jQuery('#_'+id).val(browser_title+' ('+mid+')'); - jQuery('#'+id).val(module_srl); - doGetCategoryFromModule(module_srl); + var params = { document_srl : document_srl.join(',') }; + Rhymix.ajax('document.procDocumentAdminCancelDeclare', params, function() { + location.reload(); + }); } function completeInsertExtraVar(ret_obj) { @@ -99,8 +89,9 @@ function moveVar(type, module_srl, var_idx) { module_srl : module_srl, var_idx : var_idx }; - var response_tags = ['error','message']; - exec_xml('document','procDocumentAdminMoveExtraVar', params, function() { location.reload() }); + Rhymix.ajax('document.procDocumentAdminMoveExtraVar', params, function() { + location.reload(); + }); } function completeRestoreTrash(ret_obj) { @@ -112,7 +103,7 @@ function getDocumentList() { var documentListTable = jQuery('#documentListTable'); var cartList = []; documentListTable.find(':checkbox[name=cart]').each(function(){ - if(this.checked) cartList.push(this.value); + if(this.checked) cartList.push(this.value); }); var params = new Array(); @@ -180,12 +171,16 @@ function completeGetModuleList(ret_obj, response_tags) } jQuery(document).ready(function($){ - $('#module_list').bind('change', function(e){ + $('#module_list').on('change', function(e){ makeMidList($('#module_list').val()); }); - $('#mid_list').bind('change', function(e){ + $('#mid_list').on('change', function(e){ doGetCategoryFromModule($('#mid_list').val()); }); + $('.recalculate_category_counts').on('click', function(e) { + exec_json('document.procDocumentAdminRecalculateCategoryCounts', { }); + e.preventDefault(); + }); }); function makeMidList(moduleName) @@ -212,9 +207,8 @@ function doGetCategoryFromModule(module_srl) { } function completeGetCategoryFromModules(ret_obj, response_tags) { - var obj = jQuery('#target_category').get(0); - var length = obj.options.length; - for(var i=0;i 0) category_title = depth_str.substr(0, depth) + ' ' + category_title; - var opt = new Option(category_title, category_srl, false, false); - obj.options[obj.options.length] = opt; - } + obj.append(''); + } } function checkSearch(form) diff --git a/modules/document/tpl/js/document_category.js b/modules/document/tpl/js/document_category.js index 0cdf72dc3..2ecb30fe4 100644 --- a/modules/document/tpl/js/document_category.js +++ b/modules/document/tpl/js/document_category.js @@ -4,14 +4,13 @@ * @brief document 모듈의 category tree javascript **/ -function Tree(url){ - var $ = jQuery; +function Tree(category_module_srl) { - // clear tree; - $('#menu > ul > li > ul').remove(); + // clear tree; + $('#menu > ul > li > ul').remove(); - if($("ul.simpleTree > li > a").size() == 0){ - $('') + if($("ul.simpleTree > li > a").size() == 0){ + $('') .bind('before-open.mw', function(e){ addNode(0,e); }) @@ -19,126 +18,135 @@ function Tree(url){ .xeModalWindow(); } - //ajax get data and transeform ul il - $.get(url,function(data){ - $(data).find("node").each(function(i){ - var text = $(this).attr("text"); - var node_srl = $(this).attr("node_srl"); - var parent_srl = $(this).attr("parent_srl"); - var color = $(this).attr("color"); - var url = $(this).attr("url"); + //ajax get data and transeform ul il + exec_json('document.getDocumentCategoryTree', { module_srl: category_module_srl }, function(data) { + var callback; + callback = function(item) { + var text = item.text; + var node_srl = item.node_srl; + var parent_srl = item.parent_srl; + var color = item.color; + var is_default = item.is_default; + var url = item.url; - // node - var node = ''; - if(color && color !='transparent'){ - node = $('
    • '); - var $span = $('').css('color', color).text(text); - node.append($span); - }else{ - node = $('
    • '); - var $span = $('').text(text); - node.append($span); - } + // node + var node = $('
    • '); + var title = $('').text(text); + if (color && color !='transparent') { + title.css('color', color); + } + if (is_default == 'Y') { + title.css('font-weight', 'bold'); + } + node.append(title); - // button - $('').bind("click",function(e){ - $("#tree_"+node_srl+" > span").click(); + // button + $('').bind("click",function(e){ + $("#tree_"+node_srl+" > span").click(); }) .bind('before-open.mw', function(e){ - addNode(node_srl,e); + addNode(node_srl,e); }) - .appendTo(node) - .xeModalWindow(); - - $('').bind("click",function(e){ - $("#tree_"+node_srl+" > span").click(); - }) - .bind('before-open.mw', function(e){ - modifyNode(node_srl,e); - }) .appendTo(node) .xeModalWindow(); - $('').bind("click",function(e){ - deleteNode(node_srl); - return false; - }).appendTo(node); + $('').bind("click",function(e){ + $("#tree_"+node_srl+" > span").click(); + }) + .bind('before-open.mw', function(e){ + modifyNode(node_srl,e); + }) + .appendTo(node) + .xeModalWindow(); - // insert parent child - if(parent_srl>0){ - if($('#tree_'+parent_srl+'>ul').length==0) $('#tree_'+parent_srl).append($('
        ')); - $('#tree_'+parent_srl+'> ul').append(node); - }else{ - if($('#menu ul.simpleTree > li > ul').length==0) $("
          ").appendTo('#menu ul.simpleTree > li'); - $('#menu ul.simpleTree > li > ul').append(node); - } + $('').bind("click",function(e){ + deleteNode(node_srl); + return false; + }).appendTo(node); - }); + // insert parent child + if(parent_srl>0){ + if($('#tree_'+parent_srl+'>ul').length==0) $('#tree_'+parent_srl).append($('
            ')); + $('#tree_'+parent_srl+'> ul').append(node); + }else{ + if($('#menu ul.simpleTree > li > ul').length==0) $("
              ").appendTo('#menu ul.simpleTree > li'); + $('#menu ul.simpleTree > li > ul').append(node); + } - //button show hide - $("#menu li").each(function(){ - if($(this).parents('ul').size() > max_menu_depth) $("a.add",this).hide(); - if($(">ul",this).size()>0) $(">a.delete",this).hide(); - }); + // look for children + if (item.list) { + item.list.forEach(function(child) { + callback(child); + }); + } + }; + + data.categories.forEach(function(item) { + callback(item); + }); + + //button show hide + $("#menu li").each(function(){ + if($(this).parents('ul').size() > max_menu_depth) $("a.add",this).hide(); + if($(">ul",this).size()>0) $(">a.delete",this).hide(); + }); - // draw tree - simpleTreeCollection = $('.simpleTree').simpleTree({ - autoclose: false, - afterClick:function(node){ - $('#category_info').html(""); - //alert("text-"+jQuery('span:first',node).text()); - }, - afterDblClick:function(node){ - //alert("text-"+jQuery('span:first',node).text()); - }, - afterMove:function(destination, source, pos){ - if(destination.size() == 0){ - Tree(xml_url); - return; - } - var module_srl = $("#fo_category input[name=module_srl]").val(); - var parent_srl = destination.attr('id').replace(/.*_/g,''); - var source_srl = source.attr('id').replace(/.*_/g,''); + // draw tree + simpleTreeCollection = $('.simpleTree').simpleTree({ + autoclose: false, + afterClick:function(node){ + $('#category_info').html(""); + //alert("text-"+jQuery('span:first',node).text()); + }, + afterDblClick:function(node){ + //alert("text-"+jQuery('span:first',node).text()); + }, + afterMove:function(destination, source, pos){ + if(destination.size() == 0){ + Tree(category_module_srl); + return; + } + var module_srl = $("#fo_category input[name=module_srl]").val(); + var parent_srl = destination.attr('id').replace(/.*_/g,''); + var source_srl = source.attr('id').replace(/.*_/g,''); - var target = source.prevAll("li:not([class^=line])"); - var target_srl = 0; - if(target.length >0){ - target_srl = source.prevAll("li:not([class^=line])").get(0).id.replace(/.*_/g,''); - parent_srl = 0; - } + var target = source.prevAll("li:not([class^=line])"); + var target_srl = 0; + if(target.length >0){ + target_srl = source.prevAll("li:not([class^=line])").get(0).id.replace(/.*_/g,''); + parent_srl = 0; + } - $.exec_json("document.procDocumentMoveCategory",{ "module_srl":module_srl,"parent_srl":parent_srl,"target_srl":target_srl,"source_srl":source_srl}, - function(data){ - $('#category_info').html(''); - if(data.error > 0) Tree(xml_url); - }); + $.exec_json("document.procDocumentMoveCategory",{ "module_srl":module_srl,"parent_srl":parent_srl,"target_srl":target_srl,"source_srl":source_srl}, + function(data){ + $('#category_info').html(''); + if(data.error > 0) Tree(category_module_srl); + }); - }, + }, - // i want you !! made by sol - beforeMovedToLine : function(destination, source, pos){ - return ($(destination).parents('ul').size() + jQuery('ul',source).size() <= max_menu_depth); - }, + // i want you !! made by sol + beforeMovedToLine : function(destination, source, pos){ + return ($(destination).parents('ul').size() + jQuery('ul',source).size() <= max_menu_depth); + }, - // i want you !! made by sol - beforeMovedToFolder : function(destination, source, pos){ - return ($(destination).parents('ul').size() + jQuery('ul',source).size() <= max_menu_depth-1); - }, - afterAjax:function() - { - //alert('Loaded'); - }, - animate:true - ,docToFolderConvert:true - }); + // i want you !! made by sol + beforeMovedToFolder : function(destination, source, pos){ + return ($(destination).parents('ul').size() + jQuery('ul',source).size() <= max_menu_depth-1); + }, + afterAjax:function() + { + //alert('Loaded'); + }, + animate:true + ,docToFolderConvert:true + }); + // open all node + nodeToggleAll(); - - // open all node - nodeToggleAll(); - - },"xml"); + }); } function clearValue(){ @@ -147,7 +155,8 @@ function clearValue(){ // clear value $w.find('input[type="text"], textarea').val(''); - $w.find('input[type="checkbox"]').removeAttr('checked'); + $w.find('.x_inline.checked').removeClass('checked'); + $w.find('input[type="checkbox"]').prop('checked', false); $w.find('.lang_code').trigger('reload-multilingual'); $w.find('.color-indicator').trigger('keyup'); $w.find('.rx-spectrum').trigger('keyup'); @@ -191,14 +200,18 @@ function modifyNode(node,e){ $w.find('input[name="parent_srl"]').val(data.category_info.parent_srl); $w.find('input[name="category_title"]').val(data.category_info.title).trigger('reload-multilingual'); - $w.find('input[name="category_color"]').val(data.category_info.color).trigger('keyup'); + $w.find('input[name="category_color"]').val(data.category_info.color).trigger('keyup').spectrum('set', data.category_info.color); $w.find('textarea[name="category_description"]').val(data.category_info.description).trigger('reload-multilingual'); for(var i in data.category_info.group_srls){ var group_srl = data.category_info.group_srls[i]; - $w.find('input[name="group_srls[]"][value="' + group_srl + '"]').attr('checked', 'checked'); + $w.find('input[name="group_srls[]"][value="' + group_srl + '"]').prop('checked', true) + .parent().addClass('checked'); } if(data.category_info.expand == 'Y'){ - $w.find('input[name="expand"]').attr('checked', 'checked'); + $w.find('input[name="expand"]').prop('checked', true).parent().addClass('checked'); + } + if(data.category_info.is_default == 'Y'){ + $w.find('input[name="is_default"]').prop('checked', true).parent().addClass('checked'); } }); @@ -207,41 +220,37 @@ function modifyNode(node,e){ function nodeToggleAll(){ - jQuery("[class*=close]", simpleTreeCollection[0]).each(function(){ - simpleTreeCollection[0].nodeToggle(this); - }); + jQuery("[class*=close]", simpleTreeCollection[0]).each(function(){ + simpleTreeCollection[0].nodeToggle(this); + }); } function deleteNode(node){ - if(confirm(lang_confirm_delete)){ - jQuery('#category_info').html(""); - var params ={ - "category_srl":node - ,"parent_srl":0 - ,"module_srl":jQuery("#fo_category [name=module_srl]").val() - }; + if(confirm(lang_confirm_delete)){ + jQuery('#category_info').html(""); + var params ={ + "category_srl":node + ,"parent_srl":0 + ,"module_srl":jQuery("#fo_category [name=module_srl]").val() + }; - jQuery.exec_json('document.procDocumentDeleteCategory', params, function(data){ - if(data.error==0) Tree(xml_url); - }); - } + exec_json('document.procDocumentDeleteCategory', params, function(data){ + if(data.error==0) Tree(category_module_srl); + }); + } } /* 카테고리 아이템 입력후 */ function completeInsertCategory(ret_obj) { - jQuery('#category_info').html(""); - Tree(xml_url); + jQuery('#category_info').html(""); + Tree(category_module_srl); } function hideCategoryInfo() { - jQuery('#category_info').html(""); + jQuery('#category_info').html(""); } /* 카테고리 목록 갱신 */ function doReloadTreeCategory(module_srl) { - var params = {'module_srl':module_srl}; - - // 서버에 요청하여 해당 노드의 정보를 수정할 수 있도록 한다. - var response_tags = new Array('error','message', 'xml_file'); - exec_xml('document', 'procDocumentMakeXmlFile', params, completeInsertCategory, response_tags, params); + exec_json('document.procDocumentMakeXmlFile', { module_srl: module_srl }, completeInsertCategory); } diff --git a/modules/document/tpl/js/document_extra_keys.js b/modules/document/tpl/js/document_extra_keys.js new file mode 100644 index 000000000..f4404f0a2 --- /dev/null +++ b/modules/document/tpl/js/document_extra_keys.js @@ -0,0 +1,52 @@ +(function($) { + $(function() { + + // Reorder extra keys + $('table.extra_keys.sortable').on('after-drag.st', function(e) { + const $table = $(this); + let order = []; + let i = 1; + $table.find('tbody > tr').each(function() { + order.push({ + eid: $(this).data('eid'), + old_idx: parseInt($(this).data('idx'), 10), + new_idx: i++ + }); + }); + Rhymix.ajax('document.procDocumentAdminReorderExtraVars', { + module_srl: $(this).data('moduleSrl'), + order: order + }, function() { + let i = 1; + $table.find('.var_idx').each(function() { + $(this).text(i); + i++; + }); + }); + }); + + // Show or hide fields depending on the type of variable + $('select#type').on('change', function() { + const selected_type = $(this).val(); + $(this).parents('form').find('.x_control-group').each(function() { + const visible_types = $(this).data('visibleTypes'); + if (visible_types) { + if (visible_types.split(',').indexOf(selected_type) >= 0) { + $(this).show(); + } else { + $(this).hide(); + } + } + const invisible_types = $(this).data('invisibleTypes'); + if (invisible_types) { + if (invisible_types.split(',').indexOf(selected_type) >= 0) { + $(this).hide(); + } else { + $(this).show(); + } + } + }); + }).triggerHandler('change'); + + }); +})(jQuery); diff --git a/modules/document/tpl/saved_list_popup.html b/modules/document/tpl/saved_list_popup.html index 38823f693..59cbe43a5 100644 --- a/modules/document/tpl/saved_list_popup.html +++ b/modules/document/tpl/saved_list_popup.html @@ -1,6 +1,8 @@ {@Context::addMetaTag('viewport', 'width=device-width', FALSE);} +

              {$lang->cmd_view_saved_document}

              + ×
              @@ -10,17 +12,20 @@ {$lang->date} {$lang->title} - {$lang->cmd_select} + {$lang->cmd_select} + {$lang->cmd_delete} {$val->getRegdate("Y-m-d H:i:s")} - {$val->getTitle()} - + + {$val->getTitle()} + - {$lang->cmd_select} + {$lang->cmd_select} + {$lang->cmd_delete} @@ -38,3 +43,30 @@ {$lang->last_page} ›
              + + + diff --git a/modules/editor/components/emoticon/emoticon.class.php b/modules/editor/components/emoticon/emoticon.class.php index f94d06049..11527d4c0 100644 --- a/modules/editor/components/emoticon/emoticon.class.php +++ b/modules/editor/components/emoticon/emoticon.class.php @@ -19,7 +19,7 @@ class emoticon extends EditorHandler { $this->editor_sequence = $editor_sequence; $this->component_path = $component_path; - $this->emoticon_path = sprintf('%s%s/images',preg_replace('/^\.\//i','',$this->component_path),'tpl','images'); + $this->emoticon_path = $this->component_path . 'tpl/images'; } /** @@ -28,39 +28,47 @@ class emoticon extends EditorHandler function getEmoticonList() { $emoticon = Context::get('emoticon'); - if(!$emoticon || !preg_match("/^([a-z0-9\_]+)$/i",$emoticon)) return new BaseObject(-1,'msg_invalid_request'); - - $list = $this->getEmoticons($emoticon); - - $this->add('emoticons', $list); + if(!$emoticon || !preg_match('/^[a-z0-9\_]+$/i', $emoticon)) + { + return new BaseObject(-1, 'msg_invalid_request'); + } + + $this->add('emoticons', $this->getEmoticons($emoticon)); } /** * @brief Likely to be recursively emoticons will search all the files to a subdirectory. 8000 gaekkajineun ran tests whether the stack and raise beef pro-overs and Unsure. (06/09/2007, Benny) */ - function getEmoticons($path) + function getEmoticons($emoticon) { - $emoticon_path = sprintf("%s/%s", $this->emoticon_path, $path); - $output = array(); - - $oDir = dir($emoticon_path); - while($file = $oDir->read()) + $emoticon_path = sprintf('%s/%s', $this->emoticon_path, $emoticon); + $emoticon_url = RX_BASEURL . preg_replace('@^\./@', '', $emoticon_path) . '/'; + if(!$emoticon_files = Rhymix\Framework\Storage::readDirectory($emoticon_path)) { - if(substr($file,0,1)=='.') continue; - if(preg_match('/\.(jpg|jpeg|gif|png)$/i',$file)) { - $svg = null; - $filename = sprintf("%s/%s", $path, str_replace($this->emoticon_path,'',$file)); - list($width, $height, $type, $attr) = getimagesize($emoticon_path . '/'. $file); - - if(file_exists (($emoticon_path . '/svg/'. substr($file, 0, -4) . '.svg'))) { - $svg = sprintf("%s/svg/%s", $path, str_replace($this->emoticon_path,'',substr($file, 0, -4) . '.svg')); - } - - $output[] = array('filename' => $filename, 'width' => $width, 'height' => $height, 'svg' => $svg, 'alt' => substr($file, 0, -4)); - } + return array(); } - $oDir->close(); - if(count($output)) asort($output); + + $output = array(); + foreach($emoticon_files as $file) + { + if(!preg_match('/\.(jpg|jpeg|gif|png)$/i', $file)) + { + continue; + } + + $file_name = basename($file); + list($file_width, $file_height) = getimagesize($file); + $svg_name = preg_replace('@\.[^.]+$@', '', $file_name) . '.svg'; + + $output[] = array( + 'url' => $emoticon_url . $file_name, + 'width' => $file_width, + 'height' => $file_height, + 'svg' => file_exists(sprintf('%s/svg/%s', $emoticon_path, $svg_name)) ? ($emoticon_url . 'svg/' . $svg_name) : '', + 'alt' => $file_name, + ); + } + asort($output); return $output; } @@ -91,7 +99,7 @@ class emoticon extends EditorHandler $tpl_path = $this->component_path.'tpl'; $tpl_file = 'popup.html'; - $oTemplate = &TemplateHandler::getInstance(); + $oTemplate = TemplateHandler::getInstance(); return $oTemplate->compile($tpl_path, $tpl_file); } diff --git a/modules/editor/components/emoticon/tpl/popup.html b/modules/editor/components/emoticon/tpl/popup.html index 6a6926f7d..48f4f0135 100644 --- a/modules/editor/components/emoticon/tpl/popup.html +++ b/modules/editor/components/emoticon/tpl/popup.html @@ -1,9 +1,17 @@ + - -{@Context::addMetaTag('viewport', 'width=device-width', FALSE);} + +{@ Context::addMetaTag('viewport', 'width=device-width', FALSE);} + + +

              {$component_info->title}

              + ×
              +
                diff --git a/modules/editor/components/emoticon/tpl/popup.js b/modules/editor/components/emoticon/tpl/popup.js index 1acbb35bc..3aadcdfb7 100644 --- a/modules/editor/components/emoticon/tpl/popup.js +++ b/modules/editor/components/emoticon/tpl/popup.js @@ -23,17 +23,17 @@ function completeGetEmoticons(ret_obj) { .width( parseInt(emoticons[i].width, 10)) .height(parseInt(emoticons[i].height, 10)) .attr({ - 'src': './modules/editor/components/emoticon/tpl/images/'+emoticons[i].filename, - 'data-src': './modules/editor/components/emoticon/tpl/images/'+emoticons[i].filename, + 'src': emoticons[i].url, + 'data-src': emoticons[i].url, 'alt': emoticons[i].alt }); if( emoticons[i].svg ) { $img.attr({ - 'data-svg': './modules/editor/components/emoticon/tpl/images/'+emoticons[i].svg + 'data-svg': emoticons[i].svg }); if( typeof SVGRect !== "undefined" ) { $img.attr({ - 'src': './modules/editor/components/emoticon/tpl/images/'+emoticons[i].svg + 'src': emoticons[i].svg }); } } @@ -67,7 +67,8 @@ function insertEmoticon(obj) { win.editorRelKeys[win.editorPrevSrl].pasteHTML(html); if (is_popup) window.focus(); - + + rhymix_alert(lang_success_added); return false; } diff --git a/modules/editor/components/emoticon/tpl/popup.less b/modules/editor/components/emoticon/tpl/popup.less deleted file mode 100644 index 0c5fbd8e8..000000000 --- a/modules/editor/components/emoticon/tpl/popup.less +++ /dev/null @@ -1,61 +0,0 @@ -@charset "UTF-8"; -#emoticons{ - padding:0 10px 20px 10px; - input.emoticon{ - display: inline-block; - margin:5px; - cursor:pointer; - } -} - - -div.rx_tab { - width: 100%; - white-space: nowrap; - border-bottom: 1px solid #e0e0e0; - box-sizing: border-box; - ul.rx_tab { - display: block; - list-style: outside none none; - margin: 0; - text-decoration: none; - - &>li { - display: inline-block; - height: 40px; - line-height: 18px; - position: relative; - - a { - display: inline-block; - color: #000; - font-weight: 400; - letter-spacing: -1px; - line-height: 40px; - text-decoration: none; - - span { - border-left: 1px solid #e0e0e0; - padding: 0 15px; - } - } - - a:hover, - a:focus, - &.rx_active a - { - color: #2196f3; - } - - &:first-child a span { - border-left: 0 none; - } - } - } -} - - -/* for mobile view */ -div.xe_mobile { - display:none!important; -} \ No newline at end of file diff --git a/modules/editor/components/emoticon/tpl/popup.scss b/modules/editor/components/emoticon/tpl/popup.scss new file mode 100644 index 000000000..311aa3f5c --- /dev/null +++ b/modules/editor/components/emoticon/tpl/popup.scss @@ -0,0 +1,59 @@ +#emoticons { + padding: 0 10px 20px 10px; + input.emoticon { + display: inline-block; + margin: 5px; + cursor: pointer; + } +} + +div.rx_tab { + width: 100%; + white-space: nowrap; + border-bottom: 1px solid #e0e0e0; + box-sizing: border-box; + ul.rx_tab { + display: block; + list-style: outside none none; + margin: 0; + text-decoration: none; + + & > li { + display: inline-block; + height: 40px; + line-height: 18px; + position: relative; + + a { + display: inline-block; + color: #000; + font-weight: 400; + letter-spacing: -1px; + line-height: 40px; + text-decoration: none; + + span { + border-left: 1px solid #e0e0e0; + padding: 0 15px; + } + } + + a:hover, + a:focus, + &.rx_active a + { + color: #2196f3; + } + + &:first-child a span { + border-left: 0 none; + } + } + } +} + + +/* for mobile view */ +div.xe_mobile { + display:none!important; +} diff --git a/modules/editor/components/image_gallery/image_gallery.class.php b/modules/editor/components/image_gallery/image_gallery.class.php index 52fdb2ed4..8c7536f14 100644 --- a/modules/editor/components/image_gallery/image_gallery.class.php +++ b/modules/editor/components/image_gallery/image_gallery.class.php @@ -31,7 +31,7 @@ class image_gallery extends EditorHandler Context::set("tpl_path", $tpl_path); - $oTemplate = &TemplateHandler::getInstance(); + $oTemplate = TemplateHandler::getInstance(); return $oTemplate->compile($tpl_path, $tpl_file); } @@ -86,7 +86,7 @@ class image_gallery extends EditorHandler if($gallery_info->gallery_style == "list") $tpl_file = 'list_gallery.html'; else $tpl_file = 'slide_gallery.html'; - $oTemplate = &TemplateHandler::getInstance(); + $oTemplate = TemplateHandler::getInstance(); return $oTemplate->compile($tpl_path, $tpl_file); } } diff --git a/modules/editor/components/image_gallery/tpl/popup.html b/modules/editor/components/image_gallery/tpl/popup.html index 68fd4126d..6e806dc15 100644 --- a/modules/editor/components/image_gallery/tpl/popup.html +++ b/modules/editor/components/image_gallery/tpl/popup.html @@ -4,6 +4,7 @@ {@Context::addMetaTag('viewport', 'width=device-width', FALSE);}

                {$component_info->title}

                + ×
                diff --git a/modules/editor/components/image_link/image_link.class.php b/modules/editor/components/image_link/image_link.class.php index bdd56c935..1a9d099fa 100644 --- a/modules/editor/components/image_link/image_link.class.php +++ b/modules/editor/components/image_link/image_link.class.php @@ -31,7 +31,7 @@ class image_link extends EditorHandler Context::set("tpl_path", $tpl_path); - $oTemplate = &TemplateHandler::getInstance(); + $oTemplate = TemplateHandler::getInstance(); return $oTemplate->compile($tpl_path, $tpl_file); } @@ -43,17 +43,17 @@ class image_link extends EditorHandler */ function transHTML($xml_obj) { - $src = $xml_obj->attrs->src; - $width = $xml_obj->attrs->width; - $height = $xml_obj->attrs->height; - $align = $xml_obj->attrs->align; - $alt = $xml_obj->attrs->alt; - $title = $xml_obj->attrs->title; - $border = (int)$xml_obj->attrs->border; - $link_url = $xml_obj->attrs->link_url; - $open_window = $xml_obj->attrs->open_window; - $style = $xml_obj->attrs->style; - $margin = (int)$xml_obj->attrs->margin; + $src = $xml_obj->attrs->src ?? null; + $width = $xml_obj->attrs->width ?? null; + $height = $xml_obj->attrs->height ?? null; + $align = $xml_obj->attrs->align ?? null; + $alt = $xml_obj->attrs->alt ?? null; + $title = $xml_obj->attrs->title ?? null; + $border = intval($xml_obj->attrs->border ?? 0); + $link_url = $xml_obj->attrs->link_url ?? null; + $open_window = $xml_obj->attrs->open_window ?? null; + $style = $xml_obj->attrs->style ?? null; + $margin = intval($xml_obj->attrs->margin ?? 0); $src = str_replace(array('&','"'), array('&','&qout;'), $src); $src = str_replace('&amp;', '&', $src); diff --git a/modules/editor/components/image_link/tpl/popup.html b/modules/editor/components/image_link/tpl/popup.html index d11ed24ef..aa95bfbd7 100644 --- a/modules/editor/components/image_link/tpl/popup.html +++ b/modules/editor/components/image_link/tpl/popup.html @@ -4,6 +4,7 @@ {@Context::addMetaTag('viewport', 'width=device-width', FALSE);}

                {$component_info->title}

                + ×
                diff --git a/modules/editor/components/image_link/tpl/popup.js b/modules/editor/components/image_link/tpl/popup.js index 58b13b6cc..41959c8ba 100644 --- a/modules/editor/components/image_link/tpl/popup.js +++ b/modules/editor/components/image_link/tpl/popup.js @@ -86,8 +86,8 @@ else if($form.find('#align_middle').attr('checked') == 'checked') align = 'middle'; else if($form.find('#align_right').attr('checked') == 'checked') align = 'right'; - var width = $form.find('#width').val(); - var height = $form.find('#height').val(); + var width = parseInt($form.find('#width').val(), 10); + var height = parseInt($form.find('#height').val(), 10); if(!url) { window.close(); @@ -101,11 +101,11 @@ img_attrs.src = url; if(alt) img_attrs.alt = alt; - if(width) { + if(width > 0) { img_attrs.width = width; img_style.width = width; } - if(height) { + if(height > 0) { img_attrs.height = height; img_style.height = height; } diff --git a/modules/editor/components/poll_maker/poll_maker.class.php b/modules/editor/components/poll_maker/poll_maker.class.php index f86f60cc4..c3de0ca6d 100644 --- a/modules/editor/components/poll_maker/poll_maker.class.php +++ b/modules/editor/components/poll_maker/poll_maker.class.php @@ -27,13 +27,13 @@ class poll_maker extends EditorHandler { // Wanted Skins survey $oModuleModel = getModel('module'); - $skin_list = $oModuleModel->getSkins(_XE_PATH_ . 'widgets/pollWidget/'); + $skin_list = $oModuleModel->getSkins(RX_BASEDIR . 'widgets/pollWidget/'); Context::set('skin_list', $skin_list); // Pre-compiled source code to compile template return to $tpl_path = $this->component_path.'tpl'; $tpl_file = 'popup.html'; - $oTemplate = &TemplateHandler::getInstance(); + $oTemplate = TemplateHandler::getInstance(); return $oTemplate->compile($tpl_path, $tpl_file); } @@ -58,7 +58,7 @@ class poll_maker extends EditorHandler $args->style = sprintf('width:%dpx', $width); // Set a path of the template skin (values of skin, colorset settings) - $tpl_path = sprintf('%sskins/%s', _XE_PATH_ . 'widgets/pollWidget/', $args->skin); + $tpl_path = sprintf('%sskins/%s', RX_BASEDIR . 'widgets/pollWidget/', $args->skin); $tpl_file = 'pollview'; // Get the information related to the survey @@ -66,12 +66,12 @@ class poll_maker extends EditorHandler $poll_data = $oPollModel->_getPollinfo($args->poll_srl); Context::set('poll_data', $poll_data); - Context::set('colorset', $args->colorset); + Context::set('colorset', $args->colorset ?? null); Context::set('poll_srl', $args->poll_srl); Context::set('style', $args->style); // Compile a template - $oTemplate = &TemplateHandler::getInstance(); + $oTemplate = TemplateHandler::getInstance(); return $oTemplate->compile($tpl_path, $tpl_file); } } diff --git a/modules/editor/components/poll_maker/tpl/popup.html b/modules/editor/components/poll_maker/tpl/popup.html index 1778b0eba..eecdefea0 100644 --- a/modules/editor/components/poll_maker/tpl/popup.html +++ b/modules/editor/components/poll_maker/tpl/popup.html @@ -11,6 +11,7 @@

                {$component_info->title}

                + ×
                @@ -89,12 +90,12 @@
                {$lang->poll_title}
                - +
                {$lang->poll_item} 1
                - +
                {$lang->poll_item} 2
                diff --git a/modules/editor/conf/info.xml b/modules/editor/conf/info.xml index 3814d3207..e4465b747 100644 --- a/modules/editor/conf/info.xml +++ b/modules/editor/conf/info.xml @@ -1,14 +1,14 @@ - WYSIWYG Editor - 위지윅 에디터 - WYSIWYG Editor - Editor WYSIWYG + Editor + 에디터 + Editor + Editor 网页编辑器 - ウィジウィグエディター - WYSIWYG-редактор + エディター + редактор 網頁編輯器 - Editor WYSIWYG + Editor Module hiển thị WYSIWYG Editor để quản lý những kiểu viết bài. 위지윅 에디터를 출력하거나 에디터 컴포넌트들을 관리/중계하는 합니다. Module for displaying WYSIWYG editor and managing/relaying editor components. @@ -18,8 +18,8 @@ Модуль для отображения WYSIWYG-редактора и управления/смены записей редактора. 顯示網頁編輯器或管理/傳遞編輯器組件的模組。 WYSIWYG editörünü görüntüleme ve editör bileşenlerini düzenleme/aktarma için kullanılan modüldür. - 1.7 - 2013-11-27 + RX_VERSION + RX_CORE utility diff --git a/modules/editor/conf/module.xml b/modules/editor/conf/module.xml index 2e6f733a0..aea50d220 100644 --- a/modules/editor/conf/module.xml +++ b/modules/editor/conf/module.xml @@ -5,36 +5,40 @@ - - + - - + + - + + + + + + + + - WYSIWYG Editor - 에디터 - WYSIWYG Editor - ウィジウィグエディター - WYSIWYG Editor - WYSIWYG Editor - WYSIWYG Editor - WYSIWYG Editor - WYSIWYG Editor - WYSIWYG Editor - WYSIWYG Editor + Editor + 에디터 + Editor + Editor + 网页编辑器 + エディター + редактор + 網頁編輯器 + Editor diff --git a/modules/editor/editor.admin.controller.php b/modules/editor/editor.admin.controller.php index 674420585..63a8c9054 100644 --- a/modules/editor/editor.admin.controller.php +++ b/modules/editor/editor.admin.controller.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * @brief editor of the module admin controller class */ -class editorAdminController extends editor +class EditorAdminController extends Editor { /** * @brief Initialization @@ -44,6 +44,10 @@ class editorAdminController extends editor $output = $this->editorCheckUse($componentList,$site_module_info->site_srl); if(!$output->toBool()) return new BaseObject(); + $config = ModuleModel::getModuleConfig('editor') ?: new stdClass; + $config->timestamp = time(); + ModuleController::getInstance()->insertModuleConfig('editor', $config); + $oEditorController = getController('editor'); $oEditorController->removeCache($site_module_info->site_srl); $this->setRedirectUrl(Context::get('error_return_url')); @@ -144,27 +148,29 @@ class editorAdminController extends editor { $oModuleController = getController('module'); $configVars = Context::getRequestVars(); - + $config = new stdClass; $config->editor_skin = $configVars->editor_skin; + $config->editor_colorset = $configVars->editor_colorset; $config->editor_height = $configVars->editor_height; - $config->mobile_editor_height = $configVars->mobile_editor_height; $config->editor_toolbar = $configVars->editor_toolbar; $config->editor_toolbar_hide = $configVars->editor_toolbar_hide === 'Y' ? 'Y' : 'N'; + $config->mobile_editor_skin = $configVars->mobile_editor_skin; + $config->mobile_editor_colorset = $configVars->mobile_editor_colorset; + $config->mobile_editor_height = $configVars->mobile_editor_height; $config->mobile_editor_toolbar = $configVars->mobile_editor_toolbar; $config->mobile_editor_toolbar_hide = $configVars->mobile_editor_toolbar_hide === 'Y' ? 'Y' : 'N'; $config->comment_editor_skin = $configVars->comment_editor_skin; + $config->comment_editor_colorset = $configVars->comment_editor_colorset; $config->comment_editor_height = $configVars->comment_editor_height; - $config->mobile_comment_editor_height = $configVars->mobile_comment_editor_height; $config->comment_editor_toolbar = $configVars->comment_editor_toolbar; $config->comment_editor_toolbar_hide = $configVars->comment_editor_toolbar_hide === 'Y' ? 'Y' : 'N'; + $config->mobile_comment_editor_skin = $configVars->mobile_comment_editor_skin; + $config->mobile_comment_editor_colorset = $configVars->mobile_comment_editor_colorset; + $config->mobile_comment_editor_height = $configVars->mobile_comment_editor_height; $config->mobile_comment_editor_toolbar = $configVars->mobile_comment_editor_toolbar; $config->mobile_comment_editor_toolbar_hide = $configVars->mobile_comment_editor_toolbar_hide === 'Y' ? 'Y' : 'N'; - $config->content_style = $configVars->content_style; - $config->comment_content_style = $configVars->comment_content_style; - $config->sel_editor_colorset = $configVars->sel_editor_colorset; - $config->sel_comment_editor_colorset = $configVars->sel_comment_editor_colorset; - + if ($configVars->font_defined === 'Y') { $config->font_defined = 'Y'; @@ -175,7 +181,7 @@ class editorAdminController extends editor $config->font_defined = $configVars->font_defined = 'N'; $config->content_font = $configVars->content_font; } - + if ($configVars->additional_css) { $additional_css = array_map('trim', explode("\n", $configVars->additional_css)); @@ -186,7 +192,7 @@ class editorAdminController extends editor { $config->additional_css = array(); } - + if ($configVars->additional_mobile_css) { $additional_mobile_css = array_map('trim', explode("\n", $configVars->additional_mobile_css)); @@ -197,7 +203,7 @@ class editorAdminController extends editor { $config->additional_mobile_css = array(); } - + if ($configVars->additional_plugins) { $additional_plugins = array_map('trim', explode(',', $configVars->additional_plugins)); @@ -208,7 +214,7 @@ class editorAdminController extends editor { $config->additional_plugins = array(); } - + if ($configVars->remove_plugins) { $remove_plugins = array_map('trim', explode(',', $configVars->remove_plugins)); @@ -219,7 +225,7 @@ class editorAdminController extends editor { $config->remove_plugins = array(); } - + $config->content_font_size = trim($configVars->content_font_size); $config->content_font_size = ctype_digit($config->content_font_size) ? ($config->content_font_size . 'px') : $config->content_font_size; $config->content_line_height = trim($configVars->content_line_height); @@ -229,9 +235,15 @@ class editorAdminController extends editor $config->content_word_break = $configVars->content_word_break; $config->content_word_break = in_array($config->content_word_break, array('normal', 'keep-all', 'break-all', 'none')) ? $config->content_word_break : 'normal'; $config->enable_autosave = $configVars->enable_autosave ?: 'Y'; + $config->auto_dark_mode = $configVars->auto_dark_mode ?: 'Y'; $config->allow_html = $configVars->allow_html ?: 'Y'; - $config->autoinsert_image = $configVars->autoinsert_image; - $config->autoinsert_image = in_array($config->autoinsert_image, array('paragraph', 'inline', 'none')) ? $config->autoinsert_image : 'paragraph'; + $config->autoinsert_types = array(); + foreach ($configVars->autoinsert_types as $type) + { + $config->autoinsert_types[$type] = true; + } + $config->autoinsert_position = in_array($configVars->autoinsert_position, array('paragraph', 'inline')) ? $configVars->autoinsert_position : 'paragraph'; + $config->timestamp = time(); $oModuleController->insertModuleConfig('editor', $config); $this->setRedirectUrl(Context::get('error_return_url')); diff --git a/modules/editor/editor.admin.view.php b/modules/editor/editor.admin.view.php index a2aadde52..7224658d4 100644 --- a/modules/editor/editor.admin.view.php +++ b/modules/editor/editor.admin.view.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * @brief editor admin view of the module class */ -class editorAdminView extends editor +class EditorAdminView extends Editor { /** * @brief Initialization @@ -20,69 +20,70 @@ class editorAdminView extends editor */ function dispEditorAdminIndex() { - $component_count = 0; - $site_module_info = Context::get('site_module_info'); - $site_srl = (int)$site_module_info->site_srl; - - // Get a type of component + // Get module config $oEditorModel = getModel('editor'); - $oModuleModel = getModel('module'); - $editor_config = $oModuleModel->getModuleConfig('editor'); - - if(!$editor_config) + $editor_config = ModuleModel::getModuleConfig('editor'); + if (!is_object($editor_config)) { $editor_config = new stdClass(); } // Use default config for missing values. - foreach ($this->default_editor_config as $key => $val) + foreach (self::$default_editor_config as $key => $val) { - if (!$editor_config->$key) + if (!isset($editor_config->$key)) { $editor_config->$key = $val; } } - $component_list = $oEditorModel->getComponentList(false, $site_srl, true); - $editor_skin_list = FileHandler::readDir(_XE_PATH_.'modules/editor/skins'); - $editor_skin_list = array_filter($editor_skin_list, function($name) { return !starts_with('xpresseditor', $name) && !starts_with('dreditor', $name); }); - - $skin_info = $oModuleModel->loadSkinInfo($this->module_path,$editor_config->editor_skin); - $comment_skin_info = $oModuleModel->loadSkinInfo($this->module_path,$editor_config->comment_editor_skin); - - $contents = FileHandler::readDir(_XE_PATH_.'modules/editor/styles'); - $content_style_list = array(); - for($i=0,$c=count($contents);$i<$c;$i++) + // Get skin info + $editor_skin_list = array(); + $skin_dir_list = FileHandler::readDir($this->module_path . 'skins'); + foreach ($skin_dir_list as $skin) { - $style = $contents[$i]; - $info = $oModuleModel->loadSkinInfo($this->module_path,$style,'styles'); - $content_style_list[$style] = new stdClass(); - $content_style_list[$style]->title = $info->title; + if (starts_with('xpresseditor', $skin) || starts_with('dreditor', $skin)) + { + continue; + } + + $skin_info = ModuleModel::loadSkinInfo($this->module_path, $skin); + foreach ($skin_info->colorset ?: [] as $colorset) + { + unset($colorset->screenshot); + } + $editor_skin_list[$skin] = $skin_info; } - // Get install info, update info, count + // Get editor component info $oAutoinstallModel = getModel('autoinstall'); - foreach($component_list as $component_name => $xml_info) + $component_list = $oEditorModel->getComponentList(false, 0, true); + $component_count = countobj($component_list); + $targetpackages = array(); + foreach ($component_list as $xml_info) { - $component_count++; $xml_info->path = './modules/editor/components/'.$xml_info->component_name; $xml_info->delete_url = $oAutoinstallModel->getRemoveUrlByPath($xml_info->path); $xml_info->package_srl = $oAutoinstallModel->getPackageSrlByPath($xml_info->path); - if($xml_info->package_srl) $targetpackages[$xml_info->package_srl] = 0; + if ($xml_info->package_srl) + { + $targetpackages[$xml_info->package_srl] = 0; + } } - - if(is_array($targetpackages)) $packages = $oAutoinstallModel->getInstalledPackages(array_keys($targetpackages)); - - foreach($component_list as $component_name => $xml_info) + if (count($targetpackages)) { - if($packages[$xml_info->package_srl]) $xml_info->need_update = $packages[$xml_info->package_srl]->need_update; + $packages = $oAutoinstallModel->getInstalledPackages(array_keys($targetpackages)); } - + foreach ($component_list as $xml_info) + { + if ($packages[$xml_info->package_srl]) + { + $xml_info->need_update = $packages[$xml_info->package_srl]->need_update; + } + } + Context::set('editor_config', $editor_config); Context::set('editor_skin_list', $editor_skin_list); - Context::set('editor_colorset_list', $skin_info->colorset); - Context::set('comment_editor_colorset_list', $comment_skin_info->colorset); - Context::set('content_style_list', $content_style_list); Context::set('component_list', $component_list); Context::set('component_count', $component_count); @@ -98,48 +99,50 @@ class editorAdminView extends editor */ function dispEditorAdminSetupComponent() { - $site_module_info = Context::get('site_module_info'); - $site_srl = (int)$site_module_info->site_srl; - - $component_name = Context::get('component_name'); // Get information of the editor component $oEditorModel = getModel('editor'); - $component = $oEditorModel->getComponent($component_name,$site_srl); - + $component_name = Context::get('component_name'); + $component = $oEditorModel->getComponent($component_name); if(!$component->component_name) { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - Context::set('component', $component); - // Get a group list to set a group - $oMemberModel = getModel('member'); - $group_list = $oMemberModel->getGroups($site_srl); - Context::set('group_list', $group_list); - // Get a mid list - $oModuleModel = getModel('module'); + // Get a group list to set a group + $group_list = MemberModel::getGroups(0); + foreach ($group_list ?: [] as $group) + { + $group->title = Context::replaceUserLang($group->title, true); + } + Context::set('group_list', $group_list); + + // Get a mid list $args =new stdClass(); - $args->site_srl = $site_srl; + $args->site_srl = 0; $columnList = array('module_srl', 'mid', 'module_category_srl', 'browser_title'); - $mid_list = $oModuleModel->getMidList($args, $columnList); + $mid_list = ModuleModel::getMidList($args, $columnList); + // Combination of module_category and module if(!$args->site_srl) { // Get a list of module category - $module_categories = $oModuleModel->getModuleCategories(); + $module_categories = ModuleModel::getModuleCategories(); if(!is_array($mid_list)) $mid_list = array($mid_list); foreach($mid_list as $module_srl => $module) { - if($module) $module_categories[$module->module_category_srl]->list[$module_srl] = $module; + if($module && isset($module_categories[$module->module_category_srl])) + { + $module_categories[$module->module_category_srl]->list[$module_srl] = $module; + } } } else { + $module_categories = array(new stdClass); $module_categories[0]->list = $mid_list; } - Context::set('mid_list',$module_categories); //Security diff --git a/modules/editor/editor.api.php b/modules/editor/editor.api.php index ffa9356aa..b254f2778 100644 --- a/modules/editor/editor.api.php +++ b/modules/editor/editor.api.php @@ -3,9 +3,9 @@ /** * @class editorAPI * @author NAVER (developers@xpressengine.com) - * @brief + * @brief */ -class editorAPI extends editor +class EditorAPI extends Editor { function dispEditorSkinColorset(&$oModule) { diff --git a/modules/editor/editor.class.php b/modules/editor/editor.class.php index 6bd28688f..ccb82d349 100644 --- a/modules/editor/editor.class.php +++ b/modules/editor/editor.class.php @@ -3,86 +3,77 @@ /** * @class editor * @author NAVER (developers@xpressengine.com) - * @brief high class of the editor odule + * @brief high class of the editor module */ -class editor extends ModuleObject +class Editor extends ModuleObject { /** * @brief Default font config */ - public $default_font_config = array( + public static $default_font_config = array( 'default_font_family' => 'inherit', 'default_font_size' => '13px', 'default_line_height' => '160%', 'default_paragraph_spacing' => '0', 'default_word_break' => 'normal', ); - + /** * @brief Default editor config */ - public $default_editor_config = array( + public static $default_editor_config = array( 'editor_skin' => 'ckeditor', + 'editor_colorset' => 'moono-lisa', 'editor_height' => 300, 'editor_toolbar' => 'default', 'editor_toolbar_hide' => 'N', + 'mobile_editor_skin' => 'simpleeditor', + 'mobile_editor_colorset' => 'light', 'mobile_editor_height' => 200, 'mobile_editor_toolbar' => 'simple', 'mobile_editor_toolbar_hide' => 'Y', - 'sel_editor_colorset' => 'moono-lisa', - 'content_style' => 'ckeditor_light', 'comment_editor_skin' => 'ckeditor', + 'comment_editor_colorset' => 'moono-lisa', 'comment_editor_height' => 100, 'comment_editor_toolbar' => 'simple', 'comment_editor_toolbar_hide' => 'N', + 'mobile_comment_editor_skin' => 'simpleeditor', + 'mobile_comment_editor_colorset' => 'light', 'mobile_comment_editor_height' => 100, 'mobile_comment_editor_toolbar' => 'simple', 'mobile_comment_editor_toolbar_hide' => 'Y', - 'sel_comment_editor_colorset' => 'moono-lisa', - 'comment_content_style' => 'ckeditor_light', 'content_font' => '', 'content_font_size' => '13px', 'content_line_height' => '160%', 'content_paragraph_spacing' => '0px', 'content_word_break' => 'normal', 'enable_autosave' => 'Y', + 'auto_dark_mode' => 'Y', 'allow_html' => 'Y', 'editor_focus' => 'N', - 'autoinsert_image' => 'paragraph', + 'autoinsert_types' => array('image' => true, 'audio' => true, 'video' => true), + 'autoinsert_position' => 'paragraph', 'additional_css' => array(), 'additional_mobile_css' => array(), 'additional_plugins' => array(), - 'remove_plugins' => array(), + 'remove_plugins' => array('liststyle', 'tabletools', 'tableselection', 'contextmenu', 'exportpdf'), + 'timestamp' => 0, ); - + /** * @brief Implement if additional tasks are necessary when installing */ function moduleInstall() { - // Register action forward (to use in administrator mode) - $oModuleController = getController('module'); // Add the default editor component $oEditorController = getAdminController('editor'); - $oEditorController->insertComponent('colorpicker_text',true); - $oEditorController->insertComponent('colorpicker_bg',true); - $oEditorController->insertComponent('emoticon',true); - $oEditorController->insertComponent('url_link',true); - $oEditorController->insertComponent('image_link',true); - $oEditorController->insertComponent('multimedia_link',true); - $oEditorController->insertComponent('quotation',true); - $oEditorController->insertComponent('table_maker',true); - $oEditorController->insertComponent('poll_maker',true); - $oEditorController->insertComponent('image_gallery',true); + $oEditorController->insertComponent('emoticon', false); + $oEditorController->insertComponent('image_link', false); + $oEditorController->insertComponent('image_gallery', false); + $oEditorController->insertComponent('poll_maker', true); + // Create a directory to use in the editor module FileHandler::makeDir('./files/cache/editor'); - // 2007. 10. 17 Add a trigger to delete automatically saved document whenever the document(insert or update) is modified - $oModuleController->insertTrigger('document.insertDocument', 'editor', 'controller', 'triggerDeleteSavedDoc', 'after'); - $oModuleController->insertTrigger('document.updateDocument', 'editor', 'controller', 'triggerDeleteSavedDoc', 'after'); - // 2007. 10. 23 Add an editor trigger on the module addition setup - $oModuleController->insertTrigger('module.dispAdditionSetup', 'editor', 'view', 'triggerDispEditorAdditionSetup', 'before'); - // 2009. 04. 14 Add a trigger from compiled codes of the editor component - $oModuleController->insertTrigger('display', 'editor', 'controller', 'triggerEditorComponentCompile', 'before'); } /** @@ -90,30 +81,12 @@ class editor extends ModuleObject */ function checkUpdate() { - $oModuleModel = getModel('module'); - - $oDB = &DB::getInstance(); - // 2009. 06. 15 Save module_srl when auto-saving - if(!$oDB->isColumnExists("editor_autosave","module_srl")) return true; - if(!$oDB->isIndexExists("editor_autosave","idx_module_srl")) return true; + $oDB = DB::getInstance(); // XEVE-17-030 if(!$oDB->isColumnExists('editor_autosave', 'certify_key')) return true; if(!$oDB->isIndexExists('editor_autosave', 'idx_certify_key')) return true; - // 2007. 10. 17 Add a trigger to delete automatically saved document whenever the document(insert or update) is modified - if(!$oModuleModel->getTrigger('document.insertDocument', 'editor', 'controller', 'triggerDeleteSavedDoc', 'after')) return true; - if(!$oModuleModel->getTrigger('document.updateDocument', 'editor', 'controller', 'triggerDeleteSavedDoc', 'after')) return true; - // 2007. 10. 23 Add an editor trigger on the module addition setup - if(!$oModuleModel->getTrigger('module.dispAdditionSetup', 'editor', 'view', 'triggerDispEditorAdditionSetup', 'before')) return true; - // 2009. 04. 14 Add a trigger from compiled codes of the editor component - if(!$oModuleModel->getTrigger('display', 'editor', 'controller', 'triggerEditorComponentCompile', 'before')) return true; - // 2009. 06. 19 Remove unused trigger - if($oModuleModel->getTrigger('file.getIsPermitted', 'editor', 'controller', 'triggerSrlSetting', 'before')) return true; - - // 2012. 08. 29 Add a trigger to copy additional setting when the module is copied - if(!$oModuleModel->getTrigger('module.procModuleAdminCopyModule', 'editor', 'controller', 'triggerCopyModule', 'after')) return true; - return false; } @@ -122,19 +95,8 @@ class editor extends ModuleObject */ function moduleUpdate() { - $oModuleModel = getModel('module'); + $oDB = DB::getInstance(); $oModuleController = getController('module'); - $oDB = &DB::getInstance(); - - // Save module_srl when auto-saving 15/06/2009 - if(!$oDB->isColumnExists('editor_autosave', 'module_srl')) - { - $oDB->addColumn('editor_autosave', 'module_srl', 'number'); - } - if(!$oDB->isIndexExists('editor_autosave', 'idx_module_srl')) - { - $oDB->addIndex('editor_autosave', 'idx_module_srl', 'module_srl'); - } // XEVE-17-030 if(!$oDB->isColumnExists('editor_autosave', 'certify_key')) @@ -145,34 +107,6 @@ class editor extends ModuleObject { $oDB->addIndex('editor_autosave', 'idx_certify_key', 'certify_key'); } - - // 2007. 10. 17 Add a trigger to delete automatically saved document whenever the document(insert or update) is modified - if(!$oModuleModel->getTrigger('document.insertDocument', 'editor', 'controller', 'triggerDeleteSavedDoc', 'after')) - $oModuleController->insertTrigger('document.insertDocument', 'editor', 'controller', 'triggerDeleteSavedDoc', 'after'); - if(!$oModuleModel->getTrigger('document.updateDocument', 'editor', 'controller', 'triggerDeleteSavedDoc', 'after')) - $oModuleController->insertTrigger('document.updateDocument', 'editor', 'controller', 'triggerDeleteSavedDoc', 'after'); - // 2007. 10. Add an editor trigger on the module addition setup - if(!$oModuleModel->getTrigger('module.dispAdditionSetup', 'editor', 'view', 'triggerDispEditorAdditionSetup', 'before')) - $oModuleController->insertTrigger('module.dispAdditionSetup', 'editor', 'view', 'triggerDispEditorAdditionSetup', 'before'); - // 2009. 04. 14 Add a trigger from compiled codes of the editor component - if(!$oModuleModel->getTrigger('display', 'editor', 'controller', 'triggerEditorComponentCompile', 'before')) - $oModuleController->insertTrigger('display', 'editor', 'controller', 'triggerEditorComponentCompile', 'before'); - // 2009. 06. 19 Remove unused trigger - if($oModuleModel->getTrigger('file.getIsPermitted', 'editor', 'controller', 'triggerSrlSetting', 'before')) - $oModuleController->deleteTrigger('file.getIsPermitted', 'editor', 'controller', 'triggerSrlSetting', 'before'); - - // 2012. 08. 29 Add a trigger to copy additional setting when the module is copied - if(!$oModuleModel->getTrigger('module.procModuleAdminCopyModule', 'editor', 'controller', 'triggerCopyModule', 'after')) - { - $oModuleController->insertTrigger('module.procModuleAdminCopyModule', 'editor', 'controller', 'triggerCopyModule', 'after'); - } - } - - /** - * @brief Re-generate the cache file - */ - function recompileCache() - { } } /* End of file editor.class.php */ diff --git a/modules/editor/editor.controller.php b/modules/editor/editor.controller.php index eb61ad157..0025becdc 100644 --- a/modules/editor/editor.controller.php +++ b/modules/editor/editor.controller.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * @brief editor module's controller class */ -class editorController extends editor +class EditorController extends Editor { /** * @brief Initialization @@ -52,8 +52,8 @@ class editorController extends editor throw new Rhymix\Framework\Exception('msg_component_is_not_founded', $component); } - $oEditorModel = getModel('editor'); - $oComponent = &$oEditorModel->getComponentObject($component); + $oEditorModel = EditorModel::getInstance(); + $oComponent = $oEditorModel->getComponentObject($component); if(!$oComponent->toBool()) return $oComponent; if(!method_exists($oComponent, $method)) @@ -85,23 +85,25 @@ class editorController extends editor */ function procEditorInsertModuleConfig() { + // Get request vars + $vars = Context::getRequestVars(); + // To configure many of modules at once - $target_module_srl = Context::get('target_module_srl'); + $target_module_srl = $vars->target_module_srl; $target_module_srl = array_map('trim', explode(',', $target_module_srl)); $logged_info = Context::get('logged_info'); $module_srl = array(); - $oModuleModel = getModel('module'); foreach ($target_module_srl as $srl) { if (!$srl) continue; - $module_info = $oModuleModel->getModuleInfoByModuleSrl($srl); + $module_info = ModuleModel::getModuleInfoByModuleSrl($srl); if (!$module_info->module_srl) { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - $module_grant = $oModuleModel->getGrant($module_info, $logged_info); + $module_grant = ModuleModel::getGrant($module_info, $logged_info); if (!$module_grant->manager) { throw new Rhymix\Framework\Exceptions\NotPermitted; @@ -110,58 +112,86 @@ class editorController extends editor $module_srl[] = $srl; } - $editor_config = new stdClass; - $editor_config->default_editor_settings = Context::get('default_editor_settings'); - if($editor_config->default_editor_settings !== 'Y') $editor_config->default_editor_settings = 'N'; - $editor_config->editor_skin = Context::get('editor_skin'); - $editor_config->comment_editor_skin = Context::get('comment_editor_skin'); - $editor_config->content_style = Context::get('content_style'); - $editor_config->comment_content_style = Context::get('comment_content_style'); - $editor_config->content_font = Context::get('content_font'); - if($editor_config->content_font) + // Apply default settings? + $config = new stdClass; + $config->default_editor_settings = $vars->default_editor_settings; + if ($config->default_editor_settings !== 'Y') { - $font_list = array(); - $fonts = explode(',',$editor_config->content_font); - for($i=0,$c=count($fonts);$i<$c;$i++) - { - $font = trim(str_replace(array('"','\''),'',$fonts[$i])); - if(!$font) continue; - $font_list[] = $font; - } - if(count($font_list)) $editor_config->content_font = '"'.implode('","',$font_list).'"'; + $config->default_editor_settings = 'N'; } - $editor_config->content_font_size = Context::get('content_font_size'); - $editor_config->sel_editor_colorset = Context::get('sel_editor_colorset'); - $editor_config->sel_comment_editor_colorset = Context::get('sel_comment_editor_colorset'); - $grants = array('enable_html_grant','enable_comment_html_grant','upload_file_grant','comment_upload_file_grant','enable_default_component_grant','enable_comment_default_component_grant','enable_component_grant','enable_comment_component_grant'); + // Apply module-specific editor settings. + $config->editor_skin = $vars->editor_skin; + $config->editor_colorset = $vars->editor_colorset; + $config->editor_height = $vars->editor_height; + $config->editor_toolbar = $vars->editor_toolbar; + $config->editor_toolbar_hide = $vars->editor_toolbar_hide === 'Y' ? 'Y' : 'N'; + $config->mobile_editor_skin = $vars->mobile_editor_skin; + $config->mobile_editor_colorset = $vars->mobile_editor_colorset; + $config->mobile_editor_height = $vars->mobile_editor_height; + $config->mobile_editor_toolbar = $vars->mobile_editor_toolbar; + $config->mobile_editor_toolbar_hide = $vars->mobile_editor_toolbar_hide === 'Y' ? 'Y' : 'N'; + $config->comment_editor_skin = $vars->comment_editor_skin; + $config->comment_editor_colorset = $vars->comment_editor_colorset; + $config->comment_editor_height = $vars->comment_editor_height; + $config->comment_editor_toolbar = $vars->comment_editor_toolbar; + $config->comment_editor_toolbar_hide = $vars->comment_editor_toolbar_hide === 'Y' ? 'Y' : 'N'; + $config->mobile_comment_editor_skin = $vars->mobile_comment_editor_skin; + $config->mobile_comment_editor_colorset = $vars->mobile_comment_editor_colorset; + $config->mobile_comment_editor_height = $vars->mobile_comment_editor_height; + $config->mobile_comment_editor_toolbar = $vars->mobile_comment_editor_toolbar; + $config->mobile_comment_editor_toolbar_hide = $vars->mobile_comment_editor_toolbar_hide === 'Y' ? 'Y' : 'N'; + + if ($vars->font_defined === 'Y') + { + $config->font_defined = 'Y'; + $config->content_font = $vars->content_font_defined; + } + else + { + $config->font_defined = $vars->font_defined = 'N'; + $config->content_font = $vars->content_font; + } + + $config->content_font_size = trim($vars->content_font_size); + $config->enable_autosave = $vars->enable_autosave ?: 'Y'; + $config->auto_dark_mode = $vars->auto_dark_mode ?: 'Y'; + $config->allow_html = $vars->allow_html ?: 'Y'; + + // Apply module-specific permissions. + $grants = array( + 'enable_html_grant', + 'enable_comment_html_grant', + 'upload_file_grant', + 'comment_upload_file_grant', + 'enable_default_component_grant', + 'enable_comment_default_component_grant', + 'enable_component_grant', + 'enable_comment_component_grant', + ); foreach($grants as $key) { $grant = Context::get($key); if(!$grant) { - $editor_config->{$key} = array(); + $config->{$key} = array(); } else if(is_array($grant)) { - $editor_config->{$key} = $grant; + $config->{$key} = $grant; } else { - $editor_config->{$key} = explode('|@|', $grant); + $config->{$key} = explode('|@|', $grant); } } - $editor_config->editor_height = (int)Context::get('editor_height'); - $editor_config->comment_editor_height = (int)Context::get('comment_editor_height'); - $editor_config->enable_autosave = Context::get('enable_autosave') ?: 'Y'; - $editor_config->allow_html = Context::get('allow_html') ?: 'Y'; - + // Save settings. $oModuleController = getController('module'); foreach ($module_srl as $srl) { - $oModuleController->insertModulePartConfig('editor', $srl, $editor_config); + $oModuleController->insertModulePartConfig('editor', $srl, $config); } $this->setError(-1); @@ -171,6 +201,40 @@ class editorController extends editor $this->setRedirectUrl($returnUrl); } + + /** + * @brief Load editor style + */ + function procLoadEditorStyle() + { + $module_info = Context::get('module_info'); + $module_srl = $module_info->module_srl ?? 0; + if($module_srl) + { + $editor_config = EditorModel::getEditorConfig($module_srl); + } + else + { + $editor_config = ModuleModel::getModuleConfig('editor'); + } + + if ($editor_config) + { + $default_font_config = self::$default_font_config; + if ($editor_config->content_font) $default_font_config['default_font_family'] = $editor_config->content_font; + if ($editor_config->content_font_size) $default_font_config['default_font_size'] = $editor_config->content_font_size; + if ($editor_config->content_line_height) $default_font_config['default_line_height'] = $editor_config->content_line_height; + if ($editor_config->content_paragraph_spacing) $default_font_config['default_paragraph_spacing'] = $editor_config->content_paragraph_spacing; + if ($editor_config->content_word_break) $default_font_config['default_word_break'] = $editor_config->content_word_break; + Context::set('default_font_config', $default_font_config); + } + else + { + Context::set('default_font_config', self::$default_font_config); + } + } + + /** * @brief convert editor component codes to be returned and specify content style. */ @@ -178,78 +242,7 @@ class editorController extends editor { if(Context::getResponseMethod() !== 'HTML') return; - $module_info = Context::get('module_info'); - $module_srl = $module_info->module_srl; - if($module_srl) - { - $editor_config = getModel('editor')->getEditorConfig($module_srl); - } - else - { - $editor_config = getModel('module')->getModuleConfig('editor'); - } - - if ($editor_config) - { - $default_font_config = $this->default_font_config; - if ($editor_config->content_font) $default_font_config['default_font_family'] = $editor_config->content_font; - if ($editor_config->content_font_size) $default_font_config['default_font_size'] = $editor_config->content_font_size; - if ($editor_config->content_line_height) $default_font_config['default_line_height'] = $editor_config->content_line_height; - if ($editor_config->content_paragraph_spacing) $default_font_config['default_paragraph_spacing'] = $editor_config->content_paragraph_spacing; - if ($editor_config->content_word_break) $default_font_config['default_word_break'] = $editor_config->content_word_break; - Context::set('default_font_config', $default_font_config); - - $content_style = $editor_config->content_style; - if($content_style) - { - $path = _XE_PATH_ . 'modules/editor/styles/'.$content_style.'/'; - if(is_dir($path) && file_exists($path . 'style.ini')) - { - $ini = file($path.'style.ini'); - foreach($ini as $file) - { - $file = trim($file); - if(!$file) continue; - - $args = array('./modules/editor/styles/'.$content_style.'/'.$file); - Context::loadFile($args); - } - } - } - - /* - $buff = array(); - $buff[] = ''; - Context::addHtmlHeader(implode(' ', $buff)); - */ - } - else - { - Context::set('default_font_config', $this->default_font_config); - } + $this->procLoadEditorStyle(); $content = $this->transComponent($content); } @@ -279,14 +272,20 @@ class editorController extends editor if(!isset($xml_obj->attrs)) $xml_obj->attrs = new stdClass; $xml_obj->attrs->{$m[1][$i]} = $m[2][$i]; } - $xml_obj->body = $match[4]; + $xml_obj->body = $match[4] ?? null; - if(!$xml_obj->attrs->editor_component) return $match[0]; + if(!isset($xml_obj->attrs->editor_component) || !$xml_obj->attrs->editor_component) + { + return $match[0]; + } // Get converted codes by using component::transHTML() - $oEditorModel = getModel('editor'); - $oComponent = &$oEditorModel->getComponentObject($xml_obj->attrs->editor_component, 0); - if(!is_object($oComponent)||!method_exists($oComponent, 'transHTML')) return $match[0]; + $oEditorModel = EditorModel::getInstance(); + $oComponent = $oEditorModel->getComponentObject($xml_obj->attrs->editor_component, 0); + if(!is_object($oComponent) || !method_exists($oComponent, 'transHTML')) + { + return $match[0]; + } return $oComponent->transHTML($xml_obj); } @@ -328,12 +327,9 @@ class editorController extends editor { $editor_sequence = Context::get('editor_sequence'); $primary_key = Context::get('primary_key'); - $oEditorModel = getModel('editor'); - $oFileController = getController('file'); + $saved_doc = EditorModel::getSavedDoc(null); - $saved_doc = $oEditorModel->getSavedDoc(null); - - $oFileController->setUploadInfo($editor_sequence, $saved_doc->document_srl); + FileController::setUploadInfo($editor_sequence, $saved_doc->document_srl, intval($saved_doc->module_srl)); $vars = $this->getVariables(); $this->add("editor_sequence", $editor_sequence); $this->add("key", $primary_key); @@ -388,8 +384,7 @@ class editorController extends editor return; } - $oDocumentModel = getModel('document'); - $oSaved = $oDocumentModel->getDocument($saved_doc->document_srl); + $oSaved = DocumentModel::getDocument($saved_doc->document_srl); if(!$oSaved->isExists()) { if($mode) @@ -451,18 +446,17 @@ class editorController extends editor if($component->extra_vars) { $extra_vars = unserialize($component->extra_vars); - if($extra_vars->target_group) + if(!empty($extra_vars->target_group)) { $xml_info->target_group = $extra_vars->target_group; } - - if($extra_vars->mid_list && count($extra_vars->mid_list)) + if(!empty($extra_vars->mid_list)) { $xml_info->mid_list = $extra_vars->mid_list; } - + // Check the configuration of the editor component - if($xml_info->extra_vars) + if(!empty($xml_info->extra_vars)) { foreach($xml_info->extra_vars as $key => $val) { @@ -472,7 +466,7 @@ class editorController extends editor } $component_list->{$component_name} = $xml_info; - + // Get buttons, icons, images $icon_file = RX_BASEDIR . 'modules/editor/components/'.$component_name.'/icon.gif'; $component_icon_file = RX_BASEDIR . 'modules/editor/components/'.$component_name.'/component_icon.gif'; @@ -503,7 +497,7 @@ class editorController extends editor { Rhymix\Framework\Cache::set('editor:components:all', $component_list, 0, true); } - + return $component_list; } @@ -519,8 +513,7 @@ class editorController extends editor function triggerCopyModule(&$obj) { - $oModuleModel = getModel('module'); - $editorConfig = $oModuleModel->getModulePartConfig('editor', $obj->originModuleSrl); + $editorConfig = ModuleModel::getModulePartConfig('editor', $obj->originModuleSrl); $oModuleController = getController('module'); if(is_array($obj->moduleSrlList)) diff --git a/modules/editor/editor.model.php b/modules/editor/editor.model.php index b788a2309..f6a430aef 100644 --- a/modules/editor/editor.model.php +++ b/modules/editor/editor.model.php @@ -3,11 +3,16 @@ /** * @class editorModel * @author NAVER (developers@xpressengine.com) - * @brief model class of the editor odule + * @brief model class of the editor module */ -class editorModel extends editor +class EditorModel extends Editor { - var $loaded_component_list = array(); + /** + * Cache + */ + protected static $_module_config = array(); + protected static $_loaded_component_list = array(); + /** * @brief Return the editor * @@ -21,74 +26,88 @@ class editorModel extends editor /** * @brief Return editor config for each module */ - function getEditorConfig($module_srl = null) + public static function getEditorConfig($module_srl = null) { // Load editor config for current module. - $oModuleModel = getModel('module'); if ($module_srl) { - if (!$GLOBALS['__editor_module_config__'][$module_srl]) + if (!isset(self::$_module_config[$module_srl])) { - $GLOBALS['__editor_module_config__'][$module_srl] = $oModuleModel->getModulePartConfig('editor', $module_srl); - } - $editor_config = $GLOBALS['__editor_module_config__'][$module_srl]; - if (!is_object($editor_config)) - { - $editor_config = new stdClass; + self::$_module_config[$module_srl] = ModuleModel::getModulePartConfig('editor', $module_srl); + if (!is_object(self::$_module_config[$module_srl])) + { + self::$_module_config[$module_srl] = new stdClass; + } } + $editor_config = self::$_module_config[$module_srl]; } else { $editor_config = new stdClass; } - + // Fill in some other values. - if(!is_array($editor_config->enable_html_grant)) $editor_config->enable_html_grant = array(); - if(!is_array($editor_config->enable_comment_html_grant)) $editor_config->enable_comment_html_grant = array(); - if(!is_array($editor_config->upload_file_grant)) $editor_config->upload_file_grant = array(); - if(!is_array($editor_config->comment_upload_file_grant)) $editor_config->comment_upload_file_grant = array(); - if(!is_array($editor_config->enable_default_component_grant)) $editor_config->enable_default_component_grant = array(); - if(!is_array($editor_config->enable_comment_default_component_grant)) $editor_config->enable_comment_default_component_grant = array(); - if(!is_array($editor_config->enable_component_grant)) $editor_config->enable_component_grant = array(); - if(!is_array($editor_config->enable_comment_component_grant)) $editor_config->enable_comment_component_grant= array(); - - // Load the default config for editor module. - $editor_default_config = $oModuleModel->getModuleConfig('editor'); - - // Check whether we should use the default config. - if($editor_config->default_editor_settings !== 'Y' && $editor_default_config->editor_skin && $editor_config->editor_skin && $editor_default_config->editor_skin !== $editor_config->editor_skin) + $grant_vars = array( + 'enable_html_grant', + 'enable_comment_html_grant', + 'upload_file_grant', + 'comment_upload_file_grant', + 'enable_default_component_grant', + 'enable_comment_default_component_grant', + 'enable_component_grant', + 'enable_comment_component_grant', + ); + foreach ($grant_vars as $var) { - $editor_config->default_editor_settings = 'N'; + if (!isset($editor_config->{$var}) || !is_array($editor_config->{$var})) + { + $editor_config->{$var} = []; + } } - if(!$editor_config->default_editor_settings) + + // Load the default config for editor module. + $editor_default_config = ModuleModel::getModuleConfig('editor') ?: new stdClass; + + // Check whether we should use the default config. + if(!isset($editor_config->default_editor_settings) || $editor_config->default_editor_settings !== 'Y') + { + if(isset($editor_config->editor_skin) && isset($editor_default_config->editor_skin) && $editor_default_config->editor_skin !== $editor_config->editor_skin) + { + $editor_config->default_editor_settings = 'N'; + } + } + if(!isset($editor_config->default_editor_settings)) { $editor_config->default_editor_settings = 'Y'; } - + // Apply the default config for missing values. - foreach ($this->default_editor_config as $key => $val) + foreach (self::$default_editor_config as $key => $val) { - if ($editor_config->default_editor_settings === 'Y' || !$editor_config->$key) + if ($editor_config->default_editor_settings === 'Y' || !isset($editor_config->$key)) { - $editor_config->$key = $editor_default_config->$key ?: $val; + $editor_config->$key = isset($editor_default_config->$key) ? $editor_default_config->$key : $val; } } - + return $editor_config; } - function getSkinConfig($skin_name) + /** + * @brief Return skin config + */ + public static function getSkinConfig($skin_name) { $skin_config = new stdClass; - - if($skin_info = getModel('module')->loadSkinInfo($this->module_path, $skin_name)) + + if($skin_info = ModuleModel::loadSkinInfo('./modules/editor', $skin_name)) { foreach ($skin_info->extra_vars as $val) { $skin_config->{$val->name} = $val->value; } } - + return $skin_config; } @@ -97,8 +116,17 @@ class editorModel extends editor * You can call upload_target_srl when modifying content * The upload_target_srl is used for a routine to check if an attachment exists */ - function getEditor($upload_target_srl = 0, $option = null) + public static function getEditor($upload_target_srl = 0, $option = null) { + // Load language files. + Context::loadLang('./modules/editor/lang'); + + // Initialize options. + if (!is_object($option)) + { + $option = new stdClass; + + } // Set editor sequence and upload options. if ($upload_target_srl) { @@ -106,91 +134,98 @@ class editorModel extends editor } else { - if(!$_SESSION['_editor_sequence_']) $_SESSION['_editor_sequence_'] = 1; + if(empty($_SESSION['_editor_sequence_'])) + { + $_SESSION['_editor_sequence_'] = 4; + } $option->editor_sequence = $_SESSION['_editor_sequence_']++; } Context::set('allow_fileupload', $option->allow_fileupload = toBool($option->allow_fileupload)); Context::set('upload_target_srl', $upload_target_srl); Context::set('editor_sequence', $option->editor_sequence); - - // Check that the skin and content style exist. - if (!$option->editor_skin) + + // Check that the skin exist. + if (empty($option->editor_skin)) { - $option->editor_skin = $option->skin; + $option->editor_skin = $option->skin ?? null; } - if (!$option->editor_skin || !file_exists($this->module_path . 'skins/' . $option->editor_skin . '/editor.html') || starts_with('xpresseditor', $option->editor_skin) || starts_with('dreditor', $option->editor_skin)) + if (empty($option->editor_skin) || starts_with('xpresseditor', $option->editor_skin) || starts_with('dreditor', $option->editor_skin)) { - $option->editor_skin = $this->default_editor_config['editor_skin']; + $option->editor_skin = self::$default_editor_config['editor_skin']; } - if (!$option->content_style || !file_exists($this->module_path . 'styles/' . $option->content_style)) + if (!file_exists('./modules/editor/skins/' . $option->editor_skin . '/editor.html') && !file_exists('./modules/editor/skins/' . $option->editor_skin . '/editor.blade.php')) { - $option->content_style = $this->default_editor_config['content_style']; + $option->editor_skin = self::$default_editor_config['editor_skin']; } - if (!$option->sel_editor_colorset) + if (empty($option->editor_colorset)) { - $option->sel_editor_colorset = $option->colorset ?: $this->default_editor_config['sel_editor_colorset']; + $option->editor_colorset = $option->colorset ?? ($option->sel_editor_colorset ?? self::$default_editor_config['editor_colorset']); } - if (!$option->editor_height) + if (empty($option->editor_height)) { - $option->editor_height = $option->height ?: $this->default_editor_config['editor_height']; + $option->editor_height = $option->height ?: self::$default_editor_config['editor_height']; } - if ($option->editor_skin === 'ckeditor' && preg_match('/^(?:white|black)(_text_(?:use|no)html)?$/', $option->sel_editor_colorset)) + if ($option->editor_skin === 'ckeditor' && !in_array($option->editor_colorset, array('moono', 'moono-dark', 'moono-lisa'))) { - $option->sel_editor_colorset = 'moono-lisa'; + $option->editor_colorset = 'moono-lisa'; + } + if ($option->editor_skin === 'simpleeditor' && !in_array($option->editor_colorset, array('light', 'dark'))) + { + $option->editor_colorset = 'light'; } Context::set('skin', $option->editor_skin); - Context::set('editor_path', $this->module_path . 'skins/' . $option->editor_skin . '/'); - Context::set('content_style', $option->content_style); - Context::set('content_style_path', $this->module_path . 'styles/' . $option->content_style); - Context::set('colorset', $option->sel_editor_colorset); + Context::set('editor_path', './modules/editor/skins/' . $option->editor_skin . '/'); + Context::set('colorset', $option->editor_colorset); Context::set('editor_height', $option->editor_height); - Context::set('editor_toolbar', $option->editor_toolbar); - Context::set('editor_toolbar_hide', toBool($option->editor_toolbar_hide)); - Context::set('module_type', $option->module_type); - + Context::set('editor_toolbar', $option->editor_toolbar ?? null); + Context::set('editor_toolbar_hide', toBool($option->editor_toolbar_hide ?? null)); + Context::set('module_type', $option->module_type ?? null); + // Default font setting - Context::set('content_font', $option->content_font); - Context::set('content_font_size', $option->content_font_size); - Context::set('content_line_height', $option->content_line_height); - Context::set('content_paragraph_spacing', $option->content_paragraph_spacing); - Context::set('content_word_break', $option->content_word_break); - Context::set('editor_autoinsert_image', $option->autoinsert_image); - Context::set('editor_additional_css', $option->additional_css); - Context::set('editor_additional_plugins', $option->additional_plugins); - Context::set('editor_remove_plugins', $option->remove_plugins); - + Context::set('content_font', $option->content_font ?? null); + Context::set('content_font_size', $option->content_font_size ?? null); + Context::set('content_line_height', $option->content_line_height ?? null); + Context::set('content_paragraph_spacing', $option->content_paragraph_spacing ?? null); + Context::set('content_word_break', $option->content_word_break ?? null); + Context::set('editor_autoinsert_types', $option->autoinsert_types ?? (($option->autoinsert_image ?? null) !== 'none' ? self::$default_editor_config['autoinsert_types'] : [])); + Context::set('editor_autoinsert_position', $option->autoinsert_position ?? ($option->autoinsert_image ?? null)); + Context::set('editor_additional_css', $option->additional_css ?? []); + Context::set('editor_additional_plugins', $option->additional_plugins ?? []); + Context::set('editor_remove_plugins', $option->remove_plugins ?? []); + // Set the primary key valueof the document or comments Context::set('editor_primary_key_name', $option->primary_key_name); - + // Set content column name to sync contents Context::set('editor_content_key_name', $option->content_key_name); - + // Set autosave (do not use if the post is edited) $option->enable_autosave = toBool($option->enable_autosave) && !Context::get($option->primary_key_name); if ($option->enable_autosave) { - Context::set('saved_doc', $this->getSavedDoc($upload_target_srl)); + Context::set('saved_doc', self::getSavedDoc($upload_target_srl)); } - Context::set('enable_autosave', $option->enable_autosave); - + Context::set('enable_autosave', $option->enable_autosave ?? false); + // Set allow html and focus - Context::set('allow_html', ($option->allow_html === false || $option->allow_html === 'N') ? false : true); - Context::set('editor_focus', toBool($option->editor_focus)); - + Context::set('allow_html', (isset($option->allow_html) && ($option->allow_html === false || $option->allow_html === 'N')) ? false : true); + Context::set('editor_focus', toBool($option->editor_focus ?? false)); + Context::set('editor_auto_dark_mode', ($option->auto_dark_mode ?? 'Y') !== 'N'); + // Load editor components. if($option->enable_component) { - if(!Context::get('component_list')) - { - $component_list = $this->getComponentList(true); - Context::set('component_list', $component_list); - } + Context::set('component_list', self::getComponentList(true)); } - Context::set('enable_component', $option->enable_component ? true : false); - Context::set('enable_default_component', $option->enable_default_component ? true : false); + else + { + Context::set('component_list', []); + } + Context::set('enable_component', ($option->enable_component ?? false) ? true : false); + Context::set('enable_default_component', ($option->enable_default_component ?? false) ? true : false); // Set HTML mode. - Context::set('html_mode', $option->disable_html ? false : true); + Context::set('html_mode', ($option->disable_html ?? false) ? false : true); /** * Upload setting by using configuration of the file module internally @@ -199,14 +234,16 @@ class editorModel extends editor if($option->allow_fileupload) { // Get file upload limits - $oFileModel = getModel('file'); - $file_config = $oFileModel->getUploadConfig(); + $file_config = FileModel::getUploadConfig(); $file_config->allowed_attach_size = $file_config->allowed_attach_size*1024*1024; $file_config->allowed_filesize = $file_config->allowed_filesize*1024*1024; - if (PHP_INT_SIZE < 8) + if (isset($option->allowed_filesize) && $option->allowed_filesize > 0) { - $file_config->allowed_filesize = min($file_config->allowed_filesize, 2147483647); + $file_config->allowed_attach_size = $option->allowed_filesize; + $file_config->allowed_filesize = $option->allowed_filesize; } + + // Calculate the appropriate chunk size. $file_config->allowed_chunk_size = min(FileHandler::returnBytes(ini_get('upload_max_filesize')), FileHandler::returnBytes(ini_get('post_max_size')) * 0.95, 64 * 1024 * 1024); if ($file_config->allowed_chunk_size > 4 * 1048576) { @@ -216,7 +253,7 @@ class editorModel extends editor { $file_config->allowed_chunk_size = floor($file_config->allowed_chunk_size / 65536) * 65536; } - + // Do not allow chunked uploads in IE < 10, Android browser, and Opera $browser = Rhymix\Framework\UA::getBrowserInfo(); if (($browser->browser === 'IE' && version_compare($browser->version, '10', '<')) || $browser->browser === 'Android' || $browser->browser === 'Opera') @@ -224,34 +261,58 @@ class editorModel extends editor $file_config->allowed_filesize = min($file_config->allowed_filesize, FileHandler::returnBytes(ini_get('upload_max_filesize')), FileHandler::returnBytes(ini_get('post_max_size'))); $file_config->allowed_chunk_size = 0; } - - // Do not allow chunked uploads in XpressEditor. - if (starts_with($option->editor_skin, 'xpresseditor')) + + Context::set('file_config', $file_config); + + // Configure upload status such as file size + $upload_status = FileModel::getUploadStatus(); + Context::set('upload_status', $upload_status); + + // Set upload config in session + $upload_config = []; + $upload_config_keys = ['allowed_filesize', 'allowed_extensions', 'upload_target_type']; + foreach ($upload_config_keys as $key) { - $file_config->allowed_filesize = min($file_config->allowed_filesize, FileHandler::returnBytes(ini_get('upload_max_filesize')), FileHandler::returnBytes(ini_get('post_max_size'))); - $file_config->allowed_chunk_size = 0; + if (isset($option->$key) && !empty($option->$key)) + { + $upload_config[$key] = $option->$key; + } + } + FileController::setUploadInfo($option->editor_sequence, $upload_target_srl, $option->module_srl ?? 0, $upload_config); + + // Set editor_mid, which may be different from current_mid on the client side. + // While current_mid follows the URL that the user is currently viewing, + // editor_mid unambiguously refers to the module to which files should be uploaded. + // This difference may be significant when a document from one module is shown in another module. + if (!empty($option->module_srl)) + { + $option->mid = ModuleModel::getModuleInfoByModuleSrl($option->module_srl)->mid ?? null; + } + if (!empty($option->mid)) + { + Context::addHtmlFooter(''); } - Context::set('file_config',$file_config); - // Configure upload status such as file size - $upload_status = $oFileModel->getUploadStatus(); - Context::set('upload_status', $upload_status); - // Upload enabled (internally caching) - $oFileController = getController('file'); - $oFileController->setUploadInfo($option->editor_sequence, $upload_target_srl); // Check if the file already exists - if($upload_target_srl) $files_count = $oFileModel->getFilesCount($upload_target_srl); + if ($upload_target_srl) + { + $files_count = FileModel::getFilesCount($upload_target_srl, $option->upload_target_type ?? null); + } } Context::set('files_count', (int)$files_count); // Check an option whether to start the editor manually. - Context::set('editor_manual_start', $option->manual_start); + Context::set('editor_manual_start', $option->manual_start ?? null); + + // Add the timestamp for the editor module config. + Context::set('editor_config_timestamp', $option->timestamp ?? 0); // Compile and return the editor skin template. $tpl_path = Context::get('editor_path'); Context::loadLang($tpl_path.'lang'); + $oTemplate = TemplateHandler::getInstance(); - return $oTemplate->compile($tpl_path, 'editor.html'); + return $oTemplate->compile($tpl_path, 'editor'); } /** @@ -261,27 +322,30 @@ class editorModel extends editor * 2 types of editors supported; document and comment. * 2 types of editors can be used on a single module. For instance each for original post and reply port. */ - function getModuleEditor($type = 'document', $module_srl, $upload_target_srl, $primary_key_name, $content_key_name) + public static function getModuleEditor($type, $module_srl, $upload_target_srl, $primary_key_name, $content_key_name) { // Get editor settings of the module - $editor_config = $this->getEditorConfig($module_srl); + $editor_config = self::getEditorConfig($module_srl); // Check mobile status $is_mobile = Mobile::isFromMobilePhone() || \Rhymix\Framework\UA::isMobile(); - + // Initialize options $option = new stdClass(); $option->module_type = $type; + $option->module_srl = (int)$module_srl; // Convert configuration keys according to type (document or comment). if($type == 'document') { - foreach (get_object_vars($editor_config) as $key => $val) + foreach ((array)$editor_config as $key => $val) { $option->$key = $val; } if ($is_mobile) { + $option->editor_skin = $option->mobile_editor_skin ?: $option->editor_skin; + $option->editor_colorset = $option->mobile_editor_colorset ?: ($option->editor_colorset ?: $option->sel_editor_colorset); $option->editor_height = $option->mobile_editor_height; $option->editor_toolbar = $option->mobile_editor_toolbar; $option->editor_toolbar_hide = $option->mobile_editor_toolbar_hide; @@ -290,30 +354,31 @@ class editorModel extends editor } else { - foreach (get_object_vars($editor_config) as $key => $val) + foreach ((array)$editor_config as $key => $val) { $option->$key = $val; } - $option->editor_skin = $option->comment_editor_skin; - $option->content_style = $option->comment_content_style; - $option->sel_editor_colorset = $option->sel_comment_editor_colorset; - $option->upload_file_grant = $option->comment_upload_file_grant; - $option->enable_default_component_grant = $option->enable_comment_default_component_grant; - $option->enable_component_grant = $option->enable_comment_component_grant; - $option->enable_html_grant = $option->enable_comment_html_grant; + $option->editor_skin = $option->comment_editor_skin ?: $option->editor_skin; + $option->editor_colorset = $option->comment_editor_colorset ?: ($option->editor_colorset ?: $option->sel_editor_colorset); $option->editor_height = $option->comment_editor_height; $option->editor_toolbar = $option->comment_editor_toolbar; $option->editor_toolbar_hide = $option->comment_editor_toolbar_hide; $option->enable_autosave = 'N'; + $option->upload_file_grant = $option->comment_upload_file_grant; + $option->enable_default_component_grant = $option->enable_comment_default_component_grant; + $option->enable_component_grant = $option->enable_comment_component_grant; + $option->enable_html_grant = $option->enable_comment_html_grant; if ($is_mobile) { + $option->editor_skin = $option->mobile_comment_editor_skin ?: ($option->comment_editor_skin ?: $option->editor_skin); + $option->editor_colorset = $option->mobile_comment_editor_colorset ?: ($option->comment_editor_colorset ?: ($option->editor_colorset ?: $option->sel_editor_colorset)); $option->editor_height = $option->mobile_comment_editor_height; $option->editor_toolbar = $option->mobile_comment_editor_toolbar; $option->editor_toolbar_hide = $option->mobile_comment_editor_toolbar_hide; $option->additional_css = $option->additional_mobile_css; } } - + // Check a group_list of the currently logged-in user for permission check if(Context::get('is_logged')) { @@ -324,7 +389,7 @@ class editorModel extends editor { $group_list = array(); } - + // Permission check for file upload if($module_srl) { @@ -345,7 +410,7 @@ class editorModel extends editor } } } - + // Permission check for using default components if ($logged_info->is_admin === 'Y' || !count($option->enable_default_component_grant)) { @@ -363,7 +428,7 @@ class editorModel extends editor } } } - + // Permisshion check for using extended components if($logged_info->is_admin === 'Y' || !count($option->enable_component_grant)) { @@ -381,7 +446,7 @@ class editorModel extends editor } } } - + // HTML editing privileges if($logged_info->is_admin === 'Y' || !count($option->enable_html_grant)) { @@ -399,17 +464,32 @@ class editorModel extends editor } } } - + + // Preset upload target type + if ($type === 'document') + { + $option->upload_target_type = 'doc'; + // For dispWidgetAdminAddContent + if ($primary_key_name === 'module_srl') + { + $option->upload_target_type = 'mod'; + } + } + elseif ($type === 'comment') + { + $option->upload_target_type = 'com'; + } + // Other settings $option->primary_key_name = $primary_key_name; $option->content_key_name = $content_key_name; - return $this->getEditor($upload_target_srl, $option); + return self::getEditor($upload_target_srl, $option); } /** * @brief Get information which has been auto-saved */ - function getSavedDoc($upload_target_srl) + public static function getSavedDoc($upload_target_srl) { $auto_save_args = new stdClass(); $auto_save_args->module_srl = Context::get('module_srl'); @@ -442,7 +522,11 @@ class editorModel extends editor // Return null if no result is auto-saved if(!$saved_doc) return; - + if(is_array($saved_doc)) + { + $saved_doc = array_first($saved_doc); + } + // Return null if certify key does not match if($saved_doc->certify_key && !isset($auto_save_args->certify_key)) { @@ -450,8 +534,7 @@ class editorModel extends editor } // Check if the auto-saved document already exists - $oDocumentModel = getModel('document'); - $oSaved = $oDocumentModel->getDocument($saved_doc->document_srl); + $oSaved = DocumentModel::getDocument($saved_doc->document_srl); if($oSaved->isExists()) return; // Move all the files if the auto-saved data contains document_srl and file @@ -469,7 +552,7 @@ class editorModel extends editor // Change auto-saved data $saved_doc->certify_key = $auto_save_args->certify_key; - if(!$saved_doc->certify_key) + if(!$saved_doc->certify_key && !Context::get('is_logged')) { $saved_doc->certify_key = Rhymix\Framework\Security::getRandom(32); setcookie('autosave_certify_key_' . $saved_doc->module_srl, $saved_doc->certify_key, time() + 86400, null, null, RX_SSL, true); @@ -486,33 +569,42 @@ class editorModel extends editor /** * @brief create objects of the component */ - function getComponentObject($component, $editor_sequence = 0, $site_srl = 0) + public static function getComponentObject($component, $editor_sequence = 0, $site_srl = 0) { - if(!preg_match('/^[a-zA-Z0-9_-]+$/',$component) || !preg_match('/^[0-9]+$/', $editor_sequence . $site_srl)) return; + if(!preg_match('/^[a-zA-Z0-9_-]+$/',$component) || !preg_match('/^[0-9]+$/', $editor_sequence . $site_srl)) + { + return new BaseObject(-1, 'msg_component_is_not_founded', $component); + } - if(!$this->loaded_component_list[$component][$editor_sequence]) + if(!isset(self::$_loaded_component_list[$component][$editor_sequence])) { // Create an object of the component and execute - $class_path = sprintf('%scomponents/%s/', $this->module_path, $component); + $class_path = sprintf('./modules/editor/components/%s/', $component); $class_file = sprintf('%s%s.class.php', $class_path, $component); if(!file_exists($class_file)) return new BaseObject(-1, 'msg_component_is_not_founded', $component); + // Create an object after loading the class file require_once($class_file); $oComponent = new $component($editor_sequence, $class_path); if(!$oComponent) return new BaseObject(-1, 'msg_component_is_not_founded', $component); + // Add configuration information - $component_info = $this->getComponent($component, $site_srl); + $component_info = self::getComponent($component, $site_srl); + if ($component_info->enabled !== 'Y') + { + return new BaseObject(-1, 'msg_component_is_disabled', $component); + } $oComponent->setInfo($component_info); - $this->loaded_component_list[$component][$editor_sequence] = $oComponent; + self::$_loaded_component_list[$component][$editor_sequence] = $oComponent; } - return $this->loaded_component_list[$component][$editor_sequence]; + return self::$_loaded_component_list[$component][$editor_sequence]; } /** * @brief Return a list of the editor skin */ - function getEditorSkinList() + public static function getEditorSkinList() { return FileHandler::readDir('./modules/editor/skins'); } @@ -520,7 +612,7 @@ class editorModel extends editor /** * @brief Return a component list (DB Information included) */ - function getComponentList($filter_enabled = true, $site_srl = 0, $from_db = false) + public static function getComponentList($filter_enabled = true, $site_srl = 0, $from_db = false) { $cache_key = 'editor:components:' . ($filter_enabled ? 'enabled' : 'all'); $component_list = $from_db ? null : Rhymix\Framework\Cache::get($cache_key); @@ -547,7 +639,7 @@ class editorModel extends editor if(!trim($key)) continue; if(!is_dir(\RX_BASEDIR.'modules/editor/components/'.$key)) { - return $this->getComponentList($filter_enabled, 0, true); + return self::getComponentList($filter_enabled, 0, true); } if(!$filter_enabled) continue; if($val->enabled == "N") @@ -590,7 +682,7 @@ class editorModel extends editor /** * @brief Get xml and db information of the component */ - function getComponent($component_name) + public static function getComponent($component_name) { $args = new stdClass(); $args->component_name = $component_name; @@ -601,12 +693,14 @@ class editorModel extends editor $component_name = $component->component_name; - unset($xml_info); - $xml_info = $this->getComponentXmlInfo($component_name); + $xml_info = self::getComponentXmlInfo($component_name); + if (!$xml_info) + { + return (object)['enabled' => false]; + } + $xml_info->enabled = $component->enabled; - $xml_info->target_group = array(); - $xml_info->mid_list = array(); if($component->extra_vars) @@ -640,167 +734,42 @@ class editorModel extends editor /** * @brief Read xml information of the component */ - function getComponentXmlInfo($component) + public static function getComponentXmlInfo($component) { - $lang_type = Context::getLangType(); - // Get xml file path of the requested components - $component_path = sprintf('%s/components/%s/', $this->module_path, $component); + $component = preg_replace('/[^a-zA-Z0-9-_]/', '', $component); + $component_path = sprintf('%s/components/%s/', './modules/editor', $component); $xml_file = sprintf('%sinfo.xml', $component_path); - $cache_file = sprintf('./files/cache/editor/%s.%s.php', $component, $lang_type); + $xml_mtime = filemtime($xml_file); + $lang_type = Context::getLangType(); - // Include and return xml file information if cached file exists - if(file_exists($cache_file) && file_exists($xml_file) && filemtime($cache_file) > filemtime($xml_file)) + // Get from cache + $cache_key = sprintf('editor:component:%s:%s:%d', $component, $lang_type, $xml_mtime); + $info = Rhymix\Framework\Cache::get($cache_key); + if (!empty($info)) { - include($cache_file); - - return $xml_info; + return $info; } - $oParser = new XmlParser(); - $xml_doc = $oParser->loadXmlFile($xml_file); + // Parse XML file + $info = Rhymix\Framework\Parsers\EditorComponentParser::loadXML($xml_file, $component, $lang_type); - // Component information listed - $component_info = new stdClass; - $component_info->author = array(); - $component_info->extra_vars = new stdClass; - $component_info->component_name = $component; - $component_info->title = $xml_doc->component->title->body; - - if($xml_doc->component->version) - { - $component_info->description = str_replace('\n', "\n", $xml_doc->component->description->body); - $component_info->version = $xml_doc->component->version->body; - $component_info->date = $xml_doc->component->date->body; - $component_info->homepage = $xml_doc->component->link->body; - $component_info->license = $xml_doc->component->license->body; - $component_info->license_link = $xml_doc->component->license->attrs->link; - } - else - { - sscanf($xml_doc->component->author->attrs->date, '%d. %d. %d', $date_obj->y, $date_obj->m, $date_obj->d); - $date = sprintf('%04d%02d%02d', $date_obj->y, $date_obj->m, $date_obj->d); - - $component_info->description = str_replace('\n', "\n", $xml_doc->component->author->description->body); - $component_info->version = $xml_doc->component->attrs->version; - $component_info->date = $date; - - $component_info->author = array(); - $component_info->author[0]->name = $xml_doc->component->author->name->body; - $component_info->author[0]->email_address = $xml_doc->component->author->attrs->email_address; - $component_info->author[0]->homepage = $xml_doc->component->author->attrs->link; - } - - // Author information - $author_list = array(); - if(!is_array($xml_doc->component->author)) $author_list[] = $xml_doc->component->author; - else $author_list = $xml_doc->component->author; - - for($i = 0; $i < count($author_list); $i++) - { - $author = new stdClass; - $author->name = $author_list[$i]->name->body; - $author->email_address = $author_list[$i]->attrs->email_address; - $author->homepage = $author_list[$i]->attrs->link; - $component_info->author[] = $author; - } - - // List extra variables (text type only for editor component) - $extra_vars = $xml_doc->component->extra_vars; - if($extra_vars) - { - $extra_var_groups = $extra_vars->group; - if(!$extra_var_groups) - { - $extra_var_groups = $extra_vars; - } - if(!is_array($extra_var_groups)) - { - $extra_var_groups = array($extra_var_groups); - } - - foreach($extra_var_groups as $group) - { - $extra_vars = $group->var; - if(!is_array($group->var)) - { - $extra_vars = array($group->var); - } - - foreach($extra_vars as $key => $val) - { - if(!$val) - { - continue; - } - - $obj = new stdClass(); - if(!$val->attrs) - { - $val->attrs = new stdClass(); - } - if(!$val->attrs->type) - { - $val->attrs->type = 'text'; - } - - $obj->group = $group->title->body; - $obj->name = $val->attrs->name; - $obj->title = $val->title->body; - $obj->type = $val->attrs->type; - $obj->description = $val->description->body; - if($obj->name) - { - $obj->value = $extra_vals->{$obj->name}; - } - if(strpos($obj->value, '|@|') != FALSE) - { - $obj->value = explode('|@|', $obj->value); - } - if($obj->type == 'mid_list' && !is_array($obj->value)) - { - $obj->value = array($obj->value); - } - - // 'Select'type obtained from the option list. - if($val->options && !is_array($val->options)) - { - $val->options = array($val->options); - } - - for($i = 0, $c = count($val->options); $i < $c; $i++) - { - $obj->options[$i] = new stdClass(); - $obj->options[$i]->title = $val->options[$i]->title->body; - $obj->options[$i]->value = $val->options[$i]->attrs->value; - } - - $component_info->extra_vars->{$obj->name} = $obj; - } - } - } - - $buff = array(); - $buff[] = 'getEditorConfig($obj->module_srl); - + $config = self::getEditorConfig($obj->module_srl); + // Get editor skin if (in_array($type, array('document', 'comment'))) { @@ -811,13 +780,13 @@ class editorModel extends editor $converter = $obj->converter; $skin = $obj->editor_skin ?: $config->editor_skin; } - + // if not inserted converter, Get converter from skin if (!$converter) { - $converter = $this->getSkinConfig($skin)->converter; + $converter = self::getSkinConfig($skin)->converter ?? null; } - + // if not inserted converter, Check if (!$converter) { @@ -825,7 +794,7 @@ class editorModel extends editor { $converter = 'text'; } - elseif (strpos($type == 'comment' ? $config->sel_comment_editor_colorset : $config->sel_editor_colorset, 'nohtml') !== false) + elseif (strpos($type == 'comment' ? ($config->sel_comment_editor_colorset ?? '') : ($config->sel_editor_colorset ?? ''), 'nohtml') !== false) { $converter = 'text'; } @@ -834,7 +803,7 @@ class editorModel extends editor $converter = 'nl2br'; } } - + // Convert if ($converter) { @@ -842,13 +811,13 @@ class editorModel extends editor { // Remove Tag $obj->content = strip_tags($obj->content); - + // Trim space $obj->content = utf8_trim($obj->content); - + // Escape $obj->content = escape($obj->content, false); - + // Insert HTML line $obj->content = nl2br($obj->content); } @@ -869,7 +838,7 @@ class editorModel extends editor $obj->content = nl2br($obj->content); } } - + return $obj->content; } } diff --git a/modules/editor/editor.view.php b/modules/editor/editor.view.php index a13b84b6e..6021a1870 100644 --- a/modules/editor/editor.view.php +++ b/modules/editor/editor.view.php @@ -6,7 +6,7 @@ * @author NAVER (developers@xpressengine.com) * @brief view class of the editor module */ -class editorView extends editor +class EditorView extends Editor { /** * @brief Initialization @@ -24,12 +24,11 @@ class editorView extends editor $parent_input_id = Context::get('parent_input_id'); Context::set('parent_input_id', preg_replace('/[^a-z0-9_]/i', '', $parent_input_id)); Context::addBodyClass('disable_debug_panel'); - + // Load editor $oEditorModel = getModel('editor'); $option = $oEditorModel->getEditorConfig(); $option->editor_skin = 'ckeditor'; - $option->content_style = 'ckeditor_light'; $option->sel_editor_colorset = 'moono-lisa'; $option->primary_key_name = 'primary_key'; $option->content_key_name = 'content'; @@ -41,7 +40,7 @@ class editorView extends editor $option->editor_focus = 'Y'; $editor = $oEditorModel->getEditor(0, $option); Context::set('editor', $editor); - + // Set template $this->setLayoutPath('./common/tpl/'); $this->setLayoutFile("default_layout"); @@ -56,18 +55,29 @@ class editorView extends editor { // add a css file Context::loadFile($this->module_path."tpl/css/editor.css", true); + // List variables $editor_sequence = Context::get('editor_sequence'); - $component = Context::get('component'); + $iframe_sequence = Context::get('iframe_sequence'); + if (empty($editor_sequence) || !ctype_alnum($editor_sequence)) + { + return new BaseObject(-1, 'msg_invalid_request'); + } + if (isset($iframe_sequence) && !ctype_alnum($iframe_sequence)) + { + return new BaseObject(-1, 'msg_invalid_request'); + } + $component = Context::get('component'); $site_module_info = Context::get('site_module_info'); $site_srl = (int)$site_module_info->site_srl; + // Get compoenet object - $oEditorModel = getModel('editor'); - $oComponent = &$oEditorModel->getComponentObject($component, $editor_sequence, $site_srl); + $oComponent = EditorModel::getComponentObject($component, $editor_sequence, $site_srl); if(!$oComponent->toBool()) { - Context::set('message', sprintf(lang('msg_component_is_not_founded'), $component)); + Context::set('message', sprintf($oComponent->getMessage(), $component)); + $this->setLayoutFile('popup_layout'); $this->setTemplatePath($this->module_path.'tpl'); $this->setTemplateFile('component_not_founded'); } @@ -124,74 +134,84 @@ class editorView extends editor $current_module_srl = $current_module_info->module_srl; if(!$current_module_srl) return new BaseObject(); } + // Get editors settings $oEditorModel = getModel('editor'); $editor_config = $oEditorModel->getEditorConfig($current_module_srl); + if (!is_object($editor_config)) + { + $editor_config = new stdClass(); + } + + // Use default config for missing values. + foreach (self::$default_editor_config as $key => $val) + { + if (!isset($editor_config->$key)) + { + $editor_config->$key = $val; + } + } + + // Get skin info + $editor_skin_list = array(); + $skin_dir_list = FileHandler::readDir($this->module_path . 'skins'); + foreach ($skin_dir_list as $skin) + { + if (starts_with('xpresseditor', $skin) || starts_with('dreditor', $skin)) + { + continue; + } + + $skin_info = ModuleModel::loadSkinInfo($this->module_path, $skin); + foreach ($skin_info->colorset ?: [] as $colorset) + { + unset($colorset->screenshot); + } + $editor_skin_list[$skin] = $skin_info; + } Context::set('editor_config', $editor_config); - - $oModuleModel = getModel('module'); - // Get a list of editor skin - $editor_skin_list = FileHandler::readDir(_XE_PATH_.'modules/editor/skins'); - $editor_skin_list = array_filter($editor_skin_list, function($name) { return !starts_with('xpresseditor', $name) && !starts_with('dreditor', $name); }); Context::set('editor_skin_list', $editor_skin_list); - $skin_info = $oModuleModel->loadSkinInfo($this->module_path,$editor_config->editor_skin); - Context::set('editor_colorset_list', $skin_info->colorset); - $skin_info = $oModuleModel->loadSkinInfo($this->module_path,$editor_config->comment_editor_skin); - Context::set('editor_comment_colorset_list', $skin_info->colorset); - - $contents = FileHandler::readDir(_XE_PATH_.'modules/editor/styles'); - $content_style_list = array(); - for($i=0,$c=count($contents);$i<$c;$i++) - { - $style = $contents[$i]; - $info = $oModuleModel->loadSkinInfo($this->module_path,$style,'styles'); - $content_style_list[$style] = new stdClass(); - $content_style_list[$style]->title = $info->title; - } - Context::set('content_style_list', $content_style_list); // Get a group list - $oMemberModel = getModel('member'); - $site_module_info = Context::get('site_module_info'); - $group_list = $oMemberModel->getGroups($site_module_info->site_srl); + $group_list = MemberModel::getGroups(); Context::set('group_list', $group_list); + foreach ($group_list ?: [] as $group) + { + $group->title = Context::replaceUserLang($group->title, true); + } //Security $security = new Security(); $security->encodeHTML('group_list..title'); $security->encodeHTML('group_list..description'); - $security->encodeHTML('content_style_list..'); $security->encodeHTML('editor_comment_colorset_list..title'); // Set a template file - $oTemplate = &TemplateHandler::getInstance(); + $oTemplate = TemplateHandler::getInstance(); $tpl = $oTemplate->compile($this->module_path.'tpl', 'editor_module_config'); $obj .= $tpl; return new BaseObject(); } - - function dispEditorPreview() - { - $this->setTemplatePath($this->module_path.'tpl'); - $this->setTemplateFile('preview'); - } - function dispEditorSkinColorset() { $skin = Context::get('skin'); - $oModuleModel = getModel('module'); - $skin_info = $oModuleModel->loadSkinInfo($this->module_path,$skin); - $colorset = $skin_info->colorset; + if (!preg_match('/^[a-zA-Z0-9_-]+$/', $skin)) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest(); + } + + $skin_info = ModuleModel::loadSkinInfo($this->module_path,$skin); + $colorset = $skin_info->colorset ?? null; Context::set('colorset', $colorset); } function dispEditorConfigPreview() { Context::set('editor', getModel('editor')->getModuleEditor(Context::get('type'), 0, 0, 'dummy_key', 'dummy_content')); - + $this->setLayoutFile('default_layout'); $this->setTemplatePath($this->module_path.'tpl'); $this->setTemplateFile('config_preview'); diff --git a/modules/editor/lang/en.php b/modules/editor/lang/en.php index 2419c8867..38598af33 100644 --- a/modules/editor/lang/en.php +++ b/modules/editor/lang/en.php @@ -1,14 +1,15 @@ editor_now = 'Editor Preview with the Current Settings'; $lang->editor_component = 'Editor Component'; $lang->main_editor = 'Main Editor'; $lang->comment_editor = 'Comment Editor'; +$lang->guide_editor_skin = 'Editor Skin'; +$lang->guide_editor_height = 'Height'; +$lang->guide_editor_toolbar = 'Toolbar'; +$lang->editor_toolbar_default = 'Default'; +$lang->editor_toolbar_simple = 'Simple'; +$lang->editor_toolbar_hide = 'Hidden'; $lang->editor_common_settings = 'Common Settings'; -$lang->guide_choose_main_editor = 'Main editor'; -$lang->guide_set_height_main_editor = 'Main editor height'; -$lang->guide_set_main_editor_toolbar = 'Main editor toolbar'; -$lang->guide_choose_comment_editor = 'Comment editor'; -$lang->guide_set_height_comment_editor = 'Comment editor height'; -$lang->guide_set_comment_editor_toolbar = 'Comment editor toolbar'; $lang->guide_additional_css = 'Additional CSS Files'; $lang->about_additional_css = 'To load additional CSS files inside the editor, such as web fonts, please enter one URL per line.'; $lang->guide_additional_mobile_css = 'Additional CSS Files for Mobile'; @@ -26,7 +27,12 @@ $lang->word_break_normal = 'Wrap Asian scripts at character boundary and Latin s $lang->word_break_keep_all = 'Wrap at word boundary'; $lang->word_break_break_all = 'Wrap at character boundary'; $lang->word_break_none = 'Do not wrap long lines'; -$lang->guide_choose_autoinsert_image = 'Auto-insert images'; +$lang->guide_choose_autoinsert_types = 'Auto-insert'; +$lang->autoinsert_types['image'] = 'Images'; +$lang->autoinsert_types['audio'] = 'Audio'; +$lang->autoinsert_types['video'] = 'Video'; +$lang->autoinsert_paragraph = 'New paragraph'; +$lang->autoinsert_inline = 'Inline'; $lang->autoinsert_image_paragraph = 'Automatically insert images into editor (after line break at cursor position)'; $lang->autoinsert_image_inline = 'Automatically insert images into editor (directly at cursor position)'; $lang->autoinsert_image_none = 'Do not auto-insert images into editor'; @@ -51,12 +57,12 @@ $lang->component_history = 'Updates'; $lang->component_description = 'Description'; $lang->component_extra_vars = 'Option Variable'; $lang->component_grant = 'Permission Setting'; -$lang->content_style = 'Content Style'; $lang->content_font = 'Content Font'; $lang->content_font_size = 'Content Font Size'; -$lang->about_component = 'About component'; -$lang->about_component_mid = 'Editor components can select targets.(All targets will be selected when nothing is selected.)'; +$lang->about_component = 'About Component'; +$lang->about_component_mid = 'Editor components can select targets. (All targets will be selected when nothing is selected.)'; $lang->msg_component_is_not_founded = 'Cannot find editor component %s.'; +$lang->msg_component_is_disabled = 'Editor component %s is disabled.'; $lang->msg_component_is_inserted = 'Selected component is already inserted.'; $lang->msg_component_is_first_order = 'Selected component is located at the first position.'; $lang->msg_component_is_last_order = 'Selected component is located at the last position.'; @@ -65,20 +71,22 @@ $lang->msg_auto_saved = 'Automatically Saved.'; $lang->cmd_disable = 'Inactive'; $lang->cmd_enable = 'Active'; $lang->cmd_select_cover = 'Be a cover image'; -$lang->default_editor_settings = 'Default settings for this module'; +$lang->default_editor_settings = 'Use Default Settings'; $lang->editor_skin = 'Editor Skin'; $lang->upload_file_grant = 'Permission to upload files'; $lang->enable_default_component_grant = 'Permission to use default components'; $lang->enable_extra_component_grant = 'Permission to use extra components'; $lang->enable_html_grant = 'Permission to edit HTML'; $lang->enable_autosave = 'Enable Auto-Save'; -$lang->allow_html = 'allow HTML'; +$lang->editor_auto_dark_mode = 'Automatic dark mode'; +$lang->allow_html = 'Allow HTML'; $lang->height_resizable = 'Height Resizable'; $lang->editor_height = 'Height of Editor'; -$lang->about_default_editor_settings = 'Follow editor settings of Rhymix Admin page through whole site.'; +$lang->about_default_editor_settings = 'Follow the default settings from the Editor module.'; $lang->about_content_font = 'Please use comma for multiple input.'; $lang->about_content_font_size = 'Please input units such as px or em.'; $lang->about_enable_autosave = 'You may decide whether the auto-save function will be used.'; +$lang->about_editor_auto_dark_mode = 'Automatically switch to a dark skin if dark mode is used.'; $lang->edit['fontname'] = 'Font'; $lang->edit['fontsize'] = 'Size'; $lang->edit['use_paragraph'] = 'Paragraph Function'; diff --git a/modules/editor/lang/ja.php b/modules/editor/lang/ja.php index a8ed3a819..15eddcd97 100644 --- a/modules/editor/lang/ja.php +++ b/modules/editor/lang/ja.php @@ -28,7 +28,6 @@ $lang->component_history = '更新履歴'; $lang->component_description = '説明'; $lang->component_extra_vars = '設定変数'; $lang->component_grant = '権限設定'; -$lang->content_style = 'コンテンツスタイル'; $lang->content_font = 'コンテンツフォント'; $lang->content_font_size = 'コンテンツフォントサイズ'; $lang->about_component = 'コンポーネント情報'; diff --git a/modules/editor/lang/ko.php b/modules/editor/lang/ko.php index 2e80a7ebb..409ccb0bc 100644 --- a/modules/editor/lang/ko.php +++ b/modules/editor/lang/ko.php @@ -3,12 +3,9 @@ $lang->editor_now = '현재 설정 상태'; $lang->editor_component = '에디터 컴포넌트'; $lang->main_editor = '본문 에디터'; $lang->comment_editor = '댓글 에디터'; -$lang->guide_choose_main_editor = '본문 에디터'; -$lang->guide_set_height_main_editor = '본문 에디터 높이'; -$lang->guide_set_main_editor_toolbar = '본문 에디터 도구상자'; -$lang->guide_choose_comment_editor = '댓글 에디터'; -$lang->guide_set_height_comment_editor = '댓글 에디터 높이'; -$lang->guide_set_comment_editor_toolbar = '댓글 에디터 도구상자'; +$lang->guide_editor_skin = '에디터 스킨'; +$lang->guide_editor_height = '높이'; +$lang->guide_editor_toolbar = '도구상자'; $lang->editor_toolbar_default = '기본'; $lang->editor_toolbar_simple = '간단'; $lang->editor_toolbar_hide = '숨김'; @@ -30,10 +27,12 @@ $lang->word_break_normal = '한글은 글자 단위로 줄바꿈, 영문은 단 $lang->word_break_keep_all = '모든 언어를 단어 단위로 줄바꿈'; $lang->word_break_break_all = '모든 언어를 글자 단위로 줄바꿈'; $lang->word_break_none = '줄을 바꾸지 않음'; -$lang->guide_choose_autoinsert_image = '이미지 자동 삽입'; -$lang->autoinsert_image_paragraph = '이미지 첨부시 본문에 자동 삽입 (커서 위치에서 줄을 바꾸고 삽입)'; -$lang->autoinsert_image_inline = '이미지 첨부시 본문에 자동 삽입 (커서 위치에 직접 삽입)'; -$lang->autoinsert_image_none = '이미지 첨부시 본문에 자동 삽입하지 않음'; +$lang->guide_choose_autoinsert_types = '본문 자동 삽입'; +$lang->autoinsert_types['image'] = '이미지'; +$lang->autoinsert_types['audio'] = '오디오'; +$lang->autoinsert_types['video'] = '동영상'; +$lang->autoinsert_paragraph = '커서 위치에서 줄을 바꾸어 삽입'; +$lang->autoinsert_inline = '커서 위치에 직접 삽입'; $lang->about_additional_plugins = '추가 플러그인 로딩을 지원하는 에디터에서만 적용됩니다.'; $lang->about_remove_plugins = '플러그인 제거를 지원하는 에디터에서만 적용됩니다.'; $lang->about_unit_default_px = '단위를 지정하지 않을 경우 px 단위를 사용합니다.'; @@ -55,12 +54,12 @@ $lang->component_history = '변경 이력'; $lang->component_description = '설명'; $lang->component_extra_vars = '설정 변수'; $lang->component_grant = '권한설정'; -$lang->content_style = '문서 서식'; $lang->content_font = '문서 폰트'; $lang->content_font_size = '문서 폰트 크기'; $lang->about_component = '컴포넌트 소개'; $lang->about_component_mid = '에디터 컴포넌트가 사용될 대상을 지정할 수 있습니다.(모두 해제 시 모든 대상에서 사용 가능합니다)'; $lang->msg_component_is_not_founded = '%s 에디터 컴포넌트를 찾을 수 없습니다.'; +$lang->msg_component_is_disabled = '%s 에디터 컴포넌트는 사용하지 않도록 설정되어 있습니다.'; $lang->msg_component_is_inserted = '선택한 컴포넌트는 이미 입력되어 있습니다.'; $lang->msg_component_is_first_order = '선택한 컴포넌트는 첫 번째에 위치하고 있습니다.'; $lang->msg_component_is_last_order = '선택한 컴포넌트는 마지막에 위치하고 있습니다.'; @@ -76,13 +75,15 @@ $lang->enable_default_component_grant = '기본 컴포넌트 사용 권한'; $lang->enable_extra_component_grant = '확장 컴포넌트 사용 권한'; $lang->enable_html_grant = 'HTML 편집 권한'; $lang->enable_autosave = '자동저장 사용'; +$lang->editor_auto_dark_mode = '다크모드 자동 감지'; $lang->allow_html = 'HTML 허용'; $lang->height_resizable = '높이 조절 가능'; $lang->editor_height = '에디터 높이'; -$lang->about_default_editor_settings = '사이트 전체 에디터 설정을 통일하여서 모듈별 에디터 설정을 단순하게 합니다.'; +$lang->about_default_editor_settings = '에디터 모듈의 기본 설정을 따릅니다.'; $lang->about_content_font = '콤마(,)로 여러 폰트를 지정할 수 있습니다.'; $lang->about_content_font_size = '12px, 1em등 단위까지 포함해서 입력해주세요.'; $lang->about_enable_autosave = '글 작성 시 자동 저장 기능을 활성화 시킬 수 있습니다.'; +$lang->about_editor_auto_dark_mode = '다크모드 사용시 자동으로 어두운 스킨을 사용합니다.'; $lang->edit['fontname'] = '글꼴'; $lang->edit['fontsize'] = '크기'; $lang->edit['use_paragraph'] = '문단기능'; diff --git a/modules/editor/lang/tr.php b/modules/editor/lang/tr.php index 9cafab0fd..eb27495e0 100644 --- a/modules/editor/lang/tr.php +++ b/modules/editor/lang/tr.php @@ -8,7 +8,6 @@ $lang->component_history = 'Güncelleştirmeler'; $lang->component_description = 'Açıklama'; $lang->component_extra_vars = 'Değişken Seçenek'; $lang->component_grant = 'Yetki Ayarı'; -$lang->content_style = 'İçerik Tarzı'; $lang->content_font = 'İçerik Yazı Tipi'; $lang->content_font_size = 'İçerik Yazı Boyutu'; $lang->about_component = 'Bileşen hakkında'; diff --git a/modules/editor/lang/vi.php b/modules/editor/lang/vi.php index 468c08e81..abf7a2eca 100644 --- a/modules/editor/lang/vi.php +++ b/modules/editor/lang/vi.php @@ -9,7 +9,6 @@ $lang->component_history = 'Cập nhật'; $lang->component_description = 'Mô tả'; $lang->component_extra_vars = 'Thông tin bổ xung'; $lang->component_grant = 'Thiết lập quyền'; -$lang->content_style = 'Kiểu dáng của nội dung'; $lang->content_font = 'Font chữ của nội dung'; $lang->content_font_size = 'Cỡ chữ của nội dung'; $lang->about_component = 'Thông tin Thành phần'; @@ -94,6 +93,7 @@ $lang->edit['emoticon'] = 'Diễn tả cảm xúc'; $lang->edit['upload'] = 'Đính kèm'; $lang->edit['upload_file'] = 'Đính kèm'; $lang->edit['link_file'] = 'Chèn vào bài viết'; +$lang->edit['delete_selected'] = 'Xóa'; $lang->edit['icon_align_article'] = 'Vị trí trong bài viết'; $lang->edit['icon_align_left'] = 'Trái'; $lang->edit['icon_align_middle'] = 'Giữa'; diff --git a/modules/editor/lang/zh-CN.php b/modules/editor/lang/zh-CN.php index 495259d40..915435f63 100644 --- a/modules/editor/lang/zh-CN.php +++ b/modules/editor/lang/zh-CN.php @@ -10,7 +10,6 @@ $lang->component_history = '更新日志'; $lang->component_description = '说明'; $lang->component_extra_vars = '变数设置'; $lang->component_grant = '权限设置'; -$lang->content_style = '文档样式'; $lang->content_font = '文档字体'; $lang->content_font_size = '字体大小'; $lang->about_component = '组件简介'; @@ -182,4 +181,5 @@ $lang->edit['close_help'] = '关闭帮助'; $lang->edit['confirm_submit_without_saving'] = '尚有未保存的段落。\\n确定要提交吗?'; $lang->edit['image_align'] = '图片对齐'; $lang->edit['attached_files'] = '附件'; +$lang->edit['delete_selected'] = '删除附件'; $lang->about_dblclick_in_editor = '双击背景, 文本, 图片, 引用即可对其相关组件进行详细设置。'; diff --git a/modules/editor/lang/zh-TW.php b/modules/editor/lang/zh-TW.php index 1c204096f..062c4912d 100644 --- a/modules/editor/lang/zh-TW.php +++ b/modules/editor/lang/zh-TW.php @@ -10,7 +10,6 @@ $lang->component_history = '更新紀錄'; $lang->component_description = '說明'; $lang->component_extra_vars = '變數設置'; $lang->component_grant = '權限設置'; -$lang->content_style = '內容樣式'; $lang->content_font = '內容字體'; $lang->content_font_size = '字體大小'; $lang->about_component = '組件簡介'; diff --git a/modules/editor/schemas/editor_autosave.xml b/modules/editor/schemas/editor_autosave.xml index 267455e87..3b8b794f7 100644 --- a/modules/editor/schemas/editor_autosave.xml +++ b/modules/editor/schemas/editor_autosave.xml @@ -1,8 +1,8 @@ - - - - + + + + diff --git a/modules/editor/skins/ckeditor/config.blade.php b/modules/editor/skins/ckeditor/config.blade.php new file mode 100644 index 000000000..402a7dc18 --- /dev/null +++ b/modules/editor/skins/ckeditor/config.blade.php @@ -0,0 +1,86 @@ +@php + +// Basic configuration +$ckconfig = new \stdClass; +$ckconfig->skin = $colorset; +$ckconfig->auto_dark_mode = $editor_auto_dark_mode ?? false; +$ckconfig->legacy_html_mode = $html_mode ?? false; +$ckconfig->language = str_replace('jp', 'ja', Context::getLangType()); +$ckconfig->height = $editor_height ?? 100; +$ckconfig->toolbar = $editor_toolbar ?? 'default'; +$ckconfig->hide_toolbar = $editor_toolbar_hide ?? false; +$ckconfig->focus = $editor_focus ?? false; +$ckconfig->ios_patch = (bool)preg_match('/i(Phone|Pad|Pod)/', $_SERVER['HTTP_USER_AGENT'] ?? ''); +$ckconfig->allow_upload = $allow_fileupload ?? false; + +// Plugin configuration +$ckconfig->add_plugins = $editor_additional_plugins ?: []; +$ckconfig->remove_plugins = $editor_remove_plugins ?: []; +if (!in_array('clipboard', $ckconfig->remove_plugins)) { + $ckconfig->add_plugins[] = 'rx_paste'; +} +if ($ckconfig->ios_patch) { + $ckconfig->add_plugins[] = 'divarea'; + $ckconfig->add_plugins[] = 'ios_enterkey'; + $ckconfig->remove_plugins[] = 'enterkey'; +} + +// Font configuration +$ckconfig->default_font = $content_font ?: 'none'; +$ckconfig->default_font_size = intval(preg_replace('/\D/', '', $content_font_size ?? '13'), 10); +$ckconfig->fonts = array_values(array_map('strval', $lang->edit->fontlist ?: [])); +$ckconfig->font_sizes = [8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 24, 28, 32, 36, 40, 48]; +if (!in_array($ckconfig->default_font, $ckconfig->fonts) && $ckconfig->default_font !== 'none') { + array_unshift($ckconfig->fonts, $ckconfig->default_font); +} +if (!in_array($ckconfig->default_font_size, $ckconfig->font_sizes)) { + $ckconfig->font_sizes[] = $ckconfig->default_font_size; + sort($ckconfig->font_sizes); +} +foreach ($ckconfig->fonts as &$_font_name) { + $_font_name = trim(array_first(explode(',', $_font_name, 2))) . '/' . $_font_name; +} +foreach ($ckconfig->font_sizes as &$_font_size) { + $_font_size = $_font_size . '/' . $_font_size . 'px'; +} + +// CSS configuration +$ckconfig->css_files = array_values($editor_additional_css ?: []); +$ckconfig->css_content = ''; +$ckconfig->css_vars = (object)[ + 'colorset' => $colorset, + 'content_font' => $content_font ?: 'none', + 'content_font_size' => $content_font_size ?: '13', + 'content_line_height' => $content_line_height ?: 'none', + 'content_word_break' => $content_word_break ?: 'none', + 'content_paragraph_spacing' => $content_paragraph_spacing ?: 'none', +]; + +// Legacy editor component configuration +$ckconfig->enable_component = $enable_component ?? false; +$ckconfig->enable_default_component = $enable_default_component ?? false; +$ckconfig->components = []; +foreach ($component_list ?? [] as $component_name => $component) { + $ckconfig->components[$component_name] = escape($component->title, false); +} + +// Cache-busting timestamp +$ckconfig->custom_config_exists = file_exists(RX_BASEDIR . 'common/js/plugins/ckeditor/ckeditor/config.js'); +$_filemtime1 = filemtime(RX_BASEDIR . 'common/js/plugins/ckeditor/ckeditor/ckeditor.js'); +$_filemtime2 = $ckconfig->custom_config_exists ? filemtime(RX_BASEDIR . 'common/js/plugins/ckeditor/ckeditor/config.js') : 0; +$ckconfig->timestamp = max($_filemtime1, $_filemtime2, $ckconfig_timestamp ?? 0); + +// Set initial min-height to prevent layout shift when editor is loaded. +if ($editor_toolbar_hide) { + $ckconfig->initial_height = $editor_height + 55; +} elseif ($editor_toolbar === 'simple') { + $ckconfig->initial_height = $editor_height + 71; +} else { + $ckconfig->initial_height = $editor_height + 137; +} + +if (str_contains($_SERVER['HTTP_USER_AGENT'] ?? '', 'Firefox/')) { + $ckconfig->initial_height += 2; +} + +@endphp diff --git a/modules/editor/skins/ckeditor/css/ckeditor.scss b/modules/editor/skins/ckeditor/css/ckeditor.scss new file mode 100644 index 000000000..7a19aca34 --- /dev/null +++ b/modules/editor/skins/ckeditor/css/ckeditor.scss @@ -0,0 +1,188 @@ +@charset "UTF-8"; + +@mixin light-dark($colorset) { + @if $colorset == "moono-dark" { + background-color: #333; + color: #fff; + } @else { + background-color: #fff; + color: #000; + &.color_scheme_dark.cke_auto_dark_mode { + background-color: #333; + color: #fff; + } + } +} + +@mixin light-dark-top($colorset) { + @if $colorset == "moono-dark" { + body & { + border-color: #555; + &.cke_bottom { + background: #333; + .cke_button_icon, .cke_combo_button, .cke_button_arrow, .cke_button_label { + filter: invert(1); + } + } + } + } @else { + .color_scheme_dark.cke_auto_dark_mode & { + background: #333; + border-color: #555; + .cke_button_on { + background: #000; + border-color: #555; + } + .cke_button_icon, .cke_combo_button, .cke_button_arrow, .cke_button_label { + filter: invert(1); + } + &.cke_dialog_body { + background: #555; + } + .cke_dialog_title { + background: #333; + color: #fff; + border-bottom-color: #555; + .cke_dialog_close_button { + color: #fff; + } + } + .cke_dialog_tabs { + background: #555; + .cke_dialog_tab { + background: #555; + color: #999; + border-color: #777; + } + .cke_dialog_tab_selected { + background: #777; + color: #fff; + border-color: #777; + } + } + .cke_dialog_contents { + background: #555; + border-top-color: #777; + label { + color: #fff; + } + select, input, .ImagePreviewBox td { + background: #333; + color: #fff; + border-color: #777; + } + } + .cke_dialog_footer { + background: #333; + border-top-color: #555; + } + } + } +} + +// Editor UI styling +html { + &.cke_panel_container, &.cke_panel_container body { + @include light-dark("default"); + } + width: unset; + height: unset; + min-width: unset; + min-height: 100vh; + box-sizing: border-box; + margin: 0; + padding: 0; +} +body.cke_editable { + width: unset; + height: unset; + min-width: unset; + min-height: 100vh; + box-sizing: border-box; + margin: 0; + padding: 10px; + @include light-dark($colorset); +} +textarea.cke_source.cke_editable { + box-sizing: border-box; + padding: 10px; +} +body.color_scheme_dark.cke_auto_dark_mode .cke_wysiwyg_div { + background-color: #333; + color: #fff; +} + +p.editor_autosaved_message.autosave_message { + display:none; + background: #e0e0e0; + color: #000; + padding: 10px; + margin:0; + line-height:1.2; + .color_scheme_dark & { + background: #222; + color: #fff; + } +} +.cke_reset { + .cke_top, .cke_bottom { + @include light-dark-top($colorset); + a { + &:hover, &:focus { + transition: none; + } + } + } + .cke_combo__fontsize { + .cke_combo_button { + width: 64px; + } + .cke_combo_text { + width: 30px; + } + } +} +.cke_dialog_body { + @include light-dark-top($colorset); +} + +// Content area styling +.xe_content.editable, .rhymix_content.editable, .cke_wysiwyg_div { + font-family: $content_font; + font-size: $content_font_size; + line-height: $content_line_height; + @if $content_word_break == 'none' { + white-space: nowrap; + } @else { + word-break: $content_word_break; + word-wrap: break-word; + } + p { + margin: 0 0 $content_paragraph_spacing 0; + line-height: $content_line_height; + span { + line-height: $content_line_height; + } + } + img, video { + max-width: 100%; + height: auto; + } + blockquote { + padding: 2px 0; + border-style: solid; + border-color: #ccc; + border-width: 0; + border-left-width: 5px; + padding-left: 20px; + padding-right: 8px; + + &:lang(ar), &:lang(arc), &:lang(dv), &:lang(ha), &:lang(he), &:lang(khw), &:lang(ks), &:lang(ku), &:lang(ps), &:lang(fa), &:lang(ur), &:lang(yi) { + border-left-width: 0px; + border-right-width: 5px; + padding-left: 8px; + padding-right: 20px; + + } + } +} diff --git a/modules/editor/skins/ckeditor/css/default.css b/modules/editor/skins/ckeditor/css/default.css deleted file mode 100644 index a89efd8b3..000000000 --- a/modules/editor/skins/ckeditor/css/default.css +++ /dev/null @@ -1,3 +0,0 @@ -p.editor_autosaved_message.autosave_message {display:none;background: #f6ffdb;padding:6px 10px;margin:0;line-height:1;} -span.cke_combo__fontsize .cke_combo_button { width: 64px; } -span.cke_combo__fontsize .cke_combo_text { width: 30px; } diff --git a/modules/editor/skins/ckeditor/editor.html b/modules/editor/skins/ckeditor/editor.html index 8ec1f0878..909b6a3e8 100644 --- a/modules/editor/skins/ckeditor/editor.html +++ b/modules/editor/skins/ckeditor/editor.html @@ -1,190 +1,36 @@ - - - + - + + + + + + + + - + - - -{@ $css_file_list = array(Context::getRequestUri() . $content_style_path . '/editor.css?' . date("YmdHis", @filemtime($content_style_path."/editor.css")))} - - {@ $css_file_list[] = $additional_css_url} - - -{@ $css_content = null } - - {@ $css_content .= ' .xe_content.editable { '} - {@ $css_content .= 'font-family:' . $content_font . ';';} - {@ $css_content .= 'font-size:' . $content_font_size . ';';} - {@ $css_content .= 'line-height:' . $content_line_height . ';';} - {@ $css_content .= 'white-space: nowrap;';} - {@ $css_content .= 'word-break:' . ($content_word_break ?: 'normal') . '; word-wrap: break-word;';} - {@ $css_content .= chr(125);} - {@ $css_content .= '.xe_content.editable p { margin: 0 0 ' . ($content_paragraph_spacing ?: 0) . ' 0;' . chr(125);} - + +
                +
                - - - +

                 

                + + + - + + -{@ $editor_height_fixed = $editor_height + ($editor_toolbar_hide ? 58 : ($editor_toolbar === 'simple' ? 74 : 140))} - -
                - -

                 

                - - - - - - + + + diff --git a/modules/editor/skins/ckeditor/file_upload.html b/modules/editor/skins/ckeditor/file_upload.html index f62561f56..7d808c24f 100644 --- a/modules/editor/skins/ckeditor/file_upload.html +++ b/modules/editor/skins/ckeditor/file_upload.html @@ -1,8 +1,20 @@ + + + -
                + + +
                +
                @@ -26,10 +38,9 @@
                {$lang->ckeditor_file_count} ( / )
                -
                - - + +
                @@ -38,7 +49,6 @@
                -
                @@ -48,29 +58,10 @@
                diff --git a/modules/editor/skins/ckeditor/js/default.js b/modules/editor/skins/ckeditor/js/default.js deleted file mode 100755 index a221fabfa..000000000 --- a/modules/editor/skins/ckeditor/js/default.js +++ /dev/null @@ -1,41 +0,0 @@ -//Insert uploaded file to editor -function ckInsertUploadedFile(editorSequence){ - var temp_code=''; - - var settings = uploaderSettings[editorSequence]; - var fileListAreaID = settings["fileListAreaID"]; - var fileListObj = get_by_id(fileListAreaID); - if(!fileListObj) return; - - if(editorMode[editorSequence]=='preview') return; - - for(var i=0;i"; - } - - } else { - temp_code=""+file.source_filename+"\n"; - } - } - cked_instance = 'ckeditor_instance_'+editorSequence; - CKEDITOR.instances[cked_instance].insertHtml(temp_code); -} diff --git a/modules/editor/skins/ckeditor/js/default.min.js b/modules/editor/skins/ckeditor/js/default.min.js deleted file mode 100644 index 52fd46a34..000000000 --- a/modules/editor/skins/ckeditor/js/default.min.js +++ /dev/null @@ -1 +0,0 @@ -This file is not used in Rhymix. diff --git a/modules/editor/skins/ckeditor/js/editor.js b/modules/editor/skins/ckeditor/js/editor.js new file mode 100644 index 000000000..261299067 --- /dev/null +++ b/modules/editor/skins/ckeditor/js/editor.js @@ -0,0 +1,228 @@ +'use strict'; + +/** + * Initialize each instance of CKEditor on the page. + */ +$(function() { + $('.rx_ckeditor').each(function() { + + // Load editor configuration. + const container = $(this); + const form = container.closest('form'); + const editor_sequence = parseInt(container.data('editorSequence'), 10); + const config = container.data('editorConfig'); + + // Apply auto dark mode. + if (config.auto_dark_mode) { + $('body').addClass('cke_auto_dark_mode'); + if (getColorScheme() === 'dark') { + if (config.skin !== 'moono-lisa') { + config.skin = 'moono-dark'; + } + } + } + + // If the default font is not set, use the browser default font. + if (config.default_font === 'none' && window.getComputedStyle) { + let test_content = $('
                ').hide().appendTo($(document.body)); + let test_styles = window.getComputedStyle(test_content[0], null); + if (test_styles && test_styles.getPropertyValue) { + let default_font = test_styles.getPropertyValue('font-family'); + if (default_font) { + config.default_font = $.trim(default_font.split(',')[0].replace(/['"]/g, '')); + config.css_content = '.rhymix_content.editable { font-family:' + default_font + '; } ' + config.css_content; + } + } + } + + // Define the initial structure for CKEditor settings. + const settings = { + ckeconfig: { + height: config.height, + skin: config.skin, + contentsCss: config.css_files, + font_defaultLabel: config.default_font, + font_names: config.fonts.join(';'), + fontSize_defaultLabel: config.default_font_size, + fontSize_sizes: config.font_sizes.join(';'), + toolbarStartupExpanded: !config.hide_toolbar, + toolbarCanCollapse: true, + allowedContent: true, + startupFocus: config.focus, + language: config.language, + iframe_attributes: {}, + versionCheck: false, + rx_allow_upload: config.allow_upload, + xe_editor_sequence: editor_sequence, + }, + loadXeComponent: true, + enableToolbar: true + }; + + // Add stylesheets from the current document. + $('link[rel=stylesheet]').each(function() { + settings.ckeconfig.contentsCss.push($(this).attr('href')); + }); + + // Add and remove plugins. + if (config.add_plugins) { + settings.ckeconfig.extraPlugins = config.add_plugins.join(','); + } + if (config.remove_plugins) { + settings.ckeconfig.removePlugins = config.remove_plugins.join(','); + } + + // Add editor components. + if (config.enable_component) { + settings.ckeconfig.xe_component_arrays = config.components; + } else { + settings.ckeconfig.xe_component_arrays = {}; + settings.loadXeComponent = false; + } + + if (!config.enable_default_component) { + settings.enableToolbar = false; + settings.ckeconfig.toolbarCanCollapse = false; + } + + // Patch for iOS: https://github.com/rhymix/rhymix/issues/932 + if (config.ios_patch) { + $('head').append('' + ); + } + + // Define the simple toolbar. + if (config.toolbar === 'simple') { + settings.ckeconfig.toolbar = [ + { name: 'styles', items: [ 'Font', 'FontSize', '-', 'Bold', 'Italic', 'Underline', 'Strike', 'TextColor', 'BGColor' ] }, + { name: 'paragraph', items: [ 'JustifyLeft', 'JustifyCenter', 'JustifyRight' ] }, + { name: 'clipboard', items: [ 'Cut', 'Copy', 'Paste' ] }, + { name: 'insert', items: [ 'Link', 'Image', 'Table', 'poll_maker' ] }, + { name: 'tools', items: [ 'Maximize', '-', 'Source' ] } + ]; + } + + // Support legacy HTML (full editing) mode. + if (!config.legacy_html_mode) { + settings.ckeconfig.removeButtons = 'Save,Preview,Print,Cut,Copy,Paste,Source'; + } + + // Disable loading of custom configuration if config.js does not exist. + if (!config.custom_config_exists) { + CKEDITOR.config.customConfig = ''; + } + + // Prevent removal of icon fonts and Google code. + CKEDITOR.dtd.$removeEmpty.i = 0; + CKEDITOR.dtd.$removeEmpty.ins = 0; + + // Set the cache-busting timestamp for plugins. + CKEDITOR.timestamp = config.timestamp; + + // Set the custom CSS content. + CKEDITOR.addCss(config.css_content); + + // Initialize the CKEditor XE app. + const ckeApp = container.XeCkEditor(settings); + + // Add use_editor and use_html fields to the parent form. + const use_editor = form.find('input[name=use_editor]'); + const use_html = form.find('input[name=use_html]'); + if (use_editor.length) { + use_editor.val('Y'); + } else { + form.append(''); + } + if (use_html.length) { + use_html.val('Y'); + } else { + form.append(''); + } + + }); +}); + +/** + * This function is only retained for backward compatibility. + * Do not depend on it for any reason. + */ +function ckInsertUploadedFile() { + if (typeof console == "object" && typeof console.warn == "function") { + const msg = "DEPRECATED : ckInsertUploadedFile() is obsolete in Rhymix."; + if (navigator.userAgent.match(/Firefox/)) { + console.error(msg); + } else { + console.warn(msg); + } + } +} + +/** + * Legacy function to get iframe content and insert it into CKEditor. + */ +function editorReplaceHTML(iframe_obj, content) { + if (typeof console == "object" && typeof console.warn == "function") { + const msg = "DEPRECATED : editorReplaceHTML() is deprecated in Rhymix."; + if (navigator.userAgent.match(/Firefox/)) { + console.error(msg); + } else { + console.warn(msg); + } + } + var editor_sequence = parseInt(iframe_obj.id.replace(/^.*_/, ''), 10); + _getCkeInstance(editor_sequence).insertHtml(content, 'unfiltered_html'); +} + +/** + * Legacy function to get a direct reference to the CKEditor container element. + */ +function editorGetIFrame(editor_sequence) { + if (typeof console == "object" && typeof console.warn == "function") { + const msg = "DEPRECATED : editorGetIFrame() is deprecated in Rhymix."; + if (navigator.userAgent.match(/Firefox/)) { + console.error(msg); + } else { + console.warn(msg); + } + } + return $('#ckeditor_instance_' + editor_sequence).get(0); +} + +/** + * Legacy function to get an instance of CKEditor. + */ +function _getCkeInstance(editor_sequence) { + return $('#ckeditor_instance_' + editor_sequence).data('cke_instance'); +} + +/** + * Legacy function to get the container element for CKEditor. + */ +function _getCkeContainer(editor_sequence) { + return $('#ckeditor_instance_' + editor_sequence); +} + +/** + * Legacy function to get HTML content from CKEditor. + */ +function editorGetContent(editor_sequence) { + return _getCkeInstance(editor_sequence).getData(); +} + +/** + * Legacy function to get text content from CKEditor. + */ +function editorGetContentTextarea_xe(editor_sequence) { + return _getCkeInstance(editor_sequence).getText(); +} + +/** + * Legacy function to get currently selected text from CKEditor. + */ +function editorGetSelectedHtml(editor_sequence) { + return _getCkeInstance(editor_sequence).getSelection().getSelectedText(); +} diff --git a/modules/editor/skins/ckeditor/js/file_upload.js b/modules/editor/skins/ckeditor/js/file_upload.js new file mode 100644 index 000000000..f21023af9 --- /dev/null +++ b/modules/editor/skins/ckeditor/js/file_upload.js @@ -0,0 +1,29 @@ +'use strict'; + +/** + * Initialize each instance of file uploader on the page. + */ +$(function() { + $('.xefu-container').each(function() { + const container = $(this); + const data = container.data(); + container.data('instance', container.xeUploader({ + maxFileSize: parseInt(data.maxFileSize, 10), + maxChunkSize: parseInt(data.maxChunkSize, 10), + autoinsertTypes: data.autoinsertTypes, + autoinsertPosition: data.autoinsertPosition, + singleFileUploads: true + })); + }); +}); + +/** + * This function is only retained for backward compatibility. + * Do not depend on it for any reason. + */ +function reloadUploader(editor_sequence) { + var container = $('#xefu-container-' + editor_sequence); + if (container.length) { + container.data('instance').loadFilelist(container); + } +} diff --git a/modules/editor/skins/ckeditor/js/xe_interface.js b/modules/editor/skins/ckeditor/js/xe_interface.js deleted file mode 100755 index 2639e7ee4..000000000 --- a/modules/editor/skins/ckeditor/js/xe_interface.js +++ /dev/null @@ -1,29 +0,0 @@ -function _getCkeInstance(editor_sequence) { - var $editor_area = jQuery("#ckeditor_instance_"+editor_sequence); - return $editor_area.data('cke_instance'); -} - -//Get content from editor -function editorGetContentTextarea_xe(editor_sequence) { - return _getCkeInstance(editor_sequence).getText(); -} - - -function editorGetSelectedHtml(editor_sequence) { - return _getCkeInstance(editor_sequence).getSelection().getSelectedText(); -} - -function editorGetContent(editor_sequence) { - return _getCkeInstance(editor_sequence).getData(); -} - -//Replace html content to editor -function editorReplaceHTML(iframe_obj, content) { - var editor_sequence = parseInt(iframe_obj.id.replace(/^.*_/, ''), 10); - - _getCkeInstance(editor_sequence).insertHtml(content, "unfiltered_html"); -} - -function editorGetIFrame(editor_sequence) { - return jQuery('#ckeditor_instance_' + editor_sequence).get(0); -} diff --git a/modules/editor/skins/ckeditor/js/xe_interface.min.js b/modules/editor/skins/ckeditor/js/xe_interface.min.js deleted file mode 100644 index 52fd46a34..000000000 --- a/modules/editor/skins/ckeditor/js/xe_interface.min.js +++ /dev/null @@ -1 +0,0 @@ -This file is not used in Rhymix. diff --git a/modules/editor/skins/ckeditor/js/xe_textarea.js b/modules/editor/skins/ckeditor/js/xe_textarea.js index 92eb0bf8b..5898d2ae0 100755 --- a/modules/editor/skins/ckeditor/js/xe_textarea.js +++ b/modules/editor/skins/ckeditor/js/xe_textarea.js @@ -1,6 +1,6 @@ function editorStartTextarea(editor_sequence, content_key, primary_key) { - var obj = xGetElementById('editor_'+editor_sequence); - var use_html = xGetElementById('htm_'+editor_sequence).value; + var obj = document.getElementById('editor_'+editor_sequence); + var use_html = document.getElementById('htm_'+editor_sequence).value; obj.form.setAttribute('editor_sequence', editor_sequence); obj.style.width = '100%'; @@ -24,8 +24,8 @@ function editorStartTextarea(editor_sequence, content_key, primary_key) { } function editorGetContentTextarea(editor_sequence) { - var obj = xGetElementById('editor_'+editor_sequence); - var use_html = xGetElementById('htm_'+editor_sequence).value; + var obj = document.getElementById('editor_'+editor_sequence); + var use_html = document.getElementById('htm_'+editor_sequence).value; var content = obj.value.trim(); if(use_html) { if(use_html!='br') { diff --git a/modules/editor/skins/ckeditor/js/xe_textarea.min.js b/modules/editor/skins/ckeditor/js/xe_textarea.min.js deleted file mode 100644 index 52fd46a34..000000000 --- a/modules/editor/skins/ckeditor/js/xe_textarea.min.js +++ /dev/null @@ -1 +0,0 @@ -This file is not used in Rhymix. diff --git a/modules/editor/skins/ckeditor/skin.xml b/modules/editor/skins/ckeditor/skin.xml index 210944e74..d928338ed 100644 --- a/modules/editor/skins/ckeditor/skin.xml +++ b/modules/editor/skins/ckeditor/skin.xml @@ -1,6 +1,6 @@ - CKEditor 스킨 + CKEditor CKEditor 1.0.0 2015-02-24 diff --git a/modules/editor/skins/simpleeditor/css/simpleeditor.scss b/modules/editor/skins/simpleeditor/css/simpleeditor.scss new file mode 100644 index 000000000..c0c30f871 --- /dev/null +++ b/modules/editor/skins/simpleeditor/css/simpleeditor.scss @@ -0,0 +1,29 @@ +.rx_simpleeditor { + width: 100%; + min-height: 60px; + box-sizing: border-box; + outline: 0 solid transparent; + resize: vertical; + overflow-y: auto; + &.light { + border: 1px solid #c4c4c4; + background: #fff; + padding: 10px; + } + &.dark, .color_scheme_dark & { + border-color: #111; + background: #333; + color: #fff; + } + iframe { + max-width: 100%; + padding: 5px 0; + } + img { + max-width: 50%; + padding: 5px 0; + } + img.thumbnail { + max-width: 160px; + } +} diff --git a/modules/editor/skins/simpleeditor/editor.html b/modules/editor/skins/simpleeditor/editor.html new file mode 100644 index 000000000..040feb493 --- /dev/null +++ b/modules/editor/skins/simpleeditor/editor.html @@ -0,0 +1,16 @@ + + + + + +
                +
                +
                + + + +
                diff --git a/modules/editor/skins/simpleeditor/js/interface.js b/modules/editor/skins/simpleeditor/js/interface.js new file mode 100644 index 000000000..754eb5934 --- /dev/null +++ b/modules/editor/skins/simpleeditor/js/interface.js @@ -0,0 +1,16 @@ +function _getSimpleEditorInstance(editor_sequence) { + return jQuery('#simpleeditor_instance_' + editor_sequence); +} + +function editorGetContent(editor_sequence) { + return _getSimpleEditorInstance(editor_sequence).html().escape(); +} + +function editorReplaceHTML(iframe_obj, content) { + var editor_sequence = parseInt(iframe_obj.id.replace(/^.*_/, ''), 10); + _getSimpleEditorInstance(editor_sequence).html(content); +} + +function editorGetIFrame(editor_sequence) { + return _getSimpleEditorInstance(editor_sequence).get(0); +} diff --git a/modules/editor/skins/simpleeditor/js/simpleeditor.js b/modules/editor/skins/simpleeditor/js/simpleeditor.js new file mode 100644 index 000000000..fa5f00d6a --- /dev/null +++ b/modules/editor/skins/simpleeditor/js/simpleeditor.js @@ -0,0 +1,166 @@ +"use strict"; + +(function($) { + + // Save the cursor position. + var ranges = []; + var saveSelection = function() { + var sel = window.getSelection(); + ranges = []; + if (sel.getRangeAt && sel.rangeCount) { + for (let i = 0; i < sel.rangeCount; i++) { + ranges.push(sel.getRangeAt(i)); + } + } + }; + + // Insert content at cursor position. + var insertContent = function(instance, content) { + if (content.match(/<(audio|video)\b[^>]+>(<\/p>)?/)) { + content = content + '


                '; + } + if (ranges.length) { + var range = ranges[0]; + range.collapse(false); + ranges = []; + } else { + var range = document.createRange(); + range.selectNodeContents(instance.get(0)); + range.collapse(false); + } + var sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + if (String(navigator.userAgent).match(/Trident\/7/)) { + range.insertNode(range.createContextualFragment(content)); + range.collapse(false); + } else { + document.execCommand('insertHTML', false, content); + } + }; + + // Simplify HTML content by removing unnecessary tags. + var simplifyContent = function(str) { + str = String(str); + str = str.replace(//gs, ''); + str = str.replace(/<\/?(\?xml|meta|link|font|span|style|script|noscript|frame|noframes|(?:st1|o):[a-z0-9]+)\b[^>]*?>/ig, ''); + str = str.replace(/\b(id|class|style|on(?:[a-z0-9]+)|Mso(?:[a-z0-9]+))="[^"]*"/ig, ''); + str = str.replace(/(<\/?)div(\W)/g, '$1p$2'); + if (!str.match(/<\/?p>/)) { + str = '

                ' + str + '

                '; + } + return str; + }; + + // Convert YouTube links. + var convertYouTube = function(str) { + var regexp = /(src=")?https?:\/\/(www\.youtube(?:-nocookie)?\.com\/(?:watch\?v=|v\/|embed\/)|youtu\.be\/)([a-zA-Z0-9_-]+)\S*/g; + return String(str).replace(regexp, function(match, p1, p2, p3) { + if (p1 === 'src="') { + return match; + } else { + return '

                '; + } + }); + }; + + // Page load event handler. + $(function() { + $('.rx_simpleeditor').each(function() { + + // Load editor info. + var editor = $(this); + var editor_sequence = editor.data('editorSequence'); + var content_key = editor.data('editorContentKeyName'); + var primary_key = editor.data('editorPrimaryKeyName'); + var insert_form = editor.closest('form'); + var content_input = insert_form.find('input,textarea').filter('[name=' + content_key + ']'); + var editor_height = editor.data('editorHeight'); + if (editor_height) { + editor.css('height', editor_height + 'px'); + } + + // Set editor sequence and other info to the form. + insert_form[0].setAttribute('editor_sequence', editor_sequence); + editorRelKeys[editor_sequence] = {}; + editorRelKeys[editor_sequence].primary = insert_form.find("input[name='" + primary_key + "']").get(0); + editorRelKeys[editor_sequence].content = content_input; + editorRelKeys[editor_sequence].func = editorGetContent; + + // Force

                as paragraph separator. + document.execCommand('defaultParagraphSeparator', false, 'p'); + + // Capture some simple keyboard shortcuts. + editor.on('keydown', function(event) { + if (!event.ctrlKey) { + return; + } + var char = String.fromCharCode(event.which).toLowerCase(); + if (char === 'b') { + document.execCommand('bold'); + event.preventDefault(); + } + if (char === 'i') { + document.execCommand('italic'); + event.preventDefault(); + } + if (char === 'u') { + document.execCommand('underline'); + event.preventDefault(); + } + }); + + // Save cursor position on moseup & keyup. + editor.on('mouseup keyup', function() { + saveSelection(); + }); + + // Clean up pasted content. + editor.on('paste', function(event) { + var clipboard_data = (event.clipboardData || window.clipboardData || event.originalEvent.clipboardData); + if (typeof clipboard_data !== 'undefined') { + var content = clipboard_data.getData('text/html'); + if (content === '') { + content = clipboard_data.getData('text'); + } + } else { + return; + } + content = convertYouTube(simplifyContent(content)); + insertContent(editor, content); + event.preventDefault(); + }); + + // Load existing content. + if (content_input.size()) { + editor.html(content_input.val()); + } + + // Copy edited content to the actual input element. + editor.on('input blur mouseup keyup', function() { + var content = simplifyContent(editor.html()); + content_input.val(content); + }); + }); + }); + + // Simulate CKEditor for file upload integration. + window._getCkeContainer = function(editor_sequence) { + return $('#simpleeditor_instance_' + editor_sequence); + }; + window._getCkeInstance = function(editor_sequence) { + var instance = $('#simpleeditor_instance_' + editor_sequence); + return { + getData: function() { + return String(instance.html()); + }, + setData: function(content) { + instance.html(content); + }, + insertHtml: function(content) { + insertContent(instance, content); + } + }; + }; + +})(jQuery); diff --git a/modules/editor/skins/simpleeditor/skin.xml b/modules/editor/skins/simpleeditor/skin.xml new file mode 100644 index 000000000..eca9dd0ad --- /dev/null +++ b/modules/editor/skins/simpleeditor/skin.xml @@ -0,0 +1,15 @@ + + + SimpleEditor + SimpleEditor + 1.0.0 + 2020-07-07 + + + Light + + + Dark + + + diff --git a/modules/editor/skins/textarea/js/textarea.js b/modules/editor/skins/textarea/js/textarea.js index 1c477fa06..a9ca4ff0e 100644 --- a/modules/editor/skins/textarea/js/textarea.js +++ b/modules/editor/skins/textarea/js/textarea.js @@ -1,27 +1,27 @@ function editorTextarea(editor_sequence) { var textarea = jQuery("#textarea_instance_" + editor_sequence); - var content_key = textarea.data("editor-content-key-name"); - var primary_key = textarea.data("editor-primary-key-name"); + var content_key = textarea.data("editorContentKeyName"); + var primary_key = textarea.data("editorPrimaryKeyName"); var insert_form = textarea.closest("form"); var content_input = insert_form.find("input[name='" + content_key + "']"); var content = ""; - + // Set editor keys editorRelKeys[editor_sequence] = {}; editorRelKeys[editor_sequence].primary = insert_form.find("input[name='" + primary_key + "']"); editorRelKeys[editor_sequence].content = content_input; editorRelKeys[editor_sequence].func = editorGetContent; - + // Set editor_sequence insert_form[0].setAttribute('editor_sequence', editor_sequence); - + // Load existing content if (content_input.size()) { content = String(content_input.val()).stripTags(); content_input.val(content); textarea.val(content.unescape()); } - + // Save edited content textarea.on("change", function() { content_input.val(String(jQuery(this).val()).escape()); @@ -41,4 +41,4 @@ function editorTextarea(editor_sequence) { }); editor_resize_iframe.height(textarea.height()); } -} \ No newline at end of file +} diff --git a/modules/editor/styles/ckeditor_light/editor.css b/modules/editor/styles/ckeditor_light/editor.css deleted file mode 100644 index 24ed5f64a..000000000 --- a/modules/editor/styles/ckeditor_light/editor.css +++ /dev/null @@ -1,40 +0,0 @@ -@charset "utf-8"; -/* NAVER (developers@xpressengine.com) */ -.xe_content.editable img{border:0;max-width:100%;} -.xe_content.editable blockquote.q1, -.xe_content.editable blockquote.q2, -.xe_content.editable blockquote.q3, -.xe_content.editable blockquote.q4, -.xe_content.editable blockquote.q5, -.xe_content.editable blockquote.q6, -.xe_content.editable blockquote.q7{padding:10px;margin:0 15px;border:0;} -.xe_content.editable blockquote.q1{padding:0 10px;border-left:2px solid #ccc} -.xe_content.editable blockquote.q2{padding:0 10px;background:url(./img/bg_qmark.gif) no-repeat left top} -.xe_content.editable blockquote.q3{border:1px solid #d9d9d9} -.xe_content.editable blockquote.q4{border:1px solid #d9d9d9;background:#fbfbfb} -.xe_content.editable blockquote.q5{border:2px solid #707070} -.xe_content.editable blockquote.q6{border:1px dashed #707070} -.xe_content.editable blockquote.q7{border:1px dashed #707070;background:#fbfbfb} -.xe_content.editable table .xe_selected_cell{background-color:#d6e9ff} -.xe_content.editable img.cke_iframe{background-color:#444} -.xe_content.editable blockquote -{ - padding: 2px 0; - border-style: solid; - border-color: #ccc; - border-width: 0; -} - -.xe_content.editable.cke_contents_ltr blockquote -{ - padding-left: 20px; - padding-right: 8px; - border-left-width: 5px; -} - -.xe_content.editable.cke_contents_rtl blockquote -{ - padding-left: 8px; - padding-right: 20px; - border-right-width: 5px; -} \ No newline at end of file diff --git a/modules/editor/styles/ckeditor_light/editor.html b/modules/editor/styles/ckeditor_light/editor.html deleted file mode 100755 index 976795b75..000000000 --- a/modules/editor/styles/ckeditor_light/editor.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - -Rhymix - - - - diff --git a/modules/editor/styles/ckeditor_light/img/bg_qmark.gif b/modules/editor/styles/ckeditor_light/img/bg_qmark.gif deleted file mode 100644 index 5a8a44625..000000000 Binary files a/modules/editor/styles/ckeditor_light/img/bg_qmark.gif and /dev/null differ diff --git a/modules/editor/styles/ckeditor_light/skin.xml b/modules/editor/styles/ckeditor_light/skin.xml deleted file mode 100644 index ab06ded8e..000000000 --- a/modules/editor/styles/ckeditor_light/skin.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - Rhymix 기본 서식 - Rhymix Default - 1.9 - 2016-04-27 - - NAVER - - diff --git a/modules/editor/styles/ckeditor_light/style.css b/modules/editor/styles/ckeditor_light/style.css deleted file mode 100644 index d12a4890d..000000000 --- a/modules/editor/styles/ckeditor_light/style.css +++ /dev/null @@ -1,30 +0,0 @@ -@charset "utf-8"; -/* NAVER (developers@xpressengine.com) */ -html { height: 100%; } -body { min-height: 100%; } -.xe_content blockquote.q1, -.xe_content blockquote.q2, -.xe_content blockquote.q3, -.xe_content blockquote.q4, -.xe_content blockquote.q5, -.xe_content blockquote.q6, -.xe_content blockquote.q7{padding:10px;margin:0 15px;border:0;} -.xe_content blockquote.q1{padding:0 10px;border-left:2px solid #ccc} -.xe_content blockquote.q2{padding:0 10px;background:url(./img/bg_qmark.gif) no-repeat left top} -.xe_content blockquote.q3{border:1px solid #d9d9d9} -.xe_content blockquote.q4{border:1px solid #d9d9d9;background:#fbfbfb} -.xe_content blockquote.q5{border:2px solid #707070} -.xe_content blockquote.q6{border:1px dashed #707070} -.xe_content blockquote.q7{border:1px dashed #707070;background:#fbfbfb} -.xe_content p{margin:0} - -.xe_content blockquote -{ - padding: 2px 0; - border-style: solid; - border-color: #ccc; - border-width: 0; - padding-left: 20px; - padding-right: 8px; - border-left-width: 5px; -} \ No newline at end of file diff --git a/modules/editor/styles/ckeditor_light/style.ini b/modules/editor/styles/ckeditor_light/style.ini deleted file mode 100644 index e8c7365cf..000000000 --- a/modules/editor/styles/ckeditor_light/style.ini +++ /dev/null @@ -1 +0,0 @@ -style.css diff --git a/modules/editor/styles/default/editor.css b/modules/editor/styles/default/editor.css deleted file mode 100644 index 5439772bf..000000000 --- a/modules/editor/styles/default/editor.css +++ /dev/null @@ -1,22 +0,0 @@ -@charset "utf-8"; -/* NAVER (developers@xpressengine.com) */ -html,body{height:100%} -body{margin:0;padding:0} -.xe_content{color:#000;} -.xe_content img{border:0;max-width:100%;width:auto;height:auto} -.xe_content blockquote.q1, -.xe_content blockquote.q2, -.xe_content blockquote.q3, -.xe_content blockquote.q4, -.xe_content blockquote.q5, -.xe_content blockquote.q6, -.xe_content blockquote.q7{padding:10px;margin:0 15px} -.xe_content blockquote.q1{padding:0 10px;border-left:2px solid #ccc} -.xe_content blockquote.q2{padding:0 10px;background:url(./img/bg_qmark.gif) no-repeat left top} -.xe_content blockquote.q3{border:1px solid #d9d9d9} -.xe_content blockquote.q4{border:1px solid #d9d9d9;background:#fbfbfb} -.xe_content blockquote.q5{border:2px solid #707070} -.xe_content blockquote.q6{border:1px dashed #707070} -.xe_content blockquote.q7{border:1px dashed #707070;background:#fbfbfb} -.xe_content table .xe_selected_cell{background-color:#d6e9ff} -.xe_content p{margin:0} \ No newline at end of file diff --git a/modules/editor/styles/default/editor.html b/modules/editor/styles/default/editor.html deleted file mode 100755 index 976795b75..000000000 --- a/modules/editor/styles/default/editor.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - -Rhymix - - - - diff --git a/modules/editor/styles/default/img/bg_qmark.gif b/modules/editor/styles/default/img/bg_qmark.gif deleted file mode 100644 index 5a8a44625..000000000 Binary files a/modules/editor/styles/default/img/bg_qmark.gif and /dev/null differ diff --git a/modules/editor/styles/default/skin.xml b/modules/editor/styles/default/skin.xml deleted file mode 100644 index 46d4ca6c2..000000000 --- a/modules/editor/styles/default/skin.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - XE 기본 서식 - XE Default Form - XE默认样式 - XE Mặc định - XEデフォルトスタイル - XE預設樣式 - XE Varsayılan Form - - XE 기본 문서 서식입니다. - 있는 그대로 표시가 될 뿐 편집/ 출력시 아무런 영향을 끼치지 않습니다. - - - XE's default document style. - It displays as it is, and does not affect during editing/printing. - - - XE默认样式。 - 主题显示及编辑不会受到任何影响,即原样输出。 - - - XE預設樣式。 - 主題顯示和編輯不會受到影響。 - - - XEの基本ドキュメント書式です。 - そのまま表示されるだけで、編集・出力には影響しません。 - - - XE varsayılan belge tarzı. - Olduğu gibi gösterir, düzenlemeye / yazdırmaya etki etmez. - - - Trang mẫu mặc định của XE. - Nó sẽ hiển thị trong phần sửa đổi bài viết và không ảnh hưởng đến bất cứ chức năng nào cả. - - 1.7 - 2013-11-27 - - NAVER - NAVER - NAVER - NAVER - NAVER - NAVER - NAVER - - diff --git a/modules/editor/styles/default/style.css b/modules/editor/styles/default/style.css deleted file mode 100644 index 4727dff15..000000000 --- a/modules/editor/styles/default/style.css +++ /dev/null @@ -1,19 +0,0 @@ -@charset "utf-8"; -/* NAVER (developers@xpressengine.com) */ -.xe_content{color:#000;} -.xe_content blockquote.q1, -.xe_content blockquote.q2, -.xe_content blockquote.q3, -.xe_content blockquote.q4, -.xe_content blockquote.q5, -.xe_content blockquote.q6, -.xe_content blockquote.q7{padding:10px;margin:0 15px} -.xe_content blockquote.q1{padding:0 10px;border-left:2px solid #ccc} -.xe_content blockquote.q2{padding:0 10px;background:url(./img/bg_qmark.gif) no-repeat left top} -.xe_content blockquote.q3{border:1px solid #d9d9d9} -.xe_content blockquote.q4{border:1px solid #d9d9d9;background:#fbfbfb} -.xe_content blockquote.q5{border:2px solid #707070} -.xe_content blockquote.q6{border:1px dashed #707070} -.xe_content blockquote.q7{border:1px dashed #707070;background:#fbfbfb} -.xe_content p{margin:0} - diff --git a/modules/editor/styles/default/style.ini b/modules/editor/styles/default/style.ini deleted file mode 100644 index e8c7365cf..000000000 --- a/modules/editor/styles/default/style.ini +++ /dev/null @@ -1 +0,0 @@ -style.css diff --git a/modules/editor/tpl/admin_index.html b/modules/editor/tpl/admin_index.html index 41b54406a..ff483f164 100644 --- a/modules/editor/tpl/admin_index.html +++ b/modules/editor/tpl/admin_index.html @@ -33,105 +33,124 @@

                {$lang->main_editor}

                - +
                - +
                - - - -
                - -
                -
                -
                - -
                -

                {$lang->pc} px

                -

                {$lang->mobile} px

                -
                -
                -
                - -
                -

                {$lang->pc} - - + +

                +

                + {$lang->guide_editor_height} + px +

                +

                + {$lang->guide_editor_toolbar} +  

                -

                {$lang->mobile} - - - -

                - +
                - - - +

                + + +

                +

                + {$lang->guide_editor_height} + px

                +

                +

                + {$lang->guide_editor_toolbar} +   + +

                {$lang->comment_editor}

                - +
                - - - -
                - -
                -
                - -
                - -
                -

                {$lang->pc} px

                -

                {$lang->mobile} px  

                -
                -
                -
                - -
                -

                {$lang->pc} - - + +

                +

                + {$lang->guide_editor_height} + px +

                +

                + {$lang->guide_editor_toolbar} +  

                -

                {$lang->mobile} - - - -

                - +
                - - - +

                + + +

                +

                + {$lang->guide_editor_height} + px

                +

                +

                + {$lang->guide_editor_toolbar} +   + +

                @@ -140,10 +159,10 @@
                -
                @@ -234,7 +256,15 @@
                -

                {$lang->about_enable_autosave}

                +

                {$lang->about_enable_autosave}

                +
                +
                +
                + +
                + + +

                {$lang->about_editor_auto_dark_mode}

                @@ -245,16 +275,23 @@
                - +
                -
                @@ -327,7 +364,15 @@ \ No newline at end of file + diff --git a/modules/editor/tpl/js/editor.app.js b/modules/editor/tpl/js/editor.app.js index 619753685..67df8c8d1 100644 --- a/modules/editor/tpl/js/editor.app.js +++ b/modules/editor/tpl/js/editor.app.js @@ -1,16 +1,14 @@ function getCkFormInstance(editor_sequence) { - var fo_obj = document.getElementById('ckeditor_instance_' + editor_sequence).parentNode; - while(fo_obj.nodeName != 'FORM') { fo_obj = fo_obj.parentNode; } - if(fo_obj.nodeName == 'FORM') return fo_obj; - return; + var form = $('#ckeditor_instance_' + editor_sequence).closest('form'); + return form.length ? form[0] : null; } -function getAutoSavedSrl(ret_obj, response_tags, c) { +function getAutoSavedSrl(ret_obj) { var editor_sequence = ret_obj.editor_sequence; var primary_key = ret_obj.key; var fo_obj = getCkFormInstance(editor_sequence); - + if(ret_obj.document_srl !== 0) { fo_obj[primary_key].value = ret_obj.document_srl; @@ -21,7 +19,7 @@ function getAutoSavedSrl(ret_obj, response_tags, c) { (function($){ "use strict"; var default_ckeconfig = { - bodyClass: 'xe_content editable', + bodyClass: 'rhymix_content xe_content editable', toolbarCanCollapse: true, toolbarGroups: [ { name: 'clipboard', groups: [ 'undo', 'clipboard' ] }, @@ -32,7 +30,7 @@ function getAutoSavedSrl(ret_obj, response_tags, c) { { name: 'document', groups: [ 'mode' ] }, '/', { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] }, - { name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ] }, + { name: 'paragraph', groups: [ 'align', 'list', 'indent', 'blocks', 'bidi' ] }, '/', { name: 'styles' }, { name: 'colors' }, @@ -40,8 +38,8 @@ function getAutoSavedSrl(ret_obj, response_tags, c) { { name: 'others' } ], allowedContent: true, - removePlugins: 'stylescombo,language,bidi,flash,pagebreak', - removeButtons: 'Save,Preview,Print,Cut,Copy,Paste', + removePlugins: 'stylescombo,language,bidi,flash,pagebreak,exportpdf', + removeButtons: 'Save,Preview,Print,Cut,Copy,Paste,Flash,NewPage,ExportPdf,Language', uiColor: '#EFF0F0' }; @@ -76,44 +74,48 @@ function getAutoSavedSrl(ret_obj, response_tags, c) { editorInit : function(containerEl, opts) { var self = this; var $containerEl = containerEl; - var $form = $containerEl.closest('form'); - var $contentField = $form.find(opts.content_field); + var form = $containerEl.closest('form'); var data = $containerEl.data(); - var editor_sequence = $containerEl.data().editorSequence; - var primary_key = $containerEl.data().editorPrimaryKeyName; + var editor_sequence = data.editorSequence; + var primary_key = data.editorPrimaryKeyName; + var primary_input = form.find("[name='" + primary_key + "']"); + var content_key = data.editorContentKeyName; + var content_input = form.find("[name='" + content_key + "']"); var fo_obj = getCkFormInstance(editor_sequence); this.ckeconfig = $.extend({}, default_ckeconfig, opts.ckeconfig || {}); + this.ckeconfig.bodyClass = this.ckeconfig.bodyClass + ' color_scheme_' + getColorScheme() + + ($('body').hasClass('cke_auto_dark_mode') ? ' cke_auto_dark_mode' : ''); - this.editor_sequence = data.editorSequence; - $form.attr('editor_sequence', data.editorSequence); + this.editor_sequence = editor_sequence; + form.attr('editor_sequence', editor_sequence); if(CKEDITOR.env.mobile) CKEDITOR.env.isCompatible = true; - + // saved document(자동저장 문서)에 대한 확인 if(typeof(fo_obj._saved_doc_title)!= "undefined") { ///<< _saved_doc_title field가 없으면 자동저장 하지 않음 var saved_title = fo_obj._saved_doc_title.value; var saved_content = fo_obj._saved_doc_content.value; - + if(saved_title || saved_content) { // 자동저장된 문서 활용여부를 물은 후 사용하지 않는다면 자동저장된 문서 삭제 - if(confirm(fo_obj._saved_doc_message.value)) { - if(typeof(fo_obj.title)!='undefined') fo_obj.title.value = saved_title; - $contentField.val(saved_content); - - var param = []; - param.editor_sequence = editor_sequence; - param.primary_key = primary_key; - param.mid = current_mid; - var response_tags = new Array("error","message","editor_sequence","key","title","content","document_srl"); - exec_xml('editor',"procEditorLoadSavedDocument", param, getAutoSavedSrl, response_tags); + if (confirm(fo_obj._saved_doc_message.value)) { + if(typeof(fo_obj.title) !== 'undefined') { + fo_obj.title.value = saved_title; + } + content_input.val(saved_content); + exec_json('editor.procEditorLoadSavedDocument', { + editor_sequence: editor_sequence, + primary_key: primary_key, + mid: current_mid + }, getAutoSavedSrl); } else { editorRemoveSavedDoc(); } } } - var instance = CKEDITOR.appendTo($containerEl[0], {}, $contentField.val()); + var instance = CKEDITOR.appendTo($containerEl[0], {}, content_input.val()); instance.on('customConfigLoaded', function(e) { instance.config = $.extend({}, e.editor.config, self.ckeconfig); @@ -160,28 +162,29 @@ function getAutoSavedSrl(ret_obj, response_tags, c) { } }); - instance.on('paste', function(e) { - if (e.data && e.data.dataValue && e.data.dataValue.replace) { - e.data.dataValue = e.data.dataValue.replace(/<(iframe|object)\s[^<>]+<\/\1>/g, function(m) { - return String(m).unescape() + '

                 

                '; - }); - } - }); - $containerEl.data('cke_instance', instance); window.editorRelKeys[data.editorSequence] = {}; - window.editorRelKeys[data.editorSequence].primary = $form.find('[name='+data.editorPrimaryKeyName+']')[0]; - window.editorRelKeys[data.editorSequence].content = $form.find('[name='+data.editorContentKeyName+']')[0]; - window.editorRelKeys[data.editorSequence].func = function(seq) { + window.editorRelKeys[data.editorSequence].primary = primary_input[0]; + window.editorRelKeys[data.editorSequence].content = content_input[0]; + window.editorRelKeys[data.editorSequence].func = function(seq) { return self.getContent.call(self, seq); }; window.editorRelKeys[data.editorSequence].pasteHTML = function(text){ instance.insertHtml(text, 'html'); }; - - // 자동저장 필드가 있다면 자동 저장 기능 활성화 - if(typeof(fo_obj._saved_doc_title)!="undefined" ) editorEnableAutoSave(fo_obj, editor_sequence); + + // Automatically update content input value #445 + instance.on('change', function(e) { + if (window.editorRelKeys[data.editorSequence].content) { + window.editorRelKeys[data.editorSequence].content.value = e.editor.getData(); + } + }); + + // Enable autosave + if (typeof(fo_obj._saved_doc_title) !== 'undefined') { + editorEnableAutoSave(fo_obj, editor_sequence); + } }, getContent : function(seq) { var self = this; diff --git a/modules/editor/tpl/js/editor_common.js b/modules/editor/tpl/js/editor_common.js index e6251ec35..92dcaa50c 100644 --- a/modules/editor/tpl/js/editor_common.js +++ b/modules/editor/tpl/js/editor_common.js @@ -82,7 +82,7 @@ function _editorAutoSave(exe, callback) { editorAutoSaveObj.content = content; // 메시지 만들어서 보여줌 - jQuery("#editor_autosaved_message_"+editor_sequence).text(oDate.getHours()+':'+oDate.getMinutes()+' '+auto_saved_msg).show(300); + jQuery("#editor_autosaved_message_"+editor_sequence).text(oDate.toTimeString().substring(0, 5) + ' ' + auto_saved_msg).show(300); // 현재 자동저장중임을 설정 editorAutoSaveObj.locked = true; @@ -210,7 +210,11 @@ function openComponent(component_name, editor_sequence, manual_url) { if(typeof(manual_url)!="undefined" && manual_url) popup_url += "&manual_url="+escape(manual_url); if(typeof(current_mid)!="undefined" && current_mid) popup_url += "&mid="+escape(current_mid); - popopen(popup_url, 'editorComponent'); + if (navigator.userAgent.match(/mobile/i)) { + openModalIframe(popup_url, 'editorComponent'); + } else { + popopen(popup_url, 'editorComponent'); + } } // 더블클릭 이벤트 발생시에 본문내에 포함된 컴포넌트를 찾는 함수 @@ -227,7 +231,7 @@ function editorSearchComponent(evt) { if(obj.getAttribute("widget")) { // editor_sequence을 찾음 while(tobj && tobj.nodeName != "BODY") { - tobj = xParent(tobj); + tobj = tobj.parentElement || tobj.parentNode; } if(!tobj || tobj.nodeName != "BODY" || !tobj.getAttribute("editor_sequence")) { editorPrevNode = null; @@ -238,15 +242,19 @@ function editorSearchComponent(evt) { editorPrevNode = obj; if(editorMode[editor_sequence]=='html') return; - popopen(request_uri+"?module=widget&act=dispWidgetGenerateCodeInPage&selected_widget="+widget+"&module_srl="+editor_sequence,'GenerateCodeInPage'); + var popup_url = request_uri+"?module=widget&act=dispWidgetGenerateCodeInPage&selected_widget="+widget+"&module_srl="+editor_sequence + if (navigator.userAgent.match(/mobile/i)) { + openModalIframe(popup_url, 'GenerateCodeInPage'); + } else { + popopen(popup_url, 'GenerateCodeInPage'); + } return; } // 선택되어진 object부터 상단으로 이동하면서 editor_component attribute가 있는지 검사 if(!obj.getAttribute("editor_component")) { while(obj && !obj.getAttribute("editor_component")) { - if(obj.parentElement) obj = obj.parentElement; - else obj = xParent(obj); + obj = obj.parentElement || obj.parentNode; } } @@ -274,7 +282,7 @@ function editorSearchComponent(evt) { // editor_sequence을 찾음 tobj = obj; while(tobj && tobj.nodeName != "BODY") { - tobj = xParent(tobj); + tobj = tobj.parentElement || tobj.parentNode; } if(!tobj || tobj.nodeName != "BODY" || !tobj.getAttribute("editor_sequence")) { editorPrevNode = null; diff --git a/modules/editor/tpl/js/editor_module_config.js b/modules/editor/tpl/js/editor_module_config.js index 89a343283..7e9052802 100644 --- a/modules/editor/tpl/js/editor_module_config.js +++ b/modules/editor/tpl/js/editor_module_config.js @@ -1,55 +1,23 @@ -function getEditorSkinColorList(skin_name,selected_colorset,type,testid){ - if(skin_name.length>0){ - type = type || 'document'; - var response_tags = new Array('error','message','colorset'); - exec_xml('editor','dispEditorSkinColorset',{skin:skin_name},resultGetEditorSkinColorList,response_tags,{'selected_colorset':selected_colorset,'type':type,'testid':testid}); - } -} +"use strict"; -function resultGetEditorSkinColorList(ret_obj,response_tags, params) { - var selectbox = null; - jQuery(function($){ - if(params.testid){ - selectbox = $("#"+params.testid).next('label').children('select'); - } else { - selectbox = (params.type == 'document') ? $('select[name=sel_editor_colorset]') : $('select[name=sel_comment_editor_colorset]'); - } - selectbox.html(''); +function getEditorSkinColorList() { } - if(params.type == 'document'){ - $("select[name=sel_editor_colorset]").hide() - .removeAttr('name'); - selectbox.attr('name','sel_editor_colorset'); - } else { - $("select[name=sel_comment_editor_colorset]").hide() - .removeAttr('name'); - selectbox.attr('name','sel_comment_editor_colorset'); - } - - /* jshint -W041 */ - if(ret_obj.error == 0 && ret_obj.colorset){ - var it = []; - var items = ret_obj.colorset.item; - if(typeof(items[0]) == 'undefined'){ - it[0] = items; - } else { - it = items; +(function($) { + $(function() { + $('.editor_skin_selector').on('change', function() { + var colorset_selector = $(this).siblings('.editor_colorset_selector').empty(); + var colorset_list = $(this).find('option:selected').data('colorsets'); + if (colorset_list && colorset_list.length) { + $.each(colorset_list, function(i, colorset) { + var option = $(''); + option.attr('value', colorset.name); + option.text(colorset.title); + option.appendTo(colorset_selector); + if (colorset.title.indexOf('L') > -1) { + option.attr('selected', 'selected'); + } + }); } - - var selectAttr = ""; - for(var i=0;i'+it[i].title+''); - - if(params.selected_colorset == it[i].name){ - $options.attr('selected', 'selected'); - } - - selectbox.append($options); - } - selectbox.show(); - } else { - selectbox.hide(); - selectbox.html(''); - } + }); }); -} +})(jQuery); diff --git a/modules/editor/tpl/popup.html b/modules/editor/tpl/popup.html index aa8c8f175..c1509e80a 100644 --- a/modules/editor/tpl/popup.html +++ b/modules/editor/tpl/popup.html @@ -1,3 +1 @@ -
                {$popup_content|noescape} -
                diff --git a/modules/editor/tpl/preview.html b/modules/editor/tpl/preview.html deleted file mode 100644 index 2d996e430..000000000 --- a/modules/editor/tpl/preview.html +++ /dev/null @@ -1,4 +0,0 @@ - -{$content|noescape} diff --git a/modules/editor/tpl/setup_component.html b/modules/editor/tpl/setup_component.html index b8d8d0e08..30bf7c8f1 100644 --- a/modules/editor/tpl/setup_component.html +++ b/modules/editor/tpl/setup_component.html @@ -32,7 +32,7 @@ {$component->link}
                - +

                {$var->group}

                diff --git a/modules/extravar/conf/info.xml b/modules/extravar/conf/info.xml new file mode 100644 index 000000000..a612710ef --- /dev/null +++ b/modules/extravar/conf/info.xml @@ -0,0 +1,14 @@ + + + 확장 변수 + Extra Input Fields + 회원정보, 문서 등의 사용자 정의 입력 항목을 관리합니다. + Manage custom input fields in member information, documents, etc. + RX_VERSION + RX_CORE + service + + Rhymix + Rhymix + + diff --git a/modules/extravar/conf/module.xml b/modules/extravar/conf/module.xml new file mode 100644 index 000000000..ee4bf79d7 --- /dev/null +++ b/modules/extravar/conf/module.xml @@ -0,0 +1,13 @@ + + + + + + + + + 확장 변수 + Extra Input Fields + + + diff --git a/modules/extravar/controllers/Base.php b/modules/extravar/controllers/Base.php new file mode 100644 index 000000000..9c1c6ac27 --- /dev/null +++ b/modules/extravar/controllers/Base.php @@ -0,0 +1,11 @@ +module) ?: new \stdClass; + Context::set('config', $config); + + // Get the list of installed skins. + $skins = ModuleModel::getSkins($this->module_path); + Context::set('skin_list', $skins); + + // Set admin template path. + $this->setTemplatePath($this->module_path . 'views'); + $this->setTemplateFile('config.blade.php'); + } + + /** + * Save module config. + */ + public function procExtravarAdminInsertConfig() + { + // Get current module config. + $config = ModuleModel::getModuleConfig($this->module) ?: new \stdClass; + + // Update the config object. + $vars = Context::getRequestVars(); + $config->skin = trim($vars->skin ?? ''); + if (!Storage::isDirectory(sprintf('%s/skins/%s/', rtrim($this->module_path, '/'), $config->skin))) + { + throw new TargetNotFound; + } + + // Save the updated config. + $output = ModuleController::getInstance()->insertModuleConfig($this->module, $config); + if (!$output->toBool()) + { + return $output; + } + + // Redirect back to the config page. + $this->setMessage('success_updated'); + $this->setRedirectUrl(Context::get('success_return_url') ?: getNotEncodedUrl([ + 'module' => 'admin', + 'act' => 'dispExtravarAdminConfig', + ])); + } +} diff --git a/modules/extravar/controllers/Install.php b/modules/extravar/controllers/Install.php new file mode 100644 index 000000000..0c3f1cfa5 --- /dev/null +++ b/modules/extravar/controllers/Install.php @@ -0,0 +1,11 @@ + true, + 'tel_v2' => true, + 'tel_intl' => true, + 'tel_intl_v2' => true, + 'checkbox' => true, + 'radio' => true, + 'select' => true, + 'kr_zip' => true, + ]; + + /** + * List of types that can have options. + */ + public const OPTION_TYPES = [ + 'checkbox' => true, + 'radio' => true, + 'select' => true, + ]; + + /** + * Constructor for compatibility with legacy ExtraItem class. + * + * @param int $module_srl + * @param int $idx + * @param string $name + * @param string $type + * @param mixed $default + * @param string $desc + * @param string $is_required (Y, N) + * @param string $search (Y, N) + * @param string $value + * @param string $eid + * @param string $parent_type + * @param string $is_strict + * @param string $options + * @param string $sort + */ + function __construct(int $module_srl, int $idx, string $name, string $type = 'text', $default = null, $desc = '', $is_required = 'N', $search = 'N', $value = null, $eid = '', $parent_type = 'document', $is_strict = '', $options = null, $sort = 'N') + { + if (!$idx) + { + return; + } + + $this->module_srl = $module_srl; + $this->idx = $idx; + $this->eid = $eid; + $this->type = $type; + $this->parent_type = $parent_type; + $this->value = $value; + $this->name = $name; + $this->desc = $desc; + $this->default = $default; + $this->options = $options ? json_decode($options) : null; + $this->is_required = $is_required; + $this->is_strict = $is_strict; + $this->search = $search; + $this->sort = $sort; + } + + /** + * Set the raw value. + * + * @param mixed $value + * @return void + */ + public function setValue($value): void + { + $this->value = $value; + } + + /** + * Check if this extra variable has a value. + * + * @return bool + */ + public function hasValue(): bool + { + $value = self::_getTypeValue($this->type, $this->value); + if ($value === null || $value === '' || (is_array($value) && !count($value))) + { + return false; + } + else + { + return true; + } + } + + /** + * Get the raw value. + * + * @return string|array|null + */ + public function getValue() + { + return self::_getTypeValue($this->type, $this->value); + } + + /** + * Get the value formatted as HTML. + * + * @return string + */ + public function getValueHTML(): string + { + return self::_getTypeValueHTML($this->type, $this->value); + } + + /** + * Get the HTML form for an extra input field. + * + * @return string + */ + public function getFormHTML(): string + { + $template = new Template(self::_getSkinPath(), 'form.blade.php'); + $template->setVars([ + 'definition' => $this, + 'parent_type' => $this->parent_type, + 'type' => $this->type, + 'value' => self::_getTypeValue($this->type, $this->value), + 'default' => self::_getTypeValue($this->type, $this->default), + 'input_name' => $this->parent_type === 'document' ? ('extra_vars' . $this->idx) : ($this->input_name ?: $this->eid), + 'input_id' => $this->input_id ?: '', + ]); + return $template->compile(); + } + + /** + * Get the default value. + * + * @return mixed + */ + public function getDefaultValue() + { + if (!$this->canHaveOptions()) + { + return $this->default; + } + + if (is_array($this->options)) + { + return $this->default; + } + elseif ($this->default && $this->parent_type !== 'member' && !in_array($this->type, ['checkbox', 'radio'])) + { + return is_array($this->default) ? array_first($this->default) : array_first(explode(',', $this->default)); + } + else + { + return null; + } + } + + /** + * Get options specified by the administrator. + * + * @return array + */ + public function getOptions(): array + { + if (!$this->canHaveOptions()) + { + return $this->options ?? []; + } + + if (is_array($this->options)) + { + return $this->options; + } + elseif ($this->default) + { + return is_array($this->default) ? $this->default : explode(',', $this->default); + } + else + { + return []; + } + } + + /** + * Check if the current value can have options. + * + * @return bool + */ + public function canHaveOptions(): bool + { + return isset(self::OPTION_TYPES[$this->type]); + } + + /** + * Check if the current value is an array type. + * + * @return bool + */ + public function isArrayType(): bool + { + return isset(self::ARRAY_TYPES[$this->type]); + } + + /** + * Validate a value. + * + * @param mixed $value + * @param mixed $old_value + * @return BaseObject + */ + public function validate($value, $old_value = null): BaseObject + { + // Take legacy encoding into consideration. + if (is_array($value)) + { + $is_array = true; + $values = $value; + } + elseif (str_contains($value, '|@|')) + { + $is_array = true; + $values = explode('|@|', $value); + } + else + { + $is_array = false; + $values = [$value]; + } + + // Check if a required value is empty. + if ($this->is_required === 'Y') + { + if ($this->type === 'file' && !$value && $old_value) + { + $value = $old_value; + $values = (array)$old_value; + } + if ($is_array && trim(implode('', $values)) === '') + { + return new BaseObject(-1, sprintf(lang('common.filter.isnull'), Context::replaceUserLang($this->name))); + } + if (!$is_array && trim(strval($value)) === '') + { + return new BaseObject(-1, sprintf(lang('common.filter.isnull'), Context::replaceUserLang($this->name))); + } + } + + // Check if a strict value is not one of the specified options. + if ($this->is_strict === 'Y' && $value) + { + if ($this->canHaveOptions()) + { + $options = $this->getOptions(); + foreach ($values as $v) + { + if (!in_array($v, $options)) + { + return new BaseObject(-1, sprintf(lang('common.filter.equalto'), Context::replaceUserLang($this->name))); + } + } + } + elseif ($this->isArrayType()) + { + if (!$is_array) + { + return new BaseObject(-1, sprintf(lang('common.filter.equalto'), Context::replaceUserLang($this->name))); + } + } + } + + return new BaseObject; + } + + /** + * Upload a file. + * + * @param array $file + * @param int $target_srl + * @param string $target_type + * @return BaseObject + */ + public function uploadFile(array $file, int $target_srl, string $target_type): BaseObject + { + $oFileController = FileController::getInstance(); + $output = $oFileController->insertFile($file, $this->module_srl, $target_srl); + if ($output->toBool()) + { + $oFileController->setFilesValid($target_srl, "ev:$target_type", $output->get('file_srl')); + } + return $output; + } + + /** + * Get the next temporary ID. + * + * @return string + */ + public static function getNextTempID(): string + { + return sprintf('rx_tempid_%d', self::$_temp_id++); + } + + /** + * Get the normalized value. + * + * This method is public for compatibility with legacy ExtraItem class. + * It should not be called from outside of this class. + * + * @param string $type + * @param string|array $value + * @return string|array|null + */ + public static function _getTypeValue(string $type, $value) + { + // Return if the value is empty. + if (is_array($value)) + { + if (!count($value)) + { + return; + } + } + else + { + $value = trim(strval($value ?? '')); + if ($value === '') + { + return; + } + } + + // Process array types. + if (isset(self::ARRAY_TYPES[$type])) + { + if (is_array($value)) + { + $values = $value; + } + elseif (preg_match('/^[\[\{].*[\]\}]$/', $value)) + { + $values = json_decode($value, true); + if (!is_array($values)) + { + $values = []; + } + } + elseif (str_contains($value, '|@|')) + { + $values = explode('|@|', $value); + } + /* + elseif (str_contains($value, ',') && $type !== 'kr_zip') + { + $values = explode(',', $value); + } + */ + else + { + $values = [$value]; + } + + return array_map(function($str) { + return trim(escape($str, false)); + }, array_values($values)); + } + + // Process the URL type. + if ($type === 'homepage' || $type === 'url') + { + if ($value && !preg_match('!^[a-z]+://!i', $value)) + { + $value = 'http://' . $value; + } + return escape($value, false); + } + + // Process the file upload type. + if ($type === 'file') + { + return $value ? intval($value) : null; + } + + // Escape and return all other types. + return escape($value, false); + } + + /** + * Get the normalized value in HTML format. + * + * @param string $type + * @param string|array $value + * @return string + */ + protected static function _getTypeValueHTML(string $type, $value): string + { + // Return if the value is empty. + $value = self::_getTypeValue($type, $value); + if ($value === null || $value === '' || (is_array($value) && !count($value))) + { + return ''; + } + + // Apply formatting appropriate for each type. + switch ($type) + { + case 'textarea': + return nl2br($value); + case 'select': + case 'radio': + case 'checkbox': + return is_array($value) ? implode(', ', $value) : $value; + case 'tel': + case 'tel_v2': + return is_array($value) ? implode('-', $value) : $value; + case 'tel_intl': + case 'tel_intl_v2': + if (is_array($value) && count($value)) + { + $country_code = $value[0]; + $phone_number = array_slice((array)$value, 1); + if (count($phone_number) && ctype_alpha(end($phone_number))) + { + array_pop($phone_number); + } + return sprintf('(+%d) %s', $country_code, implode('-', $phone_number)); + } + else + { + return ''; + } + case 'homepage': + case 'url': + $display = mb_strlen($value, 'UTF-8') > 60 ? mb_substr($value, 0, 40, 'UTF-8') . '...' . mb_substr($value, -10, 10, 'UTF-8') : $value; + return sprintf('%s', $value, $display); + case 'email_address': + case 'email': + return sprintf('%s', $value, $value); + case 'kr_zip': + return is_array($value) ? trim(implode(' ', $value)) : $value; + case 'country': + $country = i18n::listCountries()[$value] ?? ''; + if ($country) + { + $lang_type = \Context::getLangType(); + return $lang_type === 'ko' ? $country->name_korean : $country->name_english; + } + else + { + return ''; + } + case 'language': + return Lang::getSupportedList()[$value]['name'] ?? ''; + case 'date': + return sprintf('%s-%s-%s', substr($value, 0, 4), substr($value, 4, 2), substr($value, 6, 2)); + case 'timezone': + return DateTime::getTimezoneList()[$value] ?? ''; + case 'file': + if ($value) + { + $file = FileModel::getFile($value); + if ($file) + { + return sprintf('%s (%s)', \RX_BASEURL . ltrim($file->download_url, './'), $file->source_filename, FileHandler::filesize($file->file_size)); + } + else + { + return ''; + } + } + else + { + return ''; + } + default: + return $value; + } + } + + /** + * Get the currently configured skin path. + * + * @return string + */ + protected static function _getSkinPath(): string + { + if (self::$_skin_path !== null) + { + return self::$_skin_path; + } + + $config = ModuleModel::getModuleConfig('extravar') ?: new \stdClass; + $skin = $config->skin ?? 'default'; + if (Storage::isDirectory(\RX_BASEDIR . 'modules/extravar/skins/' . $skin)) + { + self::$_skin_path = \RX_BASEDIR . 'modules/extravar/skins/' . $skin; + } + else + { + self::$_skin_path = \RX_BASEDIR . 'modules/extravar/skins/default'; + } + + return self::$_skin_path; + } +} diff --git a/modules/extravar/models/ValueCollection.php b/modules/extravar/models/ValueCollection.php new file mode 100644 index 000000000..a3d0b3add --- /dev/null +++ b/modules/extravar/models/ValueCollection.php @@ -0,0 +1,66 @@ +module_srl = $module_srl; + $this->setExtraVarKeys($keys); + } + + /** + * Set the list of extra keys for this module. + * + * @param array $keys + * @return void + */ + public function setExtraVarKeys($keys) + { + if (!is_array($keys) || !count($keys)) + { + return; + } + + foreach ($keys as $val) + { + $this->keys[$val->idx] = new Value($val->module_srl, $val->idx, $val->name, $val->type, $val->default, $val->desc, $val->is_required, $val->search, $val->value ?? null, $val->eid, $val->parent_type ?? 'document', $val->is_strict, $val->options, $val->sort ?? 'N'); + } + } + + /** + * Returns an array of Value. + * + * @return array + */ + public function getExtraVars(): array + { + return $this->keys; + } +} diff --git a/modules/extravar/skins/default/assets/file_upload.js b/modules/extravar/skins/default/assets/file_upload.js new file mode 100644 index 000000000..d280ea1e9 --- /dev/null +++ b/modules/extravar/skins/default/assets/file_upload.js @@ -0,0 +1,26 @@ +'use strict'; + +$(function() { + $('.ev_file_upload').each(function() { + const container = $(this); + container.find('button.evFileRemover').on('click', function() { + container.find('span.filename').text(''); + container.find('span.filesize').text(''); + container.find('input[type=hidden][name^=_delete_]').val('Y'); + container.find('input[type=file]').val(''); + $(this).remove(); + }); + container.find('input.rx_ev_file').on('change', function() { + const max_size = parseInt($(this).data('allowedFilesize'), 10); + const file_count = this.files.length; + for (let i = 0; i < file_count; i++) { + if (max_size && this.files[i].size > max_size) { + alert($(this).data('msgFilesize')); + $(this).val(''); + return; + } + } + container.find('input[type=hidden][name^=_delete_]').val('N'); + }); + }); +}); diff --git a/modules/extravar/skins/default/assets/number.js b/modules/extravar/skins/default/assets/number.js new file mode 100644 index 000000000..eccbd7474 --- /dev/null +++ b/modules/extravar/skins/default/assets/number.js @@ -0,0 +1,7 @@ +'use strict'; + +$(function() { + $('input.rx_ev_number').on('input', function() { + this.value = this.value.replace(/[^0-9]/g, ''); + }); +}); diff --git a/modules/extravar/skins/default/form.blade.php b/modules/extravar/skins/default/form.blade.php new file mode 100644 index 000000000..3f7453430 --- /dev/null +++ b/modules/extravar/skins/default/form.blade.php @@ -0,0 +1,55 @@ +@switch ($type) + @case ('text') + @include ('form_types/text') + @break + @case ('textarea') + @include ('form_types/textarea') + @break + @case ('password') + @include ('form_types/password') + @break + @case ('select') + @include ('form_types/select') + @break + @case ('radio') + @case ('checkbox') + @include ('form_types/checkbox') + @break + @case ('tel') + @case ('tel_v2') + @case ('tel_intl') + @case ('tel_intl_v2') + @include ('form_types/tel') + @break + @case ('homepage') + @case ('url') + @include ('form_types/text') + @break + @case ('email_address') + @case ('email') + @include ('form_types/text') + @break + @case ('kr_zip') + @include ('form_types/kr_zip') + @break + @case ('country') + @case ('language') + @case ('timezone') + @include ('form_types/locale') + @break + @case ('date') + @case ('time') + @include ('form_types/datetime') + @break + @case ('file') + @include ('form_types/file_upload') + @break + @default + @include ('form_types/text') +@endswitch + +@if (!empty($definition->desc)) +

                + {{ Context::replaceUserLang($definition->desc)|nl2br }} +

                +@endif diff --git a/modules/extravar/skins/default/form_types/checkbox.blade.php b/modules/extravar/skins/default/form_types/checkbox.blade.php new file mode 100644 index 000000000..7087263a2 --- /dev/null +++ b/modules/extravar/skins/default/form_types/checkbox.blade.php @@ -0,0 +1,44 @@ +@php + $has_value = is_array($value); + $default_value = $definition->getDefaultValue(); +@endphp + +@if ($parent_type === 'member') +
                + @foreach ($definition->getOptions() as $v) + @php + $column_suffix = $type === 'checkbox' ? '[]' : ''; + $tempid = $definition->getNextTempID(); + $is_checked = $has_value ? in_array($v, $value) : ($v === $default_value); + @endphp + + @endforeach +
                +@else +
                  + @foreach ($definition->getOptions() as $v) + @php + $column_suffix = $type === 'checkbox' ? '[]' : ''; + $tempid = $definition->getNextTempID(); + $is_checked = $has_value ? in_array($v, $value) : ($v === $default_value); + @endphp +
                • + is_disabled)) + @readonly(toBool($definition->is_readonly)) + /> +
                • + @endforeach +
                +@endif diff --git a/modules/extravar/skins/default/form_types/datetime.blade.php b/modules/extravar/skins/default/form_types/datetime.blade.php new file mode 100644 index 000000000..dfa97f589 --- /dev/null +++ b/modules/extravar/skins/default/form_types/datetime.blade.php @@ -0,0 +1,34 @@ +@if ($type === 'date') + @php + $formatted_value = $value ? sprintf('%s-%s-%s', substr($value, 0, 4), substr($value, 4, 2), substr($value, 6, 2)) : ''; + @endphp + + is_required)) + @disabled(toBool($definition->is_disabled)) + @readonly(toBool($definition->is_readonly)) + /> + +@else + is_required)) + @disabled(toBool($definition->is_disabled)) + @readonly(toBool($definition->is_readonly)) + /> + +@endif diff --git a/modules/extravar/skins/default/form_types/file_upload.blade.php b/modules/extravar/skins/default/form_types/file_upload.blade.php new file mode 100644 index 000000000..b61ce7c94 --- /dev/null +++ b/modules/extravar/skins/default/form_types/file_upload.blade.php @@ -0,0 +1,38 @@ +@load('../assets/file_upload.js') + +
                + + @if ($value) + @php + $file = FileModel::getFile(intval($value)); + @endphp + @if ($file) +
                + {{ $file->source_filename }} + ({{ FileHandler::filesize($file->file_size) }}) + + +
                + @endif + @endif + + @php + $file_config = FileModel::getUploadConfig($definition->module_srl); + $allowed_filetypes = strtr($file_config->allowed_filetypes ?? '', ['*.' => '.', ';' => ',']); + $allowed_filesize = ($file_config->allowed_filesize ?? 0) * 1024 * 1024; + @endphp + +
                + is_required) && !$value) + @disabled(toBool($definition->is_disabled)) + @readonly(toBool($definition->is_readonly)) + /> +
                + +
                diff --git a/modules/extravar/skins/default/form_types/kr_zip.blade.php b/modules/extravar/skins/default/form_types/kr_zip.blade.php new file mode 100644 index 000000000..6db235a7b --- /dev/null +++ b/modules/extravar/skins/default/form_types/kr_zip.blade.php @@ -0,0 +1,5 @@ +@php + $oKrzipModel = KrzipModel::getInstance(); +@endphp + +{!! $oKrzipModel->getKrzipCodeSearchHtml($input_name, $value) !!} diff --git a/modules/extravar/skins/default/form_types/locale.blade.php b/modules/extravar/skins/default/form_types/locale.blade.php new file mode 100644 index 000000000..108930048 --- /dev/null +++ b/modules/extravar/skins/default/form_types/locale.blade.php @@ -0,0 +1,56 @@ +@if ($type === 'country') + @php + $lang_type = Context::getLangType(); + $lang_sort = $lang_type === 'ko' ? Rhymix\Framework\i18n::SORT_NAME_KOREAN : Rhymix\Framework\i18n::SORT_NAME_ENGLISH; + $countries = Rhymix\Framework\i18n::listCountries($lang_sort); + @endphp + +@elseif ($type === 'language') + @php + $enabled_languages = Rhymix\Framework\Config::get('locale.enabled_lang'); + $supported_languages = Rhymix\Framework\Lang::getSupportedList(); + @endphp + +@elseif ($type === 'timezone') + @php + $timezones = Rhymix\Framework\DateTime::getTimezoneList(); + @endphp + +@endif diff --git a/modules/extravar/skins/default/form_types/password.blade.php b/modules/extravar/skins/default/form_types/password.blade.php new file mode 100644 index 000000000..d304e691e --- /dev/null +++ b/modules/extravar/skins/default/form_types/password.blade.php @@ -0,0 +1,9 @@ +is_required)) + @disabled(toBool($definition->is_disabled)) + @readonly(toBool($definition->is_readonly)) +/> diff --git a/modules/extravar/skins/default/form_types/select.blade.php b/modules/extravar/skins/default/form_types/select.blade.php new file mode 100644 index 000000000..22e4570d8 --- /dev/null +++ b/modules/extravar/skins/default/form_types/select.blade.php @@ -0,0 +1,16 @@ +@php + $has_value = is_array($value); + $default_value = $definition->getDefaultValue(); +@endphp + + diff --git a/modules/extravar/skins/default/form_types/tel.blade.php b/modules/extravar/skins/default/form_types/tel.blade.php new file mode 100644 index 000000000..eee7900dc --- /dev/null +++ b/modules/extravar/skins/default/form_types/tel.blade.php @@ -0,0 +1,57 @@ +@if ($type === 'tel') + + + +@elseif ($type === 'tel_v2') + +@elseif ($type === 'tel_intl' || $type === 'tel_intl_v2') + @php + $lang_type = Context::getLangType(); + $lang_sort = $lang_type === 'ko' ? Rhymix\Framework\i18n::SORT_NAME_KOREAN : Rhymix\Framework\i18n::SORT_NAME_ENGLISH; + $countries = Rhymix\Framework\i18n::listCountries($lang_sort); + $tempid = $definition->getNextTempID(); + if (is_array($value) && count($value) && ctype_alpha(end($value))) { + $selected_iso_code = end($value); + } else { + $selected_iso_code = null; + } + @endphp + + @if ($type === 'tel_intl') + + + + @else + + @endif + +@endif diff --git a/modules/extravar/skins/default/form_types/text.blade.php b/modules/extravar/skins/default/form_types/text.blade.php new file mode 100644 index 000000000..754b6f145 --- /dev/null +++ b/modules/extravar/skins/default/form_types/text.blade.php @@ -0,0 +1,38 @@ +@if ($type === 'homepage' || $type === 'url') + is_required)) + @disabled(toBool($definition->is_disabled)) + @readonly(toBool($definition->is_readonly)) + /> +@elseif ($type === 'email_address' || $type === 'email') + is_required)) + @disabled(toBool($definition->is_disabled)) + @readonly(toBool($definition->is_readonly)) + /> +@elseif ($type === 'number') + @load('../assets/number.js') + is_required)) + @disabled(toBool($definition->is_disabled)) + @readonly(toBool($definition->is_readonly)) + /> +@else + is_required)) + @disabled(toBool($definition->is_disabled)) + @readonly(toBool($definition->is_readonly)) + /> +@endif diff --git a/modules/extravar/skins/default/form_types/textarea.blade.php b/modules/extravar/skins/default/form_types/textarea.blade.php new file mode 100644 index 000000000..f016cdc3b --- /dev/null +++ b/modules/extravar/skins/default/form_types/textarea.blade.php @@ -0,0 +1,7 @@ + diff --git a/modules/extravar/skins/default/skin.xml b/modules/extravar/skins/default/skin.xml new file mode 100644 index 000000000..1f6401385 --- /dev/null +++ b/modules/extravar/skins/default/skin.xml @@ -0,0 +1,13 @@ + + + 기본 스킨 + Default Skin + 기존 버전과 호환되는 확장변수 입력란 코드를 제공합니다. + Provides input form codes compatible with older versions. + 1.0 + 2024-04-01 + + Rhymix + Rhymix + + diff --git a/modules/extravar/views/config.blade.php b/modules/extravar/views/config.blade.php new file mode 100644 index 000000000..f270e9d77 --- /dev/null +++ b/modules/extravar/views/config.blade.php @@ -0,0 +1,30 @@ +@include ('header.blade.php') + + + + + + + @if ($XE_VALIDATOR_MESSAGE && $XE_VALIDATOR_ID === 'modules/extravar/views/config/1') +
                +

                {{ $XE_VALIDATOR_MESSAGE }}

                +
                + @endif +
                + +
                + +
                +
                +
                + + + +
                + diff --git a/modules/extravar/views/header.blade.php b/modules/extravar/views/header.blade.php new file mode 100644 index 000000000..93fa57982 --- /dev/null +++ b/modules/extravar/views/header.blade.php @@ -0,0 +1,8 @@ +
                +

                {{ $lang->extra_vars }}

                +
                + diff --git a/modules/file/conf/info.xml b/modules/file/conf/info.xml index 3da6e64c9..bf26107e5 100644 --- a/modules/file/conf/info.xml +++ b/modules/file/conf/info.xml @@ -1,25 +1,25 @@ - 첨부파일 + 파일 附件管理 - Attachment + File Đính kèm - Adjuntar archivos - 添付ファイル + Archivos + ファイル Вложения 附加檔案 Ekler - 첨부 파일을 관리합니다. + 문서 등에 첨부된 파일을 관리합니다. 管理附件的模块。 - Managing attachments. + Manage files attached to documents and other data. Module quản lý File đính kèm. Módulo para manejar los archivos adjuntos. 添付ファイルを管理するモジュールです。 Модуль для управления вложениями. 管理附加檔案的模組。 Ekleri yönetmek için kullanılan modüldür. - 1.7 - 2013-11-27 + RX_VERSION + RX_CORE content diff --git a/modules/file/conf/module.xml b/modules/file/conf/module.xml index e43dbd322..fa7dac308 100644 --- a/modules/file/conf/module.xml +++ b/modules/file/conf/module.xml @@ -3,24 +3,42 @@ - + - + - + - - + + + + + + + - - + + + + + + + + + + + + + + + File diff --git a/modules/file/file.admin.controller.php b/modules/file/file.admin.controller.php index 46ba168a4..1e5ae2e6d 100644 --- a/modules/file/file.admin.controller.php +++ b/modules/file/file.admin.controller.php @@ -4,7 +4,7 @@ * admin controller class of the file module * @author NAVER (developers@xpressengine.com) */ -class fileAdminController extends file +class FileAdminController extends File { /** * Initialization @@ -48,30 +48,80 @@ class fileAdminController extends file $oFileController->deleteFile($file_srl); } - $this->setMessage( sprintf(lang('msg_checked_file_is_deleted'), $file_count) ); + $this->setMessage(sprintf(lang('msg_checked_file_is_deleted'), $file_count)); - $returnUrl = Context::get('success_return_url') ? Context::get('success_return_url') : getNotEncodedUrl('', 'module', 'admin', 'act', 'dispFileAdminList'); - $this->setRedirectUrl($returnUrl); + $redirect_url = $_SERVER['HTTP_REFERER'] ?? ''; + if (!$redirect_url || !Rhymix\Framework\URL::isInternalURL($redirect_url)) + { + $redirect_url = Context::get('success_return_url') ?: getNotEncodedUrl('', 'module', 'admin', 'act', 'dispFileAdminList'); + } + $this->setRedirectUrl($redirect_url); } /** - * Add file information + * Save upload configuration * * @return Object */ - function procFileAdminInsertConfig() + function procFileAdminInsertUploadConfig() { - // Get configurations (using module model object) - $config = new stdClass(); + // Default settings + $config = getModel('module')->getModuleConfig('file') ?: new stdClass; $config->allowed_filesize = Context::get('allowed_filesize'); $config->allowed_attach_size = Context::get('allowed_attach_size'); - $config->allowed_filetypes = str_replace(' ', '', Context::get('allowed_filetypes')); - $config->allow_outlink = Context::get('allow_outlink'); - $config->allow_outlink_format = Context::get('allow_outlink_format'); - $config->allow_outlink_site = Context::get('allow_outlink_site'); - $config->inline_download_format = array_map('utf8_trim', Context::get('inline_download_format')); - - // Check maximum file size + $config->allowed_filetypes = Context::get('allowed_filetypes'); + + // Image settings + $config->image_autoconv = []; + foreach (Context::get('image_autoconv') ?: [] as $source_type => $target_type) + { + if (in_array($target_type, ['Y', 'N'])) + { + $config->image_autoconv[$source_type] = tobool($target_type); + } + elseif (in_array($target_type, ['', 'jpg', 'png', 'webp'])) + { + $config->image_autoconv[$source_type] = $target_type; + } + } + $config->max_image_width = intval(Context::get('max_image_width')) ?: ''; + $config->max_image_height = intval(Context::get('max_image_height')) ?: ''; + $config->max_image_size_action = Context::get('max_image_size_action') ?: ''; + $config->max_image_size_same_format = strval(Context::get('max_image_size_same_format')); + $config->max_image_size_admin = Context::get('max_image_size_admin') === 'Y' ? 'Y' : 'N'; + $config->image_quality_adjustment = max(50, min(100, intval(Context::get('image_quality_adjustment')))); + $config->image_autorotate = Context::get('image_autorotate') === 'Y' ? true : false; + $config->image_remove_exif_data = Context::get('image_remove_exif_data') === 'Y' ? true : false; + $config->image_always_reencode = Context::get('image_always_reencode') === 'Y' ? true : false; + + // Video settings + $config->max_video_width = intval(Context::get('max_video_width')) ?: ''; + $config->max_video_height = intval(Context::get('max_video_height')) ?: ''; + $config->max_video_size_action = Context::get('max_video_size_action') ?: ''; + $config->max_video_size_admin = Context::get('max_video_size_admin') === 'Y' ? 'Y' : 'N'; + $config->max_video_duration = intval(Context::get('max_video_duration')) ?: ''; + $config->max_video_duration_action = Context::get('max_video_duration_action') ?: ''; + $config->max_video_duration_admin = Context::get('max_video_duration_admin') === 'Y' ? 'Y' : 'N'; + $config->video_autoconv['any2mp4'] = Context::get('video_autoconv_any2mp4') === 'Y' ? true : false; + $config->video_always_reencode = Context::get('video_always_reencode') === 'Y' ? true : false; + $config->video_thumbnail = Context::get('video_thumbnail') === 'Y' ? true : false; + $config->video_mp4_gif_time = intval(Context::get('video_mp4_gif_time')); + + // Path to ffmpeg, ffprobe, magick + if (RX_WINDOWS) + { + $config->ffmpeg_command = escape(Context::get('ffmpeg_command')) ?: 'C:\Program Files\ffmpeg\bin\ffmpeg.exe'; + $config->ffprobe_command = escape(Context::get('ffprobe_command')) ?: 'C:\Program Files\ffmpeg\bin\ffprobe.exe'; + $config->magick_command = escape(Context::get('magick_command')) ?: ''; + } + else + { + $config->ffmpeg_command = escape(utf8_trim(Context::get('ffmpeg_command'))) ?: '/usr/bin/ffmpeg'; + $config->ffprobe_command = escape(utf8_trim(Context::get('ffprobe_command'))) ?: '/usr/bin/ffprobe'; + $config->magick_command = escape(utf8_trim(Context::get('magick_command'))) ?: ''; + } + + // Check maximum file size (probably not necessary anymore) if (PHP_INT_SIZE < 8) { if ($config->allowed_filesize > 2047 || $config->allowed_attach_size > 2047) @@ -79,12 +129,67 @@ class fileAdminController extends file throw new Rhymix\Framework\Exception('msg_32bit_max_2047mb'); } } - - // Create module Controller object - $oModuleController = getController('module'); - $output = $oModuleController->insertModuleConfig('file',$config); - $returnUrl = Context::get('success_return_url') ? Context::get('success_return_url') : getNotEncodedUrl('', 'module', 'admin', 'act', 'dispFileAdminConfig'); + // Simplify allowed_filetypes + $config->allowed_extensions = strtr(strtolower(trim($config->allowed_filetypes)), array('*.' => '', ';' => ',')); + if ($config->allowed_extensions) + { + $config->allowed_extensions = array_filter(array_map('trim', explode(',', $config->allowed_extensions)), function($str) { + return $str !== '*'; + }); + $config->allowed_filetypes = implode(';', array_map(function($ext) { + return '*.' . $ext; + }, $config->allowed_extensions)); + } + else + { + $config->allowed_extensions = array(); + $config->allowed_filetypes = '*.*'; + } + + // Save and redirect + $output = getController('module')->insertModuleConfig('file', $config); + $returnUrl = Context::get('success_return_url') ?: getNotEncodedUrl('', 'module', 'admin', 'act', 'dispFileAdminUploadConfig'); + return $this->setRedirectUrl($returnUrl, $output); + } + + /** + * Save download configuration + * + * @return Object + */ + function procFileAdminInsertDownloadConfig() + { + // Update configuration + $config = getModel('module')->getModuleConfig('file') ?: new stdClass; + $config->allow_outlink = Context::get('allow_outlink') === 'N' ? 'N' : 'Y'; + $config->allow_outlink_format = Context::get('allow_outlink_format'); + $config->allow_outlink_site = Context::get('allow_outlink_site'); + $config->allow_indexing_format = Context::get('allow_indexing_format'); + $config->allow_multimedia_direct_download = Context::get('allow_multimedia_direct_download') === 'Y' ? 'Y' : 'N'; + $config->download_short_url = Context::get('download_short_url') === 'Y' ? 'Y' : 'N'; + $config->inline_download_format = array_map('utf8_trim', Context::get('inline_download_format') ?: []); + + // Save and redirect + $output = getController('module')->insertModuleConfig('file', $config); + $returnUrl = Context::get('success_return_url') ?: getNotEncodedUrl('', 'module', 'admin', 'act', 'dispFileAdminDownloadConfig'); + return $this->setRedirectUrl($returnUrl, $output); + } + + /** + * Save other configuration + * + * @return Object + */ + function procFileAdminInsertOtherConfig() + { + // Update configuration + $config = getModel('module')->getModuleConfig('file') ?: new stdClass; + $config->save_changelog = Context::get('save_changelog') === 'Y' ? 'Y' : 'N'; + + // Save and redirect + $output = getController('module')->insertModuleConfig('file', $config); + $returnUrl = Context::get('success_return_url') ?: getNotEncodedUrl('', 'module', 'admin', 'act', 'dispFileAdminOtherConfig'); return $this->setRedirectUrl($returnUrl, $output); } @@ -95,53 +200,94 @@ class fileAdminController extends file */ function procFileAdminInsertModuleConfig() { - // Get variables - $module_srl = Context::get('target_module_srl'); - // In order to configure multiple modules at once - if(preg_match('/^([0-9,]+)$/',$module_srl)) $module_srl = explode(',',$module_srl); - else $module_srl = array($module_srl); + $config = new stdClass; - $download_grant = Context::get('download_grant'); - - $file_config = new stdClass; - $file_config->allow_outlink = Context::get('allow_outlink'); - $file_config->allow_outlink_format = Context::get('allow_outlink_format'); - $file_config->allow_outlink_site = Context::get('allow_outlink_site'); - $file_config->allowed_filesize = Context::get('allowed_filesize'); - $file_config->allowed_attach_size = Context::get('allowed_attach_size'); - $file_config->allowed_filetypes = str_replace(' ', '', Context::get('allowed_filetypes')); - - if(!is_array($download_grant)) + // Default + if(!Context::get('use_default_file_config')) { - $file_config->download_grant = explode('|@|',$download_grant); - } - else - { - $file_config->download_grant = array_values($download_grant); - } + $config->use_default_file_config = 'N'; + $config->allowed_filesize = Context::get('allowed_filesize'); + $config->allowed_attach_size = Context::get('allowed_attach_size'); + $config->allowed_filetypes = Context::get('allowed_filetypes'); - // Check maximum file size - if (PHP_INT_SIZE < 8) - { - if ($file_config->allowed_filesize > 2047 || $file_config->allowed_attach_size > 2047) + // Check maximum file size + if (PHP_INT_SIZE < 8) { - throw new Rhymix\Framework\Exception('msg_32bit_max_2047mb'); + if ($config->allowed_filesize > 2047 || $config->allowed_attach_size > 2047) + { + throw new Rhymix\Framework\Exception('msg_32bit_max_2047mb'); + } + } + + // Simplify allowed_filetypes + $config->allowed_extensions = strtr(strtolower(trim($config->allowed_filetypes)), array('*.' => '', ';' => ',')); + if ($config->allowed_extensions) + { + $config->allowed_extensions = array_filter(array_map('trim', explode(',', $config->allowed_extensions)), function($str) { + return $str !== '*'; + }); + $config->allowed_filetypes = implode(';', array_map(function($ext) { + return '*.' . $ext; + }, $config->allowed_extensions)); + } + else + { + $config->allowed_extensions = array(); + $config->allowed_filetypes = '*.*'; } } - - $oModuleController = getController('module'); - for($i=0;$iinsertModulePartConfig('file',$srl,$file_config); + $config->use_image_default_file_config = 'N'; + foreach (Context::get('image_autoconv') ?: [] as $source_type => $target_type) + { + if (in_array($target_type, ['Y', 'N'])) + { + $config->image_autoconv[$source_type] = tobool($target_type); + } + elseif (in_array($target_type, ['', 'jpg', 'png', 'webp'])) + { + $config->image_autoconv[$source_type] = $target_type; + } + } + $config->max_image_width = intval(Context::get('max_image_width')) ?: ''; + $config->max_image_height = intval(Context::get('max_image_height')) ?: ''; + $config->max_image_size_action = Context::get('max_image_size_action') ?: ''; + $config->max_image_size_same_format = strval(Context::get('max_image_size_same_format')); + $config->max_image_size_admin = Context::get('max_image_size_admin') === 'Y' ? 'Y' : 'N'; + $config->image_quality_adjustment = max(50, min(100, intval(Context::get('image_quality_adjustment')))); + $config->image_autorotate = Context::get('image_autorotate') === 'Y' ? true : false; + $config->image_remove_exif_data = Context::get('image_remove_exif_data') === 'Y' ? true : false; + } + + // Video + if(!Context::get('use_video_default_file_config')) + { + $config->use_video_default_file_config = 'N'; + $config->video_thumbnail = Context::get('video_thumbnail') === 'Y' ? true : false; + $config->video_mp4_gif_time = intval(Context::get('video_mp4_gif_time')); + } + + // Set download groups + $download_grant = Context::get('download_grant'); + $config->download_grant = is_array($download_grant) ? array_values($download_grant) : array($download_grant); + + // Update + $oModuleController = getController('module'); + foreach(explode(',', Context::get('target_module_srl')) as $module_srl) + { + $output = $oModuleController->insertModulePartConfig('file', trim($module_srl), $config); + if(!$output->toBool()) + { + return $output; + } } $this->setError(-1); $this->setMessage('success_updated', 'info'); - - $returnUrl = Context::get('success_return_url') ? Context::get('success_return_url') : getNotEncodedUrl('', 'module', 'admin', 'act', 'dispBoardAdminContent'); - $this->setRedirectUrl($returnUrl); + $this->setRedirectUrl(Context::get('success_return_url') ?: getNotEncodedUrl('', 'module', 'admin', 'act', 'dispBoardAdminContent')); } /** @@ -164,6 +310,172 @@ class fileAdminController extends file else $_SESSION['file_management'][$output->file_srl] = true; } } + + /** + * Edit filename + */ + public function procFileAdminEditFileName() + { + $file_srl = Context::get('file_srl'); + if (!$file_srl) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + $file = FileModel::getFile($file_srl); + if (!$file) + { + throw new Rhymix\Framework\Exceptions\TargetNotFound; + } + $file_name = trim(utf8_normalize_spaces(utf8_clean(Context::get('file_name')))); + $file_name = Rhymix\Framework\Filters\FilenameFilter::clean($file_name); + if ($file_name === '') + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + + $output = executeQuery('file.updateFileName', [ + 'file_srl' => $file_srl, + 'source_filename' => $file_name, + ]); + if (!$output->toBool()) + { + return $output; + } + + $this->setMessage('success_updated'); + $this->setRedirectUrl(Context::get('success_return_url') ?: getNotEncodedUrl(['module' => 'admin', 'act' => 'dispFileAdminEdit', 'file_srl' => $file_srl])); + } + + /** + * Edit image + */ + public function procFileAdminEditImage() + { + $file_srl = Context::get('file_srl'); + if (!$file_srl) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + $file = FileModel::getFile($file_srl); + if (!$file) + { + throw new Rhymix\Framework\Exceptions\TargetNotFound; + } + + // Validate user input. + $width = intval(Context::get('new_width')); + $height = intval(Context::get('new_height')); + $format = Context::get('format'); + $quality = intval(Context::get('quality')); + if ($width <= 0 || $height <= 0 || !in_array($format, ['jpg', 'png', 'webp']) || $quality <= 0 || $quality > 100) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + + // Generate filenames. + $uploaded_filename = FileHandler::getRealPath($file->uploaded_filename); + $temp_filename = \RX_BASEDIR . 'files/cache/temp/' . Rhymix\Framework\Security::getRandom(32, 'hex') . '.' . $format; + $new_filename = preg_replace('/\.[a-z]+$/', '.' . $format, $file->source_filename); + $del_filename = null; + + // Should this file be moved from binaries to images? + if (str_starts_with($uploaded_filename, \RX_BASEDIR . 'files/attach/binaries/')) + { + $del_filename = $uploaded_filename; + $uploaded_filename = preg_replace('!/files/attach/binaries/!', '/files/attach/images/', $uploaded_filename, 1) . '.' . $format; + } + + // Resize the image using GD or ImageMagick. + $config = FileModel::getFileConfig(); + $result = FileHandler::createImageFile(FileHandler::getRealPath($file->uploaded_filename), $temp_filename, $width, $height, $format, 'fill', $quality); + if (!$result && !empty($config->magick_command)) + { + $temp_dir = dirname($temp_filename); + if (!Rhymix\Framework\Storage::isDirectory($temp_dir)) + { + Rhymix\Framework\Storage::createDirectory($temp_dir); + } + $command = vsprintf('%s %s -resize %dx%d -quality %d %s %s %s', [ + \RX_WINDOWS ? escapeshellarg($config->magick_command) : $config->magick_command, + escapeshellarg(FileHandler::getRealPath($file->uploaded_filename)), + $width, $height, $quality, + '-auto-orient -strip', + '-limit memory 64MB -limit map 128MB -limit disk 1GB', + escapeshellarg($temp_filename), + ]); + @exec($command, $output, $return_var); + $result = $return_var === 0 ? true : false; + } + + // If successfully resized, replace original file and update the image size in DB. + if ($result && Rhymix\Framework\Storage::exists($temp_filename) && filesize($temp_filename) > 0) + { + $moved = Rhymix\Framework\Storage::move($temp_filename, $uploaded_filename); + if (!$moved) + { + throw new Rhymix\Framework\Exception(lang('file.msg_image_conversion_failed')); + } + if ($del_filename) + { + Rhymix\Framework\Storage::delete($del_filename); + } + + clearstatcache(true, $uploaded_filename); + $filesize = filesize($uploaded_filename); + $relative_path = preg_replace('!^' . preg_quote(\RX_BASEDIR, '!') . '!', './', $uploaded_filename, 1); + + $updated = executeQuery('file.updateFile', [ + 'file_srl' => $file_srl, + 'module_srl' => $file->module_srl, + 'upload_target_srl' => $file->upload_target_srl, + 'source_filename' => $new_filename, + 'uploaded_filename' => $relative_path, + 'direct_download' => 'Y', + 'mime_type' => Rhymix\Framework\MIME::getTypeByFilename($new_filename), + 'original_type' => $file->original_type ?: $file->mime_type, + 'is_cover' => $file->cover_image, + 'file_size' => $filesize, + 'width' => $width, + 'height' => $height, + ]); + if (!$updated->toBool()) + { + return $updated; + } + + if (isset($config->save_changelog) && $config->save_changelog === 'Y') + { + $changelog1 = executeQuery('file.insertFileChangelog', [ + 'change_type' => 'D', + 'file_srl' => $file->file_srl, + 'file_size' => $file->file_size, + 'uploaded_filename' => $file->uploaded_filename, + ]); + if (!$changelog1->toBool()) + { + return $changelog1; + } + + $changelog2 = executeQuery('file.insertFileChangelog', [ + 'change_type' => 'I', + 'file_srl' => $file->file_srl, + 'file_size' => $filesize, + 'uploaded_filename' => $relative_path, + ]); + if (!$changelog2->toBool()) + { + return $changelog2; + } + } + } + else + { + throw new Rhymix\Framework\Exception(lang('file.msg_image_conversion_failed')); + } + + $this->setMessage(sprintf(lang('file.msg_image_converted'), FileHandler::filesize($file->file_size), FileHandler::filesize($filesize))); + $this->setRedirectUrl(Context::get('success_return_url') ?: getNotEncodedUrl(['module' => 'admin', 'act' => 'dispFileAdminEdit', 'file_srl' => $file_srl])); + } } /* End of file file.admin.controller.php */ /* Location: ./modules/file/file.admin.controller.php */ diff --git a/modules/file/file.admin.model.php b/modules/file/file.admin.model.php index 1bb074c63..a33989d58 100644 --- a/modules/file/file.admin.model.php +++ b/modules/file/file.admin.model.php @@ -4,13 +4,13 @@ * Admin model class of the file module * @author NAVER (developers@xpressengine.com) */ -class fileAdminModel extends file +class FileAdminModel extends File { /** * Initialization * @return void */ - function init() + public function init() { } @@ -55,52 +55,66 @@ class fileAdminModel extends file * - isvaild * - regdate * - ipaddress - * + * * * * @param object $obj Search options * @param array $columnList Column list to get from DB * @return Object Object contains query result */ - function getFileList($obj, $columnList = array()) + public function getFileList($obj, $columnList = array()) { $args = new stdClass(); $this->_makeSearchParam($obj, $args); // Set valid/invalid state - if($obj->isvalid == 'Y') $args->isvalid = 'Y'; - elseif($obj->isvalid == 'N') $args->isvalid = 'N'; + if(isset($obj->isvalid) && in_array($obj->isvalid, ['Y', 'N'])) + { + $args->isvalid = $obj->isvalid; + } + // Set multimedia/common file - if($obj->direct_download == 'Y') $args->direct_download = 'Y'; - elseif($obj->direct_download == 'N') $args->direct_download= 'N'; + if(isset($obj->direct_download) && in_array($obj->direct_download, ['Y', 'N'])) + { + $args->direct_download = $obj->direct_download; + } + // Set variables - $args->sort_index = $obj->sort_index; - $args->page = $obj->page?$obj->page:1; - $args->list_count = $obj->list_count?$obj->list_count:20; - $args->page_count = $obj->page_count?$obj->page_count:10; - $args->s_module_srl = $obj->module_srl; - $args->exclude_module_srl = $obj->exclude_module_srl; - if(toBool($obj->exclude_secret)) + $args->sort_index = $obj->sort_index ?? null; + $args->order_type = $obj->order_type ?? 'desc'; + $args->page = isset($obj->page) ? ($obj->page ? $obj->page : 1) : 1; + $args->list_count = isset($obj->list_count) ? ($obj->list_count? $obj->list_count : 20) : 20; + $args->page_count = isset($obj->page_count) ? ($obj->page_count? $obj->page_count : 10) : 10; + $args->s_module_srl = $obj->module_srl ?? null; + $args->exclude_module_srl = $obj->exclude_module_srl ?? null; + if(toBool($obj->exclude_secret ?? null)) { $args->document_status = array('PUBLIC'); $args->comment_is_secret = array('N'); - $output = executeQuery('file.getFileListByTargetStatus', $args, $columnList); + $output = executeQueryArray('file.getFileListByTargetStatus', $args, $columnList); } else { - $output = executeQuery('file.getFileList', $args, $columnList); + $output = executeQueryArray('file.getFileList', $args, $columnList); } - // Return if no result or an error occurs - if(!$output->toBool()||!count($output->data)) return $output; - $oFileModel = getModel('file'); + // Return if no result or an error occurs + if(!$output->toBool() || !count($output->data)) + { + return $output; + } foreach($output->data as $key => $file) { - if($_SESSION['file_management'][$file->file_srl]) $file->isCarted = true; - else $file->isCarted = false; - - $file->download_url = $oFileModel->getDownloadUrl($file->file_srl, $file->sid, $file->module_srl); + if(isset($_SESSION['file_management'][$file->file_srl]) && $_SESSION['file_management'][$file->file_srl]) + { + $file->isCarted = true; + } + else + { + $file->isCarted = false; + } + $file->download_url = FileModel::getDownloadUrl($file->file_srl, $file->sid, 0, $file->source_filename); $output->data[$key] = $file; } @@ -124,7 +138,7 @@ class fileAdminModel extends file * @param object $obj Search options (not used...) * @return array */ - function getFilesCountByGroupValid($obj = '') + public function getFilesCountByGroupValid($obj = '') { //$this->_makeSearchParam($obj, $args); @@ -138,7 +152,7 @@ class fileAdminModel extends file * @param string $date Date string * @return int */ - function getFilesCountByDate($date = '') + public function getFilesCountByDate($date = '') { $args = new stdClass(); if($date) @@ -162,18 +176,17 @@ class fileAdminModel extends file * @param object $args Result searach options * @return void */ - function _makeSearchParam(&$obj, &$args) + protected function _makeSearchParam(&$obj, &$args) { // Search options - $search_target = $obj->search_target?$obj->search_target:trim(Context::get('search_target')); - $search_keyword = $obj->search_keyword?$obj->search_keyword:trim(Context::get('search_keyword')); + $search_target = isset($obj->search_target)? ($obj->search_target? $obj->search_target : trim(Context::get('search_target'))) : trim(Context::get('search_target') ?? ''); + $search_keyword = isset($obj->search_keyword)? ($obj->search_keyword? $obj->search_keyword : trim(Context::get('search_keyword'))) : trim(Context::get('search_keyword') ?? ''); if($search_target && $search_keyword) { switch($search_target) { case 'filename' : - if($search_keyword) $search_keyword = str_replace(' ','%',$search_keyword); $args->s_filename = $search_keyword; break; case 'filesize_more' : @@ -191,6 +204,9 @@ class fileAdminModel extends file case 'download_count' : $args->s_download_count = (int)$search_keyword; break; + case 'download_count_less' : + $args->s_download_count_less = (int)$search_keyword; + break; case 'regdate' : $args->s_regdate = $search_keyword; break; diff --git a/modules/file/file.admin.view.php b/modules/file/file.admin.view.php index 8ae9804b0..abd23c5b1 100644 --- a/modules/file/file.admin.view.php +++ b/modules/file/file.admin.view.php @@ -4,22 +4,14 @@ * Admin view of the module class file * @author NAVER (developers@xpressengine.com) */ -class fileAdminView extends file +class FileAdminView extends File { - /** - * Initialization - * @return void - */ - function init() - { - } - /** * Display output list (for administrator) * * @return Object */ - function dispFileAdminList() + public function dispFileAdminList() { // Options to get a list $args = new stdClass(); @@ -27,15 +19,14 @@ class fileAdminView extends file $args->list_count = 30; // /< Number of documents that appear on a single page $args->page_count = 10; // /< Number of pages that appear in the page navigation - $args->sort_index = 'file_srl'; // /< Sorting values + $args->sort_index = Context::get('sort_index') ?? 'file_srl'; // /< Sorting values + $args->order_type = Context::get('order_type') ?? null; $args->isvalid = Context::get('isvalid'); $args->module_srl = Context::get('module_srl'); // Get a list $oFileAdminModel = getAdminModel('file'); - $columnList = array('file_srl', 'upload_target_srl', 'upload_target_type', 'sid', 'files.module_srl' - , 'source_filename', 'isvalid', 'file_size', 'download_count', 'files.regdate', 'files.ipaddress', 'member.member_srl', 'member.nick_name', 'uploaded_filename'); - $output = $oFileAdminModel->getFileList($args, $columnList); - + $output = $oFileAdminModel->getFileList($args); + // Get the document for looping a list if($output->data) { @@ -46,11 +37,11 @@ class fileAdminView extends file $file_list = array(); $document_list = array(); $comment_list = array(); - $module_list= array(); + $module_list = array(); $doc_srls = array(); $com_srls = array(); - $mod_srls= array(); + $mod_srls = array(); foreach($output->data as $file) { @@ -61,16 +52,16 @@ class fileAdminView extends file // Find and update if upload_target_type doesn't exist if(!$file->upload_target_type) { - // Pass if upload_target_type is already found - if($document_list[$target_srl]) + // Pass if upload_target_type is already found + if(isset($document_list[$target_srl])) { $file->upload_target_type = 'doc'; } - else if($comment_list[$target_srl]) + elseif(isset($comment_list[$target_srl])) { $file->upload_target_type = 'com'; } - else if($module_list[$target_srl]) + elseif(isset($module_list[$target_srl])) { $file->upload_target_type = 'mod'; } @@ -108,36 +99,40 @@ class fileAdminView extends file $module_list[$module->comment_srl] = $module; } } - if($file_update_args->upload_target_type) + if(isset($file_update_args->upload_target_type) && $file_update_args->upload_target_type) { executeQuery('file.updateFileTargetType', $file_update_args); } } // Check if data is already obtained - for($i = 0; $i < $com_srls_count; ++$i) + foreach($com_srls as $i => $com_srl) { - if($comment_list[$com_srls[$i]]) delete($com_srls[$i]); + if(isset($comment_list[$com_srl])) unset($com_srls[$i]); } - for($i = 0; $i < $doc_srls_count; ++$i) + foreach($doc_srls as $i => $doc_srl) { - if($document_list[$doc_srls[$i]]) delete($doc_srls[$i]); + if(isset($document_list[$doc_srl])) unset($doc_srls[$i]); } - for($i = 0; $i < $mod_srls_count; ++$i) + foreach($mod_srls as $i => $mod_srl) { - if($module_list[$mod_srls[$i]]) delete($mod_srls[$i]); + if(isset($module_list[$mod_srl])) unset($mod_srls[$i]); } } - if($file->upload_target_type && is_array(${$file->upload_target_type.'_srls'})) + if (in_array($file->upload_target_type, ['doc', 'com', 'ev:doc', 'ev:com'])) { - if(!in_array($file->upload_target_srl, ${$file->upload_target_type.'_srls'})) + $var = str_replace('ev:', '', $file->upload_target_type) . '_srls'; + if (!in_array($target_srl, $$var)) { - ${$file->upload_target_type.'_srls'}[] = $target_srl; + $$var[] = $target_srl; } } + if (!in_array($file->module_srl, $mod_srls)) + { + $mod_srls[] = $file->module_srl; + } $file_list[$file_srl] = $file; - $mod_srls[] = $file->module_srl; } // Remove duplication $doc_srls = array_unique($doc_srls); @@ -167,30 +162,25 @@ class fileAdminView extends file } } } - // Module List - $mod_srls_count = count($mod_srls); - if($mod_srls_count) - { - $columnList = array('module_srl', 'mid', 'browser_title'); - $module_output = $oModuleModel->getModulesInfo($mod_srls, $columnList); - if($module_output && is_array($module_output)) - { - foreach($module_output as $module) - { - $module_list[$module->module_srl] = $module; - } - } - } foreach($file_list as $srl => $file) { - if($file->upload_target_type == 'com') + if($file->upload_target_type === 'com' || $file->upload_target_type === 'ev:com') { $file_list[$srl]->target_document_srl = $comment_list[$file->upload_target_srl]->document_srl; } } } + // Module list + $mod_output = executeQueryArray('comment.getModuleList'); + foreach ($mod_output->data as $item) + { + $item->browser_title = Context::replaceUserLang($item->browser_title); + $module_list[$item->module_srl] = $item; + } + Context::set('module_list', $module_list); + Context::set('file_list', $file_list); Context::set('document_list', $document_list); Context::set('comment_list', $comment_list); @@ -210,19 +200,74 @@ class fileAdminView extends file } /** - * Set attachment information (for administrator) - * - * @return Object + * File edit screen */ - function dispFileAdminConfig() + public function dispFileAdminEdit() + { + $file_srl = intval(Context::get('file_srl')); + if (!$file_srl) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + $file = FileModel::getFile($file_srl); + if (!$file) + { + throw new Rhymix\Framework\Exceptions\TargetNotFound; + } + + $config = FileModel::getFileConfig(); + Context::set('config', $config); + Context::set('file', $file); + Context::set('is_ffmpeg', function_exists('exec') && !empty($config->ffmpeg_command) && Rhymix\Framework\Storage::isExecutable($config->ffmpeg_command) && !empty($config->ffprobe_command) && Rhymix\Framework\Storage::isExecutable($config->ffprobe_command)); + Context::set('is_magick', function_exists('exec') && !empty($config->magick_command) && Rhymix\Framework\Storage::isExecutable($config->magick_command)); + + $this->setTemplatePath($this->module_path . 'tpl'); + $this->setTemplateFile('file_edit'); + } + + /** + * Upload config screen + */ + public function dispFileAdminUploadConfig() + { + $oFileModel = getModel('file'); + $config = $oFileModel->getFileConfig(); + Context::set('config', $config); + Context::set('is_ffmpeg', function_exists('exec') && !empty($config->ffmpeg_command) && Rhymix\Framework\Storage::isExecutable($config->ffmpeg_command) && !empty($config->ffprobe_command) && Rhymix\Framework\Storage::isExecutable($config->ffprobe_command)); + Context::set('is_magick', function_exists('exec') && !empty($config->magick_command) && Rhymix\Framework\Storage::isExecutable($config->magick_command)); + Context::set('is_exec_available', function_exists('exec')); + + // Set a template file + $this->setTemplatePath($this->module_path.'tpl'); + $this->setTemplateFile('upload_config'); + } + + /** + * Download config screen + */ + public function dispFileAdminDownloadConfig() { $oFileModel = getModel('file'); $config = $oFileModel->getFileConfig(); Context::set('config',$config); - + // Set a template file $this->setTemplatePath($this->module_path.'tpl'); - $this->setTemplateFile('adminConfig'); + $this->setTemplateFile('download_config'); + } + + /** + * Other config screen + */ + public function dispFileAdminOtherConfig() + { + $oFileModel = getModel('file'); + $config = $oFileModel->getFileConfig(); + Context::set('config',$config); + + // Set a template file + $this->setTemplatePath($this->module_path.'tpl'); + $this->setTemplateFile('other_config'); } } /* End of file file.admin.view.php */ diff --git a/modules/file/file.class.php b/modules/file/file.class.php index 67032e4ce..c335ae354 100644 --- a/modules/file/file.class.php +++ b/modules/file/file.class.php @@ -4,7 +4,7 @@ * High class of the file module * @author NAVER (developers@xpressengine.com) */ -class file extends ModuleObject +class File extends ModuleObject { /** * Implement if additional tasks are necessary when installing @@ -13,35 +13,9 @@ class file extends ModuleObject */ function moduleInstall() { - // Register action forward (to use in administrator mode) - $oModuleController = getController('module'); - - // Save the default settings for attachments - $config = new stdClass; - $config->allowed_filesize = '2'; - $config->allowed_attach_size = '2'; - $config->allowed_filetypes = '*.*'; - $oModuleController->insertModuleConfig('file', $config); // Generate a directory for the file module FileHandler::makeDir('./files/attach/images'); FileHandler::makeDir('./files/attach/binaries'); - // 2007. 10. 17 Create a trigger to insert, update, delete documents and comments - $oModuleController->insertTrigger('document.insertDocument', 'file', 'controller', 'triggerCheckAttached', 'before'); - $oModuleController->insertTrigger('document.insertDocument', 'file', 'controller', 'triggerAttachFiles', 'after'); - $oModuleController->insertTrigger('document.updateDocument', 'file', 'controller', 'triggerCheckAttached', 'before'); - $oModuleController->insertTrigger('document.updateDocument', 'file', 'controller', 'triggerAttachFiles', 'after'); - $oModuleController->insertTrigger('document.deleteDocument', 'file', 'controller', 'triggerDeleteAttached', 'after'); - $oModuleController->insertTrigger('comment.insertComment', 'file', 'controller', 'triggerCommentCheckAttached', 'before'); - $oModuleController->insertTrigger('comment.insertComment', 'file', 'controller', 'triggerCommentAttachFiles', 'after'); - $oModuleController->insertTrigger('comment.updateComment', 'file', 'controller', 'triggerCommentCheckAttached', 'before'); - $oModuleController->insertTrigger('comment.updateComment', 'file', 'controller', 'triggerCommentAttachFiles', 'after'); - $oModuleController->insertTrigger('comment.deleteComment', 'file', 'controller', 'triggerCommentDeleteAttached', 'after'); - // 2009. 6. 9 Delete all the attachements when auto-saved document is deleted - $oModuleController->insertTrigger('editor.deleteSavedDoc', 'file', 'controller', 'triggerDeleteAttached', 'after'); - // 2007. 10. 17 Create a trigger to delete all the attachements when the module is deleted - $oModuleController->insertTrigger('module.deleteModule', 'file', 'controller', 'triggerDeleteModuleFiles', 'after'); - // 2007. 10. 19 Call a trigger to set up the file permissions before displaying - $oModuleController->insertTrigger('module.dispAdditionSetup', 'file', 'view', 'triggerDispFileAdditionSetup', 'before'); } /** @@ -51,46 +25,69 @@ class file extends ModuleObject */ function checkUpdate() { - $oDB = &DB::getInstance(); - $oModuleModel = getModel('module'); - // 2007. 10. 17 Create a trigger to insert, update, delete documents and comments - if(!$oModuleModel->getTrigger('document.insertDocument', 'file', 'controller', 'triggerCheckAttached', 'before')) return true; - if(!$oModuleModel->getTrigger('document.insertDocument', 'file', 'controller', 'triggerAttachFiles', 'after')) return true; - if(!$oModuleModel->getTrigger('document.updateDocument', 'file', 'controller', 'triggerCheckAttached', 'before')) return true; - if(!$oModuleModel->getTrigger('document.updateDocument', 'file', 'controller', 'triggerAttachFiles', 'after')) return true; - if(!$oModuleModel->getTrigger('document.deleteDocument', 'file', 'controller', 'triggerDeleteAttached', 'after')) return true; - if(!$oModuleModel->getTrigger('comment.insertComment', 'file', 'controller', 'triggerCommentCheckAttached', 'before')) return true; - if(!$oModuleModel->getTrigger('comment.insertComment', 'file', 'controller', 'triggerCommentAttachFiles', 'after')) return true; - if(!$oModuleModel->getTrigger('comment.updateComment', 'file', 'controller', 'triggerCommentCheckAttached', 'before')) return true; - if(!$oModuleModel->getTrigger('comment.updateComment', 'file', 'controller', 'triggerCommentAttachFiles', 'after')) return true; - if(!$oModuleModel->getTrigger('comment.deleteComment', 'file', 'controller', 'triggerCommentDeleteAttached', 'after')) return true; - // 2009. 6. 9 Delete all the attachements when auto-saved document is deleted - if(!$oModuleModel->getTrigger('editor.deleteSavedDoc', 'file', 'controller', 'triggerDeleteAttached', 'after')) return true; - // 2007. 10. 17 Create a trigger to delete all the attachements when the module is deleted - if(!$oModuleModel->getTrigger('module.deleteModule', 'file', 'controller', 'triggerDeleteModuleFiles', 'after')) return true; - // 2007. 10. 19 Call a trigger to set up the file permissions before displaying - if(!$oModuleModel->getTrigger('module.dispAdditionSetup', 'file', 'view', 'triggerDispFileAdditionSetup', 'before')) return true; - // A column to determine a target type - if(!$oDB->isColumnExists('files', 'upload_target_type')) return true; + $oDB = DB::getInstance(); - // 2012. 08. 29 Add a trigger to copy additional setting when the module is copied - if(!$oModuleModel->getTrigger('module.procModuleAdminCopyModule', 'file', 'controller', 'triggerCopyModule', 'after')) return true; + // Check columns + if(!$oDB->isColumnExists('files', 'upload_target_type')) + { + return true; + } + if($oDB->getColumnInfo('files', 'upload_target_type')->size < 20) + { + return true; + } + if(!$oDB->isColumnExists('files', 'cover_image')) + { + return true; + } - if(!$oDB->isColumnExists('files', 'cover_image')) return true; - - if(!$oModuleModel->getTrigger('document.moveDocumentModule', 'file', 'controller', 'triggerMoveDocument', 'after')) + if(!$oDB->isColumnExists('files', 'thumbnail_filename')) { return true; } - if(!$oModuleModel->getTrigger('document.copyDocumentModule', 'file', 'controller', 'triggerAddCopyDocument', 'add')) + if(!$oDB->isColumnExists('files', 'mime_type') || !$oDB->isIndexExists('files', 'idx_mime_type')) { return true; } - if(!$oModuleModel->getTrigger('comment.copyCommentByDocument', 'file', 'controller', 'triggerAddCopyCommentByDocument', 'add')) + if($oDB->getColumnInfo('files', 'mime_type')->size < 100) { return true; } - + if(!$oDB->isColumnExists('files', 'original_type')) + { + return true; + } + if($oDB->getColumnInfo('files', 'original_type')->size < 100) + { + return true; + } + if(!$oDB->isColumnExists('files', 'width')) + { + return true; + } + if(!$oDB->isColumnExists('files', 'height')) + { + return true; + } + if(!$oDB->isColumnExists('files', 'duration')) + { + return true; + } + + // Check indexes + if (!$oDB->isIndexExists('files', 'idx_upload_target_type')) + { + return true; + } + if (!$oDB->isIndexExists('files', 'idx_cover_image')) + { + return true; + } + if ($oDB->isIndexExists('files', 'idx_list_order')) + { + return true; + } + return false; } @@ -101,81 +98,72 @@ class file extends ModuleObject */ function moduleUpdate() { - $oDB = &DB::getInstance(); - $oModuleModel = getModel('module'); - $oModuleController = getController('module'); - // 2007. 10. 17 Create a trigger to insert, update, delete documents and comments - if(!$oModuleModel->getTrigger('document.insertDocument', 'file', 'controller', 'triggerCheckAttached', 'before')) - $oModuleController->insertTrigger('document.insertDocument', 'file', 'controller', 'triggerCheckAttached', 'before'); + $oDB = DB::getInstance(); - if(!$oModuleModel->getTrigger('document.insertDocument', 'file', 'controller', 'triggerAttachFiles', 'after')) - $oModuleController->insertTrigger('document.insertDocument', 'file', 'controller', 'triggerAttachFiles', 'after'); - - if(!$oModuleModel->getTrigger('document.updateDocument', 'file', 'controller', 'triggerCheckAttached', 'before')) - $oModuleController->insertTrigger('document.updateDocument', 'file', 'controller', 'triggerCheckAttached', 'before'); - - if(!$oModuleModel->getTrigger('document.updateDocument', 'file', 'controller', 'triggerAttachFiles', 'after')) - $oModuleController->insertTrigger('document.updateDocument', 'file', 'controller', 'triggerAttachFiles', 'after'); - - if(!$oModuleModel->getTrigger('document.deleteDocument', 'file', 'controller', 'triggerDeleteAttached', 'after')) - $oModuleController->insertTrigger('document.deleteDocument', 'file', 'controller', 'triggerDeleteAttached', 'after'); - - if(!$oModuleModel->getTrigger('comment.insertComment', 'file', 'controller', 'triggerCommentCheckAttached', 'before')) - $oModuleController->insertTrigger('comment.insertComment', 'file', 'controller', 'triggerCommentCheckAttached', 'before'); - - if(!$oModuleModel->getTrigger('comment.insertComment', 'file', 'controller', 'triggerCommentAttachFiles', 'after')) - $oModuleController->insertTrigger('comment.insertComment', 'file', 'controller', 'triggerCommentAttachFiles', 'after'); - - if(!$oModuleModel->getTrigger('comment.updateComment', 'file', 'controller', 'triggerCommentCheckAttached', 'before')) - $oModuleController->insertTrigger('comment.updateComment', 'file', 'controller', 'triggerCommentCheckAttached', 'before'); - - if(!$oModuleModel->getTrigger('comment.updateComment', 'file', 'controller', 'triggerCommentAttachFiles', 'after')) - $oModuleController->insertTrigger('comment.updateComment', 'file', 'controller', 'triggerCommentAttachFiles', 'after'); - - if(!$oModuleModel->getTrigger('comment.deleteComment', 'file', 'controller', 'triggerCommentDeleteAttached', 'after')) - $oModuleController->insertTrigger('comment.deleteComment', 'file', 'controller', 'triggerCommentDeleteAttached', 'after'); - // 2009. 6. 9 Delete all the attachements when auto-saved document is deleted - if(!$oModuleModel->getTrigger('editor.deleteSavedDoc', 'file', 'controller', 'triggerDeleteAttached', 'after')) - $oModuleController->insertTrigger('editor.deleteSavedDoc', 'file', 'controller', 'triggerDeleteAttached', 'after'); - // 2007. 10. 17 Create a trigger to delete all the attachements when the module is deleted - if(!$oModuleModel->getTrigger('module.deleteModule', 'file', 'controller', 'triggerDeleteModuleFiles', 'after')) - $oModuleController->insertTrigger('module.deleteModule', 'file', 'controller', 'triggerDeleteModuleFiles', 'after'); - // 2007. 10. 19 Call a trigger to set up the file permissions before displaying - if(!$oModuleModel->getTrigger('module.dispAdditionSetup', 'file', 'view', 'triggerDispFileAdditionSetup', 'before')) - $oModuleController->insertTrigger('module.dispAdditionSetup', 'file', 'view', 'triggerDispFileAdditionSetup', 'before'); - // A column to determine a target type - if(!$oDB->isColumnExists('files', 'upload_target_type')) $oDB->addColumn('files', 'upload_target_type', 'char', '3'); - - // 2012. 08. 29 Add a trigger to copy additional setting when the module is copied - if(!$oModuleModel->getTrigger('module.procModuleAdminCopyModule', 'file', 'controller', 'triggerCopyModule', 'after')) + // Check columns + if(!$oDB->isColumnExists('files', 'upload_target_type')) { - $oModuleController->insertTrigger('module.procModuleAdminCopyModule', 'file', 'controller', 'triggerCopyModule', 'after'); + $oDB->addColumn('files', 'upload_target_type', 'varchar', '20', null, false, 'upload_target_srl'); + } + if($oDB->getColumnInfo('files', 'upload_target_type')->size < 20) + { + $oDB->modifyColumn('files', 'upload_target_type', 'varchar', 20, null, false); + } + if(!$oDB->isColumnExists('files', 'cover_image')) + { + $oDB->addColumn('files', 'cover_image', 'char', '1', 'N', false, 'isvalid'); + } + if(!$oDB->isColumnExists('files', 'thumbnail_filename')) + { + $oDB->addColumn('files', 'thumbnail_filename', 'varchar', '250', null, false, 'uploaded_filename'); + } + if(!$oDB->isColumnExists('files', 'mime_type')) + { + $oDB->addColumn('files', 'mime_type', 'varchar', '100', '', true, 'file_size'); + } + if($oDB->getColumnInfo('files', 'mime_type')->size < 100) + { + $oDB->modifyColumn('files', 'mime_type', 'varchar', 100, '', true); + } + if(!$oDB->isIndexExists('files', 'idx_mime_type')) + { + $oDB->addIndex('files', 'idx_mime_type', 'mime_type'); + } + if(!$oDB->isColumnExists('files', 'original_type')) + { + $oDB->addColumn('files', 'original_type', 'varchar', '100', null, false, 'mime_type'); + } + if($oDB->getColumnInfo('files', 'original_type')->size < 100) + { + $oDB->modifyColumn('files', 'original_type', 'varchar', 100, '', false); + } + if(!$oDB->isColumnExists('files', 'width')) + { + $oDB->addColumn('files', 'width', 'number', '11', null, false, 'original_type'); + } + if(!$oDB->isColumnExists('files', 'height')) + { + $oDB->addColumn('files', 'height', 'number', '11', null, false, 'width'); + } + if(!$oDB->isColumnExists('files', 'duration')) + { + $oDB->addColumn('files', 'duration', 'number', '11', null, false, 'height'); } - if(!$oDB->isColumnExists('files', 'cover_image')) $oDB->addColumn('files', 'cover_image', 'char', '1', 'N'); - - if(!$oModuleModel->getTrigger('document.moveDocumentModule', 'file', 'controller', 'triggerMoveDocument', 'after')) + // Check indexes + if (!$oDB->isIndexExists('files', 'idx_upload_target_type')) { - $oModuleController->insertTrigger('document.moveDocumentModule', 'file', 'controller', 'triggerMoveDocument', 'after'); + $oDB->addIndex('files', 'idx_upload_target_type', ['upload_target_type']); } - if(!$oModuleModel->getTrigger('document.copyDocumentModule', 'file', 'controller', 'triggerAddCopyDocument', 'add')) + if (!$oDB->isIndexExists('files', 'idx_cover_image')) { - $oModuleController->insertTrigger('document.copyDocumentModule', 'file', 'controller', 'triggerAddCopyDocument', 'add'); + $oDB->addIndex('files', 'idx_cover_image', ['cover_image']); } - if(!$oModuleModel->getTrigger('comment.copyCommentByDocument', 'file', 'controller', 'triggerAddCopyCommentByDocument', 'add')) + if ($oDB->isIndexExists('files', 'idx_list_order')) { - $oModuleController->insertTrigger('comment.copyCommentByDocument', 'file', 'controller', 'triggerAddCopyCommentByDocument', 'add'); + $oDB->dropIndex('files', 'idx_list_order'); } } - - /** - * Re-generate the cache file - * - * @return Object - */ - function recompileCache() - { - } } /* End of file file.class.php */ /* Location: ./modules/file/file.class.php */ diff --git a/modules/file/file.controller.php b/modules/file/file.controller.php index d9887391e..4b4e7afcb 100644 --- a/modules/file/file.controller.php +++ b/modules/file/file.controller.php @@ -4,7 +4,7 @@ * Controller class of the file module * @author NAVER (developers@xpressengine.com) */ -class fileController extends file +class FileController extends File { /** * Initialization @@ -29,33 +29,38 @@ class fileController extends file $file_info = Context::get('Filedata'); // An error appears if not a normally uploaded file - if(!$file_info || !is_uploaded_file($file_info['tmp_name'])) exit(); - - // Basic variables setting - $oFileModel = getModel('file'); - $editor_sequence = Context::get('editor_sequence'); - $module_srl = $this->module_srl; - - // Exit a session if there is neither upload permission nor information - if(!$_SESSION['upload_info'][$editor_sequence]->enabled) + if (!$file_info || !is_uploaded_file($file_info['tmp_name'])) { - throw new Rhymix\Framework\Exceptions\NotPermitted; + throw new Rhymix\Framework\Exceptions\InvalidRequest(); } - - // Get upload_target_srl - $upload_target_srl = intval(Context::get('uploadTargetSrl')) ?: intval(Context::get('upload_target_srl')); - if (!$upload_target_srl) + + // Validate editor_sequence and module_srl. + $editor_sequence = Context::get('editor_sequence'); + $module_srl = intval($this->module_srl); + if (empty($_SESSION['upload_info'][$editor_sequence]->enabled)) { - $upload_target_srl = $_SESSION['upload_info'][$editor_sequence]->upload_target_srl; + throw new Rhymix\Framework\Exceptions\InvalidRequest(sprintf(lang('file.msg_invalid_upload_info'), 'editor_sequence')); + } + if ($_SESSION['upload_info'][$editor_sequence]->module_srl !== $module_srl) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest(sprintf(lang('file.msg_invalid_upload_info'), 'module_srl')); + } + + // Validate upload_target_srl. + $upload_target_srl = intval($_SESSION['upload_info'][$editor_sequence]->upload_target_srl); + $submitted_upload_target_srl = intval(Context::get('uploadTargetSrl')) ?: intval(Context::get('upload_target_srl')); + if ($submitted_upload_target_srl && $submitted_upload_target_srl !== $upload_target_srl) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest(sprintf(lang('file.msg_invalid_upload_info'), 'upload_target_srl')); } if (!$upload_target_srl) { $upload_target_srl = getNextSequence(); $_SESSION['upload_info'][$editor_sequence]->upload_target_srl = $upload_target_srl; } - + // Handle chunking - if (preg_match('!^bytes (\d+)-(\d+)/(\d+)$!', $_SERVER['HTTP_CONTENT_RANGE'], $matches)) + if (preg_match('!^bytes (\d+)-(\d+)/(\d+)$!', $_SERVER['HTTP_CONTENT_RANGE'] ?? '', $matches)) { // Check basic sanity $chunk_start = intval($matches[1]); @@ -67,10 +72,9 @@ class fileController extends file } $this->add('chunk_current_size', $chunk_size); $this->add('chunk_uploaded_size', $chunk_start); - + // Check existing chunks - $nonce = Context::get('nonce'); - $temp_key = hash_hmac('sha1', sprintf('%d:%d:%d:%s:%s', $editor_sequence, $upload_target_srl, $module_srl, $file_info['name'], $nonce), config('crypto.authentication_key')); + $temp_key = hash_hmac('sha1', sprintf('%d:%d:%d:%s:%s', $editor_sequence, $upload_target_srl, $module_srl, $file_info['name'], session_id()), config('crypto.authentication_key')); $temp_filename = RX_BASEDIR . 'files/attach/chunks/' . $temp_key; if ($chunk_start == 0 && Rhymix\Framework\Storage::isFile($temp_filename)) { @@ -84,14 +88,21 @@ class fileController extends file $this->add('chunk_status', 12); throw new Rhymix\Framework\Exception('msg_upload_invalid_chunk'); } - + // Check size limit $is_admin = (Context::get('logged_info')->is_admin === 'Y'); if (!$is_admin) { - $module_config = getModel('file')->getFileConfig($module_srl); - $allowed_attach_size = $module_config->allowed_attach_size * 1024 * 1024; - $allowed_filesize = $module_config->allowed_filesize * 1024 * 1024; + if (isset($_SESSION['upload_info'][$editor_sequence]->allowed_filesize)) + { + $allowed_attach_size = $allowed_filesize = $_SESSION['upload_info'][$editor_sequence]->allowed_filesize; + } + else + { + $module_config = FileModel::getFileConfig($module_srl); + $allowed_attach_size = $module_config->allowed_attach_size * 1024 * 1024; + $allowed_filesize = $module_config->allowed_filesize * 1024 * 1024; + } if ($total_size > $allowed_filesize) { $this->add('chunk_status', 21); @@ -104,7 +115,7 @@ class fileController extends file throw new Rhymix\Framework\Exception('msg_exceeds_limit_size'); } } - + // Append to chunk $fp = fopen($file_info['tmp_name'], 'r'); $success = Rhymix\Framework\Storage::write($temp_filename, $fp, 'a'); @@ -114,6 +125,10 @@ class fileController extends file $this->add('chunk_uploaded_size', $chunk_start + $chunk_size); if ($chunk_start + $chunk_size == $total_size) { + if (!Rhymix\Framework\Filters\FileContentFilter::check($temp_filename, $file_info['name'])) + { + throw new Rhymix\Framework\Exception('msg_security_violation'); + } $file_info['tmp_name'] = $temp_filename; $file_info['size'] = Rhymix\Framework\Storage::getSize($temp_filename); } @@ -133,59 +148,96 @@ class fileController extends file { $this->add('chunk_status', -1); } - + // Save the file - $output = $this->insertFile($file_info, $module_srl, $upload_target_srl); + $output = $this->insertFile($file_info, $module_srl, $upload_target_srl, 0, false, $editor_sequence); if($output->error != '0') { throw new Rhymix\Framework\Exception($output->message); } - + // Create the response Context::setResponseMethod('JSON'); $this->add('file_srl', $output->get('file_srl')); - $this->add('file_size', $output->get('file_size')); - $this->add('direct_download', $output->get('direct_download')); - $this->add('source_filename', $output->get('source_filename')); $this->add('upload_target_srl', $output->get('upload_target_srl')); + $this->add('source_filename', $output->get('source_filename')); + $this->add('thumbnail_filename', $output->get('thumbnail_filename')); + $this->add('file_size', $output->get('file_size')); + $this->add('disp_file_size', FileHandler::filesize($output->get('file_size'))); + $this->add('mime_type', $output->get('mime_type')); + $this->add('original_type', $output->get('original_type')); + $this->add('width', $output->get('width')); + $this->add('height', $output->get('height')); + $this->add('duration', $output->get('duration')); + $this->add('direct_download', $output->get('direct_download')); if ($output->get('direct_download') === 'Y') { - $this->add('download_url', $oFileModel->getDirectFileUrl($output->get('uploaded_filename'))); + $this->add('download_url', FileModel::getDirectFileUrl($output->get('uploaded_filename'))); } else { - $this->add('download_url', $oFileModel->getDownloadUrl($output->get('file_srl'), $output->get('sid'), $module_srl)); + $this->add('download_url', FileModel::getDownloadUrl($output->get('file_srl'), $output->get('sid'), 0, $output->get('source_filename'))); + } + + // Add upload status (getFileList) + try + { + $file_list = FileModel::getInstance()->getFileList($editor_sequence); + foreach ($file_list as $key => $val) + { + if (!isset($this->variables[$key])) + { + $this->add($key, $val); + } + } + } + catch (Exception $e) + { + // pass } } /** * Iframe upload attachments * - * @return Object + * @return void */ function procFileIframeUpload() { // Basic variables setting - $editor_sequence = Context::get('editor_sequence'); $callback = Context::get('callback'); - $module_srl = $this->module_srl; - $upload_target_srl = intval(Context::get('uploadTargetSrl')); - if(!$upload_target_srl) $upload_target_srl = intval(Context::get('upload_target_srl')); - // Exit a session if there is neither upload permission nor information - if(!$_SESSION['upload_info'][$editor_sequence]->enabled) exit(); - // Extract from session information if upload_target_srl is not specified - if(!$upload_target_srl) $upload_target_srl = $_SESSION['upload_info'][$editor_sequence]->upload_target_srl; - // Create if upload_target_srl is not defined in the session information - if(!$upload_target_srl) $_SESSION['upload_info'][$editor_sequence]->upload_target_srl = $upload_target_srl = getNextSequence(); + // Validate editor_sequence and module_srl. + $editor_sequence = Context::get('editor_sequence'); + $module_srl = intval($this->module_srl); + if (empty($_SESSION['upload_info'][$editor_sequence]->enabled)) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest(sprintf(lang('file.msg_invalid_upload_info'), 'editor_sequence')); + } + if ($_SESSION['upload_info'][$editor_sequence]->module_srl !== $module_srl) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest(sprintf(lang('file.msg_invalid_upload_info'), 'module_srl')); + } + + // Get upload_target_srl + $upload_target_srl = intval($_SESSION['upload_info'][$editor_sequence]->upload_target_srl); + $submitted_upload_target_srl = intval(Context::get('uploadTargetSrl')) ?: intval(Context::get('upload_target_srl')); + if ($submitted_upload_target_srl && $submitted_upload_target_srl !== $upload_target_srl) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest(sprintf(lang('file.msg_invalid_upload_info'), 'upload_target_srl')); + } + if (!$upload_target_srl) + { + $upload_target_srl = getNextSequence(); + $_SESSION['upload_info'][$editor_sequence]->upload_target_srl = $upload_target_srl; + } + // Delete and then attempt to re-upload if file_srl is requested $file_srl = Context::get('file_srl'); if($file_srl) { - $oFileModel = getModel('file'); - $logged_info = Context::get('logged_info'); - $file_info = $oFileModel->getFile($file_srl); - if($file_info->file_srl == $file_srl && $oFileModel->getFileGrant($file_info, $logged_info)->is_deletable) + $file_info = FileModel::getFile($file_srl); + if($file_info->file_srl == $file_srl && $file_info->upload_target_srl == $upload_target_srl && FileModel::isDeletable($file_info)) { $this->deleteFile($file_srl); } @@ -194,8 +246,9 @@ class fileController extends file $file_info = Context::get('Filedata'); // An error appears if not a normally uploaded file if(is_uploaded_file($file_info['tmp_name'])) { - $output = $this->insertFile($file_info, $module_srl, $upload_target_srl); + $output = $this->insertFile($file_info, $module_srl, $upload_target_srl, 0, false, $editor_sequence); Context::set('uploaded_fileinfo',$output); + Context::set('module_srl', $module_srl); } Context::set('layout','none'); @@ -207,43 +260,11 @@ class fileController extends file /** * Image resize * - * @return Object + * @deprecated */ function procFileImageResize() { - $file_srl = Context::get('file_srl'); - $width = Context::get('width'); - $height = Context::get('height'); - - if(!$file_srl || !$width) - { - throw new Rhymix\Framework\Exceptions\InvalidRequest; - } - - $oFileModel = getModel('file'); - $fileInfo = $oFileModel->getFile($file_srl); - if(!$fileInfo || $fileInfo->direct_download != 'Y') - { - throw new Rhymix\Framework\Exceptions\InvalidRequest; - } - - $source_src = $fileInfo->uploaded_filename; - $output_src = $source_src . '.resized' . strrchr($source_src,'.'); - - if(!$height) $height = $width-1; - - if(FileHandler::createImageFile($source_src,$output_src,$width,$height,'','ratio')) - { - $output = new stdClass(); - $output->info = getimagesize($output_src); - $output->src = $output_src; - } - else - { - throw new Rhymix\Framework\Exceptions\InvalidRequest; - } - - $this->add('resized_info',$output); + throw new Rhymix\Framework\Exceptions\FeatureDisabled; } /** @@ -279,8 +300,6 @@ class fileController extends file */ function procFileDownload() { - $oFileModel = getModel('file'); - if(isset($this->grant->access) && $this->grant->access !== true) { throw new Rhymix\Framework\Exceptions\NotPermitted; @@ -288,25 +307,25 @@ class fileController extends file $file_srl = Context::get('file_srl'); $sid = Context::get('sid'); - $logged_info = Context::get('logged_info'); + $filename_arg = htmlspecialchars_decode(Context::get('filename') ?? ''); + // Get file information from the DB - $columnList = array('file_srl', 'sid', 'isvalid', 'source_filename', 'module_srl', 'uploaded_filename', 'file_size', 'member_srl', 'upload_target_srl', 'upload_target_type'); - $file_obj = $oFileModel->getFile($file_srl, $columnList); + $file_obj = FileModel::getFile($file_srl); + $filename = preg_replace('/\.\.+/', '.', $file_obj->source_filename); + // If the requested file information is incorrect, an error that file cannot be found appears if($file_obj->file_srl != $file_srl || $file_obj->sid !== $sid) { throw new Rhymix\Framework\Exceptions\TargetNotFound('msg_file_not_found'); } - // Notify that file download is not allowed when standing-by(Only a top-administrator is permitted) - if($logged_info->is_admin != 'Y' && $file_obj->isvalid != 'Y') + if ($filename_arg !== '' && $filename_arg !== $filename) { - throw new Rhymix\Framework\Exceptions\NotPermitted('msg_not_permitted_download'); + throw new Rhymix\Framework\Exceptions\TargetNotFound('msg_file_not_found'); } - // File name - $filename = $file_obj->source_filename; - $file_module_config = $oFileModel->getFileModuleConfig($file_obj->module_srl); + // Not allow the file outlink - if($file_module_config->allow_outlink == 'N' && $_SERVER["HTTP_REFERER"]) + $file_module_config = FileModel::getFileConfig($file_obj->module_srl); + if($file_module_config->allow_outlink == 'N' && !empty($_SERVER['HTTP_REFERER'])) { // Handles extension to allow outlink if($file_module_config->allow_outlink_format) @@ -356,49 +375,10 @@ class fileController extends file } } - // Check if a permission for file download is granted - $downloadGrantCount = 0; - if(is_array($file_module_config->download_grant)) + // Check if the file is downloadable + if(!FileModel::isDownloadable($file_obj)) { - foreach($file_module_config->download_grant AS $value) - if($value) $downloadGrantCount++; - } - - if(is_array($file_module_config->download_grant) && $downloadGrantCount>0) - { - if(!Context::get('is_logged')) - { - throw new Rhymix\Framework\Exceptions\NotPermitted('msg_not_permitted_download'); - } - - $logged_info = Context::get('logged_info'); - if($logged_info->is_admin != 'Y') - { - $oModuleModel =& getModel('module'); - $columnList = array('module_srl', 'site_srl'); - $module_info = $oModuleModel->getModuleInfoByModuleSrl($file_obj->module_srl, $columnList); - - if(!$oModuleModel->isSiteAdmin($logged_info, $module_info->site_srl)) - { - $oMemberModel =& getModel('member'); - $member_groups = $oMemberModel->getMemberGroups($logged_info->member_srl, $module_info->site_srl); - - $is_permitted = false; - for($i=0;$idownload_grant);$i++) - { - $group_srl = $file_module_config->download_grant[$i]; - if($member_groups[$group_srl]) - { - $is_permitted = true; - break; - } - } - if(!$is_permitted) - { - throw new Rhymix\Framework\Exceptions\NotPermitted('msg_not_permitted_download'); - } - } - } + throw new Rhymix\Framework\Exceptions\NotPermitted('msg_not_permitted_download'); } // Call a trigger (before) @@ -424,13 +404,27 @@ class fileController extends file ModuleHandler::triggerCall('file.downloadFile', 'after', $file_obj); // Redirect to procFileOutput using file key - if(!isset($_SESSION['__XE_FILE_KEY__']) || !is_string($_SESSION['__XE_FILE_KEY__']) || strlen($_SESSION['__XE_FILE_KEY__']) != 32) + $file_key_timestamp = \RX_TIME; + $file_key_data = sprintf('%d:%d:%s:%s', $file_obj->file_srl, $file_key_timestamp, $file_obj->uploaded_filename, \RX_CLIENT_IP); + $file_key_sig = \Rhymix\Framework\Security::createSignature($file_key_data); + $file_key = dechex($file_key_timestamp) . $file_key_sig; + + // Use short URL or long URL + if ($file_module_config->download_short_url === 'Y' && config('use_rewrite')) { - $_SESSION['__XE_FILE_KEY__'] = Rhymix\Framework\Security::getRandom(32, 'hex'); + $url = RX_BASEURL . sprintf('files/download/%d/%s/%s', $file_srl, $file_key, rawurlencode(preg_replace('/\.\.+/', '.', $filename))); } - $file_key_data = $file_obj->file_srl . $file_obj->file_size . $file_obj->uploaded_filename . $_SERVER['REMOTE_ADDR']; - $file_key = substr(hash_hmac('sha256', $file_key_data, $_SESSION['__XE_FILE_KEY__']), 0, 32); - header('Location: '.getNotEncodedUrl('', 'act', 'procFileOutput', 'file_srl', $file_srl, 'file_key', $file_key, 'force_download', Context::get('force_download') === 'Y' ? 'Y' : null)); + else + { + $url = getNotEncodedUrl('', 'module', 'file', 'act', 'procFileOutput', 'file_srl', $file_srl, 'file_key', $file_key, 'force_download', Context::get('force_download') === 'Y' ? 'Y' : null); + } + + if (!FileModel::isIndexable($filename, $file_module_config)) + { + header('X-Robots-Tag: noindex'); + } + + header('Location: ' . $url); Context::close(); exit(); } @@ -438,29 +432,39 @@ class fileController extends file public function procFileOutput() { // Get requsted file info - $oFileModel = getModel('file'); $file_srl = Context::get('file_srl'); $file_key = Context::get('file_key'); + $filename_arg = htmlspecialchars_decode(Context::get('filename') ?? ''); $columnList = array('source_filename', 'uploaded_filename', 'file_size'); - $file_obj = $oFileModel->getFile($file_srl, $columnList); - $file_config = $oFileModel->getFileConfig($file_obj->module_srl ?: null); + $file_obj = FileModel::getFile($file_srl, $columnList); + $file_config = FileModel::getFileConfig($file_obj->module_srl ?: null); $filesize = $file_obj->file_size; - $filename = $file_obj->source_filename; - $etag = md5($file_srl . $file_key . $_SERVER['HTTP_USER_AGENT']); + $filename = preg_replace('/\.\.+/', '.', $file_obj->source_filename); + $etag = md5($file_srl . $file_key . \RX_CLIENT_IP); // Check file key - if(strlen($file_key) != 32 || !isset($_SESSION['__XE_FILE_KEY__']) || !is_string($_SESSION['__XE_FILE_KEY__'])) + if(strlen($file_key) != 48 || !ctype_xdigit(substr($file_key, 0, 8))) { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - $file_key_data = $file_srl . $file_obj->file_size . $file_obj->uploaded_filename . $_SERVER['REMOTE_ADDR']; - $file_key_compare = substr(hash_hmac('sha256', $file_key_data, $_SESSION['__XE_FILE_KEY__']), 0, 32); - if($file_key !== $file_key_compare) + $file_key_timestamp = hexdec(substr($file_key, 0, 8)); + if ($file_key_timestamp < \RX_TIME - 300) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest('msg_file_key_expired'); + } + $file_key_data = sprintf('%d:%d:%s:%s', $file_srl, $file_key_timestamp, $file_obj->uploaded_filename, \RX_CLIENT_IP); + if (!\Rhymix\Framework\Security::verifySignature($file_key_data, substr($file_key, 8))) { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - + + // Check filename if given + if ($filename_arg !== '' && $filename_arg !== $filename) + { + throw new Rhymix\Framework\Exceptions\TargetNotFound('msg_file_not_found'); + } + // Check if file exists $uploaded_filename = $file_obj->uploaded_filename; if(!file_exists($uploaded_filename)) @@ -472,7 +476,7 @@ class fileController extends file if(isset($_SERVER['HTTP_IF_NONE_MATCH']) && trim(trim($_SERVER['HTTP_IF_NONE_MATCH']), '\'"') === $etag) { header('HTTP/1.1 304 Not Modified'); - exit(); + exit(); } // If client sent an If-Modified-Since header with a recent modification date, do not download again @@ -483,7 +487,14 @@ class fileController extends file } // Encode the filename. - $filename_param = Rhymix\Framework\UA::encodeFilenameForDownload($filename); + if ($filename_arg !== null && $filename_arg === $filename) + { + $filename_param = ''; + } + else + { + $filename_param = '; ' . Rhymix\Framework\UA::encodeFilenameForDownload($filename); + } // Close context to prevent blocking the session Context::close(); @@ -544,25 +555,29 @@ class fileController extends file { $download_type = 'attachment'; } - + // Clear buffer while(ob_get_level()) ob_end_clean(); - + // Set filename headers - header('Content-Type: ' . ($download_type === 'inline' ? $mime_type : 'application/octet-stream')); - header('Content-Disposition: ' . $download_type . '; ' . $filename_param); - + header('Content-Type: ' . $mime_type); + header('Content-Disposition: ' . $download_type . $filename_param); + // Set cache headers header('Cache-Control: private; max-age=3600'); header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); header('Pragma: '); - + // Set other headers - header('Content-Transfer-Encoding: binary'); header('Content-Length: ' . $range_length); header('Accept-Ranges: bytes'); header('Etag: "' . $etag . '"'); + if (!FileModel::isIndexable($filename, $file_config)) + { + header('X-Robots-Tag: noindex' . false); + } + // Print the file contents for($offset = 0; $offset < $range_length; $offset += 4096) { @@ -577,7 +592,7 @@ class fileController extends file /** * Delete an attachment from the editor * - * @return Object + * @return void */ function procFileDelete() { @@ -586,13 +601,18 @@ class fileController extends file $file_srl = Context::get('file_srl'); $file_srls = Context::get('file_srls'); if($file_srls) $file_srl = $file_srls; + // Exit a session if there is neither upload permission nor information - if(!$_SESSION['upload_info'][$editor_sequence]->enabled) exit(); - + if (!$_SESSION['upload_info'][$editor_sequence]->enabled) + { + throw new Rhymix\Framework\Exceptions\NotPermitted; + } $upload_target_srl = $_SESSION['upload_info'][$editor_sequence]->upload_target_srl; - - $logged_info = Context::get('logged_info'); - $oFileModel = getModel('file'); + if (!$upload_target_srl) + { + throw new Rhymix\Framework\Exceptions\TargetNotFound; + } + $module_srl = $_SESSION['upload_info'][$editor_sequence]->module_srl ?? 0; $srls = explode(',',$file_srl); if(!count($srls)) return; @@ -608,20 +628,34 @@ class fileController extends file if(!$output->toBool()) continue; $file_info = $output->data; - if(!$file_info) continue; + if(!$file_info || $file_info->upload_target_srl != $upload_target_srl) continue; + //if($module_srl && $file_info->module_srl != $module_srl) continue; + if(!FileModel::isDeletable($file_info)) continue; + $output = $this->deleteFile($file_srl); + } - $file_grant = $oFileModel->getFileGrant($file_info, $logged_info); - - if(!$file_grant->is_deletable) continue; - - if($upload_target_srl && $file_srl) $output = $this->deleteFile($file_srl); + // Add upload status (getFileList) + try + { + $file_list = FileModel::getInstance()->getFileList($editor_sequence); + foreach ($file_list as $key => $val) + { + if (!isset($this->variables[$key])) + { + $this->add($key, $val); + } + } + } + catch (Exception $e) + { + // pass } } /** * get file list * - * @return Object + * @return void */ function procFileGetList() { @@ -629,21 +663,21 @@ class fileController extends file { throw new Rhymix\Framework\Exceptions\NotPermitted; } - + $logged_info = Context::get('logged_info'); - if($logged_info->is_admin !== 'Y' && !getModel('module')->isSiteAdmin($logged_info)) + if($logged_info->is_admin !== 'Y' && !ModuleModel::isSiteAdmin($logged_info)) { throw new Rhymix\Framework\Exceptions\NotPermitted; } - + $fileSrls = Context::get('file_srls'); if($fileSrls) $fileSrlList = explode(',', $fileSrls); global $lang; if(count($fileSrlList) > 0) { - $oFileModel = getModel('file'); - $fileList = $oFileModel->getFile($fileSrlList); + $fileList = FileModel::getFile($fileSrlList); + $fileSizeTotal = 0; if(!is_array($fileList)) $fileList = array($fileList); if(is_array($fileList)) @@ -653,54 +687,27 @@ class fileController extends file $value->human_file_size = FileHandler::filesize($value->file_size); if($value->isvalid=='Y') $value->validName = $lang->is_valid; else $value->validName = $lang->is_stand_by; + $fileSizeTotal += $value->file_size; } } } else { $fileList = array(); + $fileSizeTotal = 0; $this->setMessage($lang->no_files); } $this->add('file_list', $fileList); - } - /** - * A trigger to return numbers of attachments in the upload_target_srl (document_srl) - * - * @param object $obj Trigger object - * @return Object - */ - function triggerCheckAttached(&$obj) - { - $document_srl = $obj->document_srl; - if(!$document_srl) return; - - // Get numbers of attachments - $oFileModel = getModel('file'); - $obj->uploaded_count = $oFileModel->getFilesCount($document_srl); - // TODO: WTF are we doing with uploaded_count anyway? - } - - /** - * A trigger to link the attachment with the upload_target_srl (document_srl) - * - * @param object $obj Trigger object - * @return Object - */ - function triggerAttachFiles(&$obj) - { - $document_srl = $obj->document_srl; - if(!$document_srl) return; - - $output = $this->setFilesValid($document_srl); - if(!$output->toBool()) return $output; + $this->add('file_size_total', $fileSizeTotal); + $this->add('file_size_total_human', FileHandler::filesize($fileSizeTotal)); } /** * A trigger to delete the attachment in the upload_target_srl (document_srl) * * @param object $obj Trigger object - * @return Object + * @return BaseObject */ function triggerDeleteAttached(&$obj) { @@ -711,42 +718,11 @@ class fileController extends file return $output; } - /** - * A trigger to return numbers of attachments in the upload_target_srl (comment_srl) - * - * @param object $obj Trigger object - * @return Object - */ - function triggerCommentCheckAttached(&$obj) - { - $comment_srl = $obj->comment_srl; - if(!$comment_srl) return; - // Get numbers of attachments - $oFileModel = getModel('file'); - $obj->uploaded_count = $oFileModel->getFilesCount($comment_srl); - } - - /** - * A trigger to link the attachment with the upload_target_srl (comment_srl) - * - * @param object $obj Trigger object - * @return Object - */ - function triggerCommentAttachFiles(&$obj) - { - $comment_srl = $obj->comment_srl; - $uploaded_count = $obj->uploaded_count; - if(!$comment_srl || !$uploaded_count) return; - - $output = $this->setFilesValid($comment_srl); - if(!$output->toBool()) return $output; - } - /** * A trigger to delete the attachment in the upload_target_srl (comment_srl) * * @param object $obj Trigger object - * @return Object + * @return BaseObject */ function triggerCommentDeleteAttached(&$obj) { @@ -763,13 +739,13 @@ class fileController extends file * A trigger to delete all the attachements when deleting the module * * @param object $obj Trigger object - * @return Object + * @return BaseObject */ function triggerDeleteModuleFiles(&$obj) { $module_srl = $obj->module_srl; if(!$module_srl) return; - + return $this->deleteModuleFiles($module_srl); } @@ -778,20 +754,56 @@ class fileController extends file * * @param int $editor_sequence * @param int $upload_target_srl - * @return void + * @param int $module_srl + * @param array $config + * @return int */ - function setUploadInfo($editor_sequence, $upload_target_srl=0) + public static function setUploadInfo($editor_sequence = 0, $upload_target_srl = 0, $module_srl = 0, array $config = []) { + if(!$editor_sequence) + { + if(!isset($_SESSION['_editor_sequence_'])) + { + $_SESSION['_editor_sequence_'] = 1; + } + $editor_sequence = ++$_SESSION['_editor_sequence_']; + } + if(!$module_srl) + { + $current_module_info = Context::get('current_module_info'); + if (!empty($current_module_info->module_srl)) + { + $module_srl = $current_module_info->module_srl; + } + } if(!isset($_SESSION['upload_info']) || !is_array($_SESSION['upload_info'])) { $_SESSION['upload_info'] = array(); } + if(count($_SESSION['upload_info']) > 200) + { + $_SESSION['upload_info'] = array_slice($_SESSION['upload_info'], 100, null, true); + } if(!isset($_SESSION['upload_info'][$editor_sequence])) { $_SESSION['upload_info'][$editor_sequence] = new stdClass(); } $_SESSION['upload_info'][$editor_sequence]->enabled = true; - $_SESSION['upload_info'][$editor_sequence]->upload_target_srl = $upload_target_srl; + $_SESSION['upload_info'][$editor_sequence]->upload_target_srl = (int)$upload_target_srl; + $_SESSION['upload_info'][$editor_sequence]->module_srl = (int)$module_srl; + if (!$module_srl) + { + trigger_error('No module_srl supplied to setUploadInfo(), and cannot determine automatically', E_USER_WARNING); + } + if ($config) + { + foreach ($config as $key => $val) + { + $_SESSION['upload_info'][$editor_sequence]->$key = $val; + } + } + + return $editor_sequence; } /** @@ -799,13 +811,41 @@ class fileController extends file * By changing its state to valid when a document is inserted, it prevents from being considered as a unnecessary file * * @param int $upload_target_srl - * @return Object + * @param ?string $upload_target_type + * @param ?array $file_srl + * @return BaseObject */ - function setFilesValid($upload_target_srl) + function setFilesValid($upload_target_srl, $upload_target_type = null, $file_srl = null) { $args = new stdClass(); $args->upload_target_srl = $upload_target_srl; - return executeQuery('file.updateFileValid', $args); + $args->old_isvalid = 'N'; + if ($upload_target_type) + { + $args->upload_target_type = $upload_target_type; + } + if ($file_srl) + { + $args->file_srl = $file_srl; + } + $output = executeQuery('file.updateFileValid', $args); + $output->add('updated_file_count', intval(DB::getInstance()->getAffectedRows())); + return $output; + } + + /** + * Update upload target type + * + * @param int|array $file_srl + * @param string $upload_target_type + * @return BaseObject + */ + public function updateTargetType($file_srl, $upload_target_type) + { + $args = new stdClass; + $args->file_srl = $file_srl; + $args->upload_target_type = $upload_target_type; + return executeQuery('file.updateFileTargetType', $args); } /** @@ -832,162 +872,256 @@ class fileController extends file * - sid * * - * @param object $file_info PHP file information array + * @param array $file_info PHP file information array * @param int $module_srl Sequence of module to upload file * @param int $upload_target_srl Sequence of target to upload file * @param int $download_count Initial download count * @param bool $manual_insert If set true, pass validation check - * @return Object + * @param int $editor_sequence Optional + * @return BaseObject */ - function insertFile($file_info, $module_srl, $upload_target_srl, $download_count = 0, $manual_insert = false) + function insertFile($file_info, $module_srl, $upload_target_srl, $download_count = 0, $manual_insert = false, $editor_sequence = 0) { + // Set base information + $file_info['name'] = Rhymix\Framework\Filters\FilenameFilter::clean($file_info['name']); + $file_info['type'] = Rhymix\Framework\MIME::getContentType($file_info['tmp_name']); + $file_info['original_type'] = $file_info['type']; + $file_info['extension'] = strtolower(array_last(explode('.', $file_info['name']))); + $file_info['original_extension'] = $file_info['extension']; + $file_info['width'] = null; + $file_info['height'] = null; + $file_info['duration'] = null; + $file_info['thumbnail'] = null; + $file_info['save_path'] = null; + $file_info['converted'] = false; + + // Correct extension + if($file_info['extension']) + { + $type_by_extension = Rhymix\Framework\MIME::getTypeByExtension($file_info['extension']); + if(!in_array($type_by_extension, [$file_info['type'], 'application/octet-stream'])) + { + $extension_by_type = Rhymix\Framework\MIME::getExtensionByType($file_info['type']); + if($extension_by_type && preg_match('@^(?:image|audio|video)/@m', $file_info['type'] . PHP_EOL . $type_by_extension)) + { + $file_info['extension'] = $extension_by_type; + } + } + } + // Call a trigger (before) $trigger_obj = new stdClass; - $trigger_obj->file_info = $file_info; + $trigger_obj->file_info = &$file_info; $trigger_obj->module_srl = $module_srl; $trigger_obj->upload_target_srl = $upload_target_srl; $output = ModuleHandler::triggerCall('file.insertFile', 'before', $trigger_obj); if(!$output->toBool()) return $output; - // A workaround for Firefox upload bug - if(preg_match('/^=\?UTF-8\?B\?(.+)\?=$/i', $file_info['name'], $match)) + // Get file module configuration + $config = FileModel::getFileConfig($module_srl); + + // Check file extension + if(!$manual_insert && !$this->user->isAdmin()) { - $file_info['name'] = base64_decode(strtr($match[1], ':', '/')); + if (isset($_SESSION['upload_info'][$editor_sequence]->allowed_extensions)) + { + if (!in_array($file_info['extension'], $_SESSION['upload_info'][$editor_sequence]->allowed_extensions)) + { + throw new Rhymix\Framework\Exception('msg_not_allowed_filetype'); + } + } + elseif($config->allowed_extensions && !in_array($file_info['extension'], $config->allowed_extensions)) + { + throw new Rhymix\Framework\Exception('msg_not_allowed_filetype'); + } } + // Adjust if(!$manual_insert) { - // Get the file configurations - $logged_info = Context::get('logged_info'); - if($logged_info->is_admin != 'Y') + // image + if(in_array($file_info['extension'], ['gif', 'jpg', 'jpeg', 'jfif', 'png', 'webp', 'bmp', 'avif', 'heic', 'heif'])) { - $oFileModel = getModel('file'); - $config = $oFileModel->getFileConfig($module_srl); + $file_info = $this->adjustUploadedImage($file_info, $config); + } - // check file type - if(isset($config->allowed_filetypes) && $config->allowed_filetypes !== '*.*') - { - $filetypes = explode(';', $config->allowed_filetypes); - $ext = array(); - foreach($filetypes as $item) { - $item = explode('.', $item); - $ext[] = strtolower($item[1]); - } - $uploaded_ext = explode('.', $file_info['name']); - $uploaded_ext = strtolower(array_pop($uploaded_ext)); - - if(!in_array($uploaded_ext, $ext)) - { - throw new Rhymix\Framework\Exception('msg_not_allowed_filetype'); - } - } - - $allowed_filesize = $config->allowed_filesize * 1024 * 1024; - $allowed_attach_size = $config->allowed_attach_size * 1024 * 1024; - // An error appears if file size exceeds a limit - if($allowed_filesize < filesize($file_info['tmp_name'])) throw new Rhymix\Framework\Exception('msg_exceeds_limit_size'); - // Get total file size of all attachements (from DB) - $size_args = new stdClass; - $size_args->upload_target_srl = $upload_target_srl; - $output = executeQuery('file.getAttachedFileSize', $size_args); - $attached_size = (int)$output->data->attached_size + filesize($file_info['tmp_name']); - if($attached_size > $allowed_attach_size) throw new Rhymix\Framework\Exception('msg_exceeds_limit_size'); + // video + if(in_array($file_info['extension'], ['mp4', 'webm', 'ogv', 'avi', 'mkv', 'mov', 'mpg', 'mpe', 'mpeg', 'wmv', 'm4v', 'flv'])) + { + $file_info = $this->adjustUploadedVideo($file_info, $config); } } - // Sanitize filename - $file_info['name'] = Rhymix\Framework\Filters\FilenameFilter::clean($file_info['name']); - - // Get file_srl - $file_srl = getNextSequence(); - $file_regdate = date('YmdHis'); - - // Set upload path by checking if the attachement is an image or other kinds of file - if(Rhymix\Framework\Filters\FilenameFilter::isDirectDownload($file_info['name'])) + // Check file size + if(!$manual_insert && !$this->user->isAdmin()) { - $path = $this->getStoragePath('images', $file_srl, $module_srl, $upload_target_srl, $file_regdate); - - // change to random file name. because window php bug. window php is not recognize unicode character file name - by cherryfilter - $ext = substr(strrchr($file_info['name'],'.'),1); - $filename = $path . Rhymix\Framework\Security::getRandom(32, 'hex') . '.' . $ext; - while(file_exists($filename)) + $file_size = filesize($file_info['tmp_name']); + if (isset($_SESSION['upload_info'][$editor_sequence]->allowed_filesize)) { - $filename = $path . Rhymix\Framework\Security::getRandom(32, 'hex') . '.' . $ext; + $allowed_attach_size = $allowed_filesize = $_SESSION['upload_info'][$editor_sequence]->allowed_filesize; } - $direct_download = 'Y'; + else + { + $allowed_filesize = $config->allowed_filesize * 1024 * 1024; + $allowed_attach_size = $config->allowed_attach_size * 1024 * 1024; + } + if($allowed_filesize < $file_size) + { + throw new Rhymix\Framework\Exception('msg_exceeds_limit_size'); + } + + $size_args = new stdClass; + $size_args->upload_target_srl = $upload_target_srl; + $output = executeQuery('file.getAttachedFileSize', $size_args); + if($allowed_attach_size < intval($output->data->attached_size) + $file_size) + { + throw new Rhymix\Framework\Exception('msg_exceeds_limit_size'); + } + } + + $args = new stdClass; + $args->file_srl = getNextSequence(); + $args->regdate = date('YmdHis'); + $args->module_srl = $module_srl; + $args->upload_target_srl = $upload_target_srl; + $args->download_count = $download_count; + $args->member_srl = Rhymix\Framework\Session::getMemberSrl() ?: 0; + $args->source_filename = $file_info['name']; + $args->sid = Rhymix\Framework\Security::getRandom(32, 'hex'); + $args->mime_type = $file_info['type']; + $args->width = $file_info['width']; + $args->height = $file_info['height']; + $args->duration = $file_info['duration']; + + // Set original type + $args->original_type = null; + if($file_info['type'] !== $file_info['original_type']) + { + $args->original_type = $file_info['original_type']; + } + + // Add changed extension + if($file_info['extension'] && $file_info['extension'] !== $file_info['original_extension']) + { + $args->source_filename .= '.' . $file_info['extension']; + } + + // Set storage path by checking if the attachement is an image or other kinds of file + if(!empty($file_info['save_path'])) + { + $storage_path = dirname(FileHandler::getRealPath($file_info['save_path'])); + $uploaded_filename = basename($file_info['save_path']); + } + elseif($direct = Rhymix\Framework\Filters\FilenameFilter::isDirectDownload($args->source_filename)) + { + $storage_path = $this->getStoragePath('images', $args->file_srl, $module_srl, $upload_target_srl, $args->regdate); + $uploaded_filename = null; } else { - $path = $this->getStoragePath('binaries', $file_srl, $module_srl, $upload_target_srl, $file_regdate); - $filename = $path . Rhymix\Framework\Security::getRandom(32, 'hex'); - while(file_exists($filename)) - { - $filename = $path . Rhymix\Framework\Security::getRandom(32, 'hex'); - } - $direct_download = 'N'; + $storage_path = $this->getStoragePath('binaries', $args->file_srl, $module_srl, $upload_target_srl, $args->regdate); + $uploaded_filename = null; + } + + // Set direct download option + if(isset($config->allow_multimedia_direct_download) && $config->allow_multimedia_direct_download !== 'N') + { + $args->direct_download = $direct ? 'Y' : 'N'; + } + else + { + $args->direct_download = Rhymix\Framework\Filters\FilenameFilter::isDirectDownload($args->source_filename, false) ? 'Y' : 'N'; } // Create a directory - if(!Rhymix\Framework\Storage::isDirectory($path) && !Rhymix\Framework\Storage::createDirectory($path)) + if(!Rhymix\Framework\Storage::isDirectory($storage_path) && !Rhymix\Framework\Storage::createDirectory($storage_path)) { throw new Rhymix\Framework\Exception('msg_not_permitted_create'); } - - // Move the file - if($manual_insert) - { - @copy($file_info['tmp_name'], $filename); - if(!file_exists($filename)) - { - @copy($file_info['tmp_name'], $filename); - if(!file_exists($filename)) - { - throw new Rhymix\Framework\Exception('msg_file_upload_error'); - } - } - } - elseif(starts_with(RX_BASEDIR . 'files/attach/chunks/', $file_info['tmp_name'])) - { - if (!Rhymix\Framework\Storage::move($file_info['tmp_name'], $filename)) - { - if (!Rhymix\Framework\Storage::move($file_info['tmp_name'], $filename)) - { - throw new Rhymix\Framework\Exception('msg_file_upload_error'); - } - } - } - else - { - if(!@move_uploaded_file($file_info['tmp_name'], $filename)) - { - if(!@move_uploaded_file($file_info['tmp_name'], $filename)) - { - throw new Rhymix\Framework\Exception('msg_file_upload_error'); - } - } - } - - // Get member information - $oMemberModel = getModel('member'); - $member_srl = $oMemberModel->getLoggedMemberSrl(); - // List file information - $args = new stdClass; - $args->file_srl = $file_srl; - $args->upload_target_srl = $upload_target_srl; - $args->module_srl = $module_srl; - $args->direct_download = $direct_download; - $args->source_filename = $file_info['name']; - $args->uploaded_filename = './' . substr($filename, strlen(RX_BASEDIR)); - $args->download_count = $download_count; - $args->file_size = @filesize($filename); - $args->comment = NULL; - $args->member_srl = $member_srl; - $args->regdate = $file_regdate; - $args->sid = Rhymix\Framework\Security::getRandom(32, 'hex'); + // Set move type and uploaded filename + $move_type = $manual_insert ? 'copy' : ''; + if($file_info['converted'] || starts_with(RX_BASEDIR . 'files/attach/chunks/', $file_info['tmp_name'])) + { + $move_type = 'move'; + } + if(!$uploaded_filename) + { + $extension = ($direct && $file_info['extension']) ? ('.' . $file_info['extension']) : ''; + $random_filename = Rhymix\Framework\Security::getRandom(32, 'hex'); + $uploaded_filename = $storage_path . $random_filename . $extension; + while(file_exists($uploaded_filename)) + { + $random_filename = Rhymix\Framework\Security::getRandom(32, 'hex'); + $uploaded_filename = $storage_path . $random_filename . $extension; + } + } + + // fanbinit edit: remove original filename info + $args->source_filename = $random_filename . '.' . $file_info['extension']; + + // Move the uploaded file + if(!Rhymix\Framework\Storage::moveUploadedFile($file_info['tmp_name'], $uploaded_filename, $move_type)) + { + throw new Rhymix\Framework\Exception('msg_file_upload_error'); + } + clearstatcache(true, $uploaded_filename); + $args->uploaded_filename = './' . substr($uploaded_filename, strlen(RX_BASEDIR)); + $args->file_size = Rhymix\Framework\Storage::getSize($uploaded_filename) ?: 0; + + // Move the generated thumbnail image + $args->thumbnail_filename = null; + if($file_info['thumbnail']) + { + $thumbnail_filename = $storage_path . Rhymix\Framework\Security::getRandom(32, 'hex') . '.jpg'; + while(file_exists($thumbnail_filename)) + { + $thumbnail_filename = $storage_path . Rhymix\Framework\Security::getRandom(32, 'hex') . '.jpg'; + } + if(Rhymix\Framework\Storage::moveUploadedFile($file_info['thumbnail'], $thumbnail_filename, 'move')) + { + $args->thumbnail_filename = './' . substr($thumbnail_filename, strlen(RX_BASEDIR)); + } + } + + // Set upload target type + if ($editor_sequence && isset($_SESSION['upload_info'][$editor_sequence]->upload_target_type)) + { + $args->upload_target_type = strval($_SESSION['upload_info'][$editor_sequence]->upload_target_type); + } + + $oDB = DB::getInstance(); + $oDB->begin(); + + // Insert file information $output = executeQuery('file.insertFile', $args); - if(!$output->toBool()) return $output; - + if(!$output->toBool()) + { + $oDB->rollback(); + $this->deleteFile(array($args)); + return $output; + } + + // Insert changelog + if(isset($config->save_changelog) && $config->save_changelog === 'Y') + { + $clargs = new stdClass; + $clargs->change_type = 'I'; + $clargs->file_srl = $args->file_srl; + $clargs->file_size = $args->file_size; + $clargs->uploaded_filename = $args->uploaded_filename; + $clargs->regdate = $args->regdate; + $output = executeQuery('file.insertFileChangelog', $clargs); + if(!$output->toBool()) + { + $oDB->rollback(); + $this->deleteFile(array($args)); + return $output; + } + } + + $oDB->commit(); + // Call a trigger (after) ModuleHandler::triggerCall('file.insertFile', 'after', $args); @@ -995,14 +1129,479 @@ class fileController extends file $output->add('file_srl', $args->file_srl); $output->add('file_size', $args->file_size); - $output->add('sid', $args->sid); + $output->add('upload_target_srl', $upload_target_srl); $output->add('direct_download', $args->direct_download); $output->add('source_filename', $args->source_filename); - $output->add('upload_target_srl', $upload_target_srl); $output->add('uploaded_filename', $args->uploaded_filename); + $output->add('thumbnail_filename', $args->thumbnail_filename); + $output->add('mime_type', $args->mime_type); + $output->add('original_type', $args->original_type); + $output->add('width', $args->width); + $output->add('height', $args->height); + $output->add('duration', $args->duration); + $output->add('sid', $args->sid); + return $output; } + /** + * Adjust uploaded image + */ + public function adjustUploadedImage($file_info, $config) + { + // Get image information + if (in_array($file_info['extension'], ['avif', 'heic', 'heif']) && !empty($config->magick_command)) + { + $command = \RX_WINDOWS ? escapeshellarg($config->magick_command) : $config->magick_command; + $command .= ' identify ' . escapeshellarg($file_info['tmp_name']); + @exec($command, $output, $return_var); + if ($return_var === 0 && preg_match('/([A-Z]+) ([0-9]+)x([0-9]+)/', substr(array_last($output), strlen($file_info['tmp_name'])), $matches)) + { + $image_info = [ + 'width' => (int)$matches[2], + 'height' => (int)$matches[3], + 'type' => strtolower($matches[1]), + ]; + } + else + { + $image_info = false; + } + } + else + { + $image_info = Rhymix\Framework\Image::getImageInfo($file_info['tmp_name']); + } + + // Return if image cannot be converted + if (!$image_info) + { + return $file_info; + } + + // Set image size + $file_info['width'] = $image_info['width']; + $file_info['height'] = $image_info['height']; + + // Set base information + $force = false; + $adjusted = [ + 'width' => $image_info['width'], + 'height' => $image_info['height'], + 'type' => $image_info['type'], + 'quality' => $config->image_quality_adjustment ?: 75, + 'rotate' => 0, + ]; + $is_animated = Rhymix\Framework\Image::isAnimatedGIF($file_info['tmp_name']); + + // Adjust image type + if ($config->image_autoconv['gif2mp4'] && $is_animated && function_exists('exec') && Rhymix\Framework\Storage::isExecutable($config->ffmpeg_command)) + { + $adjusted['type'] = 'mp4'; + } + elseif (!empty($config->image_autoconv[$image_info['type']]) && tobool($config->image_autoconv[$image_info['type']])) + { + $adjusted['type'] = $config->image_autoconv[$image_info['type']]; + } + elseif (!empty($config->image_autoconv[$image_info['type'] . '2jpg'])) + { + $adjusted['type'] = 'jpg'; + } + elseif ($image_info['type'] === 'avif' || $image_info['type'] === 'heic') + { + return $file_info; + } + + // Adjust image rotation + if ($config->image_autorotate && $image_info['type'] === 'jpg') + { + $rotate = FileHandler::checkImageRotation($file_info['tmp_name']); + if ($rotate) + { + if ($rotate === 90 || $rotate === 270) + { + $adjusted['width'] = $image_info['height']; + $adjusted['height'] = $image_info['width']; + } + $adjusted['rotate'] = $rotate; + } + } + + // Adjust image size + if ($config->max_image_size_action && ($config->max_image_width || $config->max_image_height) && (!$this->user->isAdmin() || $config->max_image_size_admin === 'Y')) + { + $exceeded = 0; + $resize_width = $adjusted['width']; + $resize_height = $adjusted['height']; + if ($config->max_image_width > 0 && $adjusted['width'] > $config->max_image_width) + { + $resize_width = $config->max_image_width; + $resize_height = $adjusted['height'] * ($config->max_image_width / $adjusted['width']); + $exceeded++; + } + if ($config->max_image_height > 0 && $resize_height > $config->max_image_height) + { + $resize_width = $resize_width * ($config->max_image_height / $resize_height); + $resize_height = $config->max_image_height; + $exceeded++; + } + + if ($exceeded) + { + // Block upload + if ($config->max_image_size_action === 'block') + { + if ($config->max_image_width && $config->max_image_height) + { + $message = sprintf(lang('msg_exceeds_max_image_size'), $config->max_image_width, $config->max_image_height); + } + elseif ($config->max_image_width) + { + $message = sprintf(lang('msg_exceeds_max_image_width'), $config->max_image_width); + } + else + { + $message = sprintf(lang('msg_exceeds_max_image_height'), $config->max_image_height); + } + throw new Rhymix\Framework\Exception($message); + } + + $adjusted['width'] = (int)$resize_width; + $adjusted['height'] = (int)$resize_height; + if (!$is_animated && $adjusted['type'] === $image_info['type'] && $config->max_image_size_same_format !== 'Y') + { + if (in_array($config->max_image_size_same_format, ['jpg', 'png', 'webp'])) + { + $adjusted['type'] = $config->max_image_size_same_format; + } + else + { + $adjusted['type'] = 'jpg'; + } + } + } + } + + // Set force for remove EXIF data + if($config->image_remove_exif_data && $image_info['type'] === 'jpg' && function_exists('exif_read_data')) + { + if(!isset($exif)) + { + $exif = @exif_read_data($file_info['tmp_name']); + } + if($exif && (isset($exif['Model']) || isset($exif['Software']) || isset($exif['GPSVersion']))) + { + $force = true; + } + } + + // Check if this image should be reencoded anyway + if (isset($config->image_always_reencode) && $config->image_always_reencode) + { + $force = true; + } + + // Convert image if adjusted + if ($adjusted['width'] !== $image_info['width'] || + $adjusted['height'] !== $image_info['height'] || + $adjusted['type'] !== $image_info['type'] || + $adjusted['rotate'] !== 0 || $force + ) + { + $output_name = $file_info['tmp_name'] . '.converted.' . $adjusted['type']; + + // Generate an output file + if ($adjusted['type'] === 'mp4') + { + // Width and height must be even + $adjusted['width'] -= $adjusted['width'] % 2; + $adjusted['height'] -= $adjusted['height'] % 2; + + // Convert using ffmpeg + $command = \RX_WINDOWS ? escapeshellarg($config->ffmpeg_command) : $config->ffmpeg_command; + $command .= ' -nostdin -i ' . escapeshellarg($file_info['tmp_name']); + $command .= ' -movflags +faststart -pix_fmt yuv420p -c:v libx264 -crf 23'; + $command .= sprintf(' -vf "scale=%d:%d"', $adjusted['width'], $adjusted['height']); + $command .= ' ' . escapeshellarg($output_name); + @exec($command, $output, $return_var); + $result = $return_var === 0 ? true : false; + + // Generate a thumbnail image + if ($result) + { + $thumbnail_name = $file_info['tmp_name'] . '.thumbnail.jpg'; + if (FileHandler::createImageFile($file_info['tmp_name'], $thumbnail_name, $adjusted['width'], $adjusted['height'], 'jpg', 'fill', $adjusted['quality'])) + { + $file_info['thumbnail'] = $thumbnail_name; + } + } + } + elseif ($image_info['type'] === 'avif' || $image_info['type'] === 'heic') + { + // Width and height must be even + $adjusted['width'] -= $adjusted['width'] % 2; + $adjusted['height'] -= $adjusted['height'] % 2; + + // Convert using magick + $command = vsprintf('%s %s -resize %dx%d -quality %d %s %s %s', [ + \RX_WINDOWS ? escapeshellarg($config->magick_command) : $config->magick_command, + escapeshellarg($file_info['tmp_name']), + $adjusted['width'], + $adjusted['height'], + intval($adjusted['quality'] ?: 75), + '-auto-orient -strip', + '-limit memory 64MB -limit map 128MB -limit disk 1GB', + escapeshellarg($output_name), + ]); + @exec($command, $output, $return_var); + $result = $return_var === 0 ? true : false; + } + else + { + // Try resizing with GD. + $result = FileHandler::createImageFile($file_info['tmp_name'], $output_name, $adjusted['width'], $adjusted['height'], $adjusted['type'], 'fill', $adjusted['quality'], $adjusted['rotate']); + + // If the image cannot be resized using GD, try ImageMagick. + if (!$result && !empty($config->magick_command)) + { + $command = vsprintf('%s %s -resize %dx%d -quality %d %s %s %s', [ + \RX_WINDOWS ? escapeshellarg($config->magick_command) : $config->magick_command, + escapeshellarg($file_info['tmp_name']), + $adjusted['width'], + $adjusted['height'], + intval($adjusted['quality'] ?: 75), + '-auto-orient -strip', + '-limit memory 64MB -limit map 128MB -limit disk 1GB', + escapeshellarg($output_name), + ]); + @exec($command, $output, $return_var); + $result = $return_var === 0 ? true : false; + } + } + + // Change to information in the output file + if ($result && file_exists($output_name)) + { + $file_info['tmp_name'] = $output_name; + $file_info['size'] = filesize($output_name); + $file_info['type'] = Rhymix\Framework\MIME::getContentType($output_name); + $file_info['extension'] = $adjusted['type']; + $file_info['width'] = $adjusted['width']; + $file_info['height'] = $adjusted['height']; + $file_info['converted'] = true; + } + } + + return $file_info; + } + + /** + * Adjust uploaded video + */ + public function adjustUploadedVideo($file_info, $config) + { + if (!function_exists('exec') || !Rhymix\Framework\Storage::isExecutable($config->ffmpeg_command) || !Rhymix\Framework\Storage::isExecutable($config->ffprobe_command)) + { + return $file_info; + } + + // Analyze video file + $command = \RX_WINDOWS ? escapeshellarg($config->ffprobe_command) : $config->ffprobe_command; + $command .= ' -v quiet -print_format json -show_streams'; + $command .= ' ' . escapeshellarg($file_info['tmp_name']); + @exec($command, $output, $return_var); + if ($return_var !== 0 || !$output = json_decode(implode('', $output), true)) + { + return $file_info; + } + + // Get stream information + $stream_info = []; + foreach ($output['streams'] as $info) + { + $stream_info[$info['codec_type']] = $info; + } + if (empty($stream_info['video'])) + { + return $file_info; + } + + // Check if video needs to be rotated + $rotate = false; + if (isset($stream_info['video']['tags']['rotate']) && in_array($stream_info['video']['tags']['rotate'], [90, 270])) + { + $rotate = true; + } + elseif (isset($stream_info['video']['side_data_list']) && is_array($stream_info['video']['side_data_list'])) + { + foreach ($stream_info['video']['side_data_list'] as $side_data) + { + if (isset($side_data['rotation']) && in_array(abs($side_data['rotation']), [90, 270])) + { + $rotate = true; + } + } + } + + // Get video size and duration + $file_info['width'] = intval($rotate ? $stream_info['video']['height'] : $stream_info['video']['width']); + $file_info['height'] = intval($rotate ? $stream_info['video']['width'] : $stream_info['video']['height']); + $file_info['duration'] = round($stream_info['video']['duration']); + $adjusted = [ + 'width' => $file_info['width'], + 'height' => $file_info['height'], + 'duration' => $file_info['duration'], + 'type' => $file_info['extension'], + 'force' => false, + ]; + + // Check video size + if (!empty($config->max_video_size_action) && ($config->max_video_width || $config->max_video_height) && (!$this->user->isAdmin() || $config->max_video_size_admin === 'Y')) + { + $exceeded = 0; + $resize_width = $adjusted['width']; + $resize_height = $adjusted['height']; + if ($config->max_video_width > 0 && $adjusted['width'] > $config->max_video_width) + { + $resize_width = $config->max_video_width; + $resize_height = $adjusted['height'] * ($config->max_video_width / $adjusted['width']); + $exceeded++; + } + if ($config->max_video_height > 0 && $resize_height > $config->max_video_height) + { + $resize_width = $resize_width * ($config->max_video_height / $resize_height); + $resize_height = $config->max_video_height; + $exceeded++; + } + + if ($exceeded) + { + // Block upload + if ($config->max_video_size_action === 'block') + { + if ($config->max_video_width && $config->max_video_height) + { + $message = sprintf(lang('msg_exceeds_max_video_size'), $config->max_video_width, $config->max_video_height); + } + elseif ($config->max_video_width) + { + $message = sprintf(lang('msg_exceeds_max_video_width'), $config->max_video_width); + } + else + { + $message = sprintf(lang('msg_exceeds_max_video_height'), $config->max_video_height); + } + throw new Rhymix\Framework\Exception($message); + } + + // Resize + if ($config->max_video_size_action === 'resize') + { + $adjusted['width'] = (int)$resize_width; + $adjusted['height'] = (int)$resize_height; + $adjusted['type'] = 'mp4'; + } + } + } + + // Check video duration + if (!empty($config->max_video_duration_action) && $config->max_video_duration && $adjusted['duration'] > $config->max_video_duration && (!$this->user->isAdmin() || $config->max_video_duration_admin === 'Y')) + { + // Block upload + if ($config->max_video_duration_action === 'block') + { + $message = sprintf(lang('msg_exceeds_max_video_duration'), $config->max_video_duration); + throw new Rhymix\Framework\Exception($message); + } + + // Cut video + if ($config->max_video_duration_action === 'cut') + { + $adjusted['duration'] = $config->max_video_duration; + $adjusted['type'] = 'mp4'; + } + } + + // Check if this video should be force-converted to MP4 + if (isset($config->video_autoconv['any2mp4']) && $config->video_autoconv['any2mp4'] && $file_info['extension'] !== 'mp4') + { + $adjusted['type'] = 'mp4'; + } + + // Check if this video should be reencoded anyway + if (isset($config->video_always_reencode) && $config->video_always_reencode) + { + $adjusted['force'] = true; + $adjusted['type'] = 'mp4'; + } + + // Convert + if ($adjusted['width'] !== $file_info['width'] || + $adjusted['height'] !== $file_info['height'] || + $adjusted['duration'] !== $file_info['duration'] || + $adjusted['type'] !== $file_info['extension'] || + $adjusted['force'] + ) + { + $output_name = $file_info['tmp_name'] . '.converted.mp4'; + + // Width and height of video must be even + $adjusted['width'] -= $adjusted['width'] % 2; + $adjusted['height'] -= $adjusted['height'] % 2; + + // Convert using ffmpeg + $command = \RX_WINDOWS ? escapeshellarg($config->ffmpeg_command) : $config->ffmpeg_command; + $command .= ' -nostdin -i ' . escapeshellarg($file_info['tmp_name']); + if ($adjusted['duration'] !== $file_info['duration']) + { + $command .= sprintf(' -t %d', $adjusted['duration']); + } + $command .= ' -movflags +faststart -pix_fmt yuv420p -c:v libx264 -crf 23'; + $command .= empty($stream_info['audio']) ? ' -an' : ' -acodec aac'; + $command .= sprintf(' -vf "scale=%d:%d"', $adjusted['width'], $adjusted['height']); + $command .= ' ' . escapeshellarg($output_name); + @exec($command, $output, $return_var); + $result = $return_var === 0 ? true : false; + + // Update file info + if ($result) + { + $file_info['tmp_name'] = $output_name; + $file_info['size'] = filesize($output_name); + $file_info['type'] = Rhymix\Framework\MIME::getContentType($output_name); + $file_info['extension'] = $adjusted['type']; + $file_info['width'] = $adjusted['width']; + $file_info['height'] = $adjusted['height']; + $file_info['converted'] = true; + } + } + + // Set original type to GIF if this video is short and has no audio stream. + if (isset($config->video_mp4_gif_time) && $config->video_mp4_gif_time) + { + if ($file_info['extension'] === 'mp4' && $file_info['duration'] <= $config->video_mp4_gif_time && empty($stream_info['audio'])) + { + $file_info['original_type'] = 'image/gif'; + } + } + + // Generate a thumbnail image + if ($config->video_thumbnail) + { + $thumbnail_name = $file_info['tmp_name'] . '.thumbnail.jpeg'; + $command = \RX_WINDOWS ? escapeshellarg($config->ffmpeg_command) : $config->ffmpeg_command; + $command .= sprintf(' -ss 00:00:00.%d -i %s -vframes 1', mt_rand(0, 99), escapeshellarg($file_info['tmp_name'])); + $command .= ' -nostdin ' . escapeshellarg($thumbnail_name); + @exec($command, $output, $return_var); + if ($return_var === 0) + { + $file_info['thumbnail'] = $thumbnail_name; + } + } + + return $file_info; + } + /** * Delete the attachment * @@ -1028,7 +1627,7 @@ class fileController extends file * * * @param array|int $file_list or $file_srl - * @return Object + * @return BaseObject */ function deleteFile($file_list) { @@ -1036,12 +1635,16 @@ class fileController extends file { $file_list = explode(',', $file_list); } - + if(empty($file_list)) { return new BaseObject(); } - + + $config = FileModel::getFileConfig(); + $oDB = DB::getInstance(); + $oDB->begin(); + foreach($file_list as $file) { if(!is_object($file)) @@ -1050,101 +1653,131 @@ class fileController extends file { continue; } - $file = getModel('file')->getFile($file_srl); + $file = FileModel::getFile($file_srl); } - + if(empty($file->file_srl)) { continue; } - + // Call a trigger (before) $output = ModuleHandler::triggerCall('file.deleteFile', 'before', $file); if(!$output->toBool()) return $output; - + // Remove from the DB $output = executeQuery('file.deleteFile', $file); - if(!$output->toBool()) return $output; - + if(!$output->toBool()) + { + $oDB->rollback(); + return $output; + } + + if($config->save_changelog === 'Y') + { + $clargs = new stdClass; + $clargs->change_type = 'D'; + $clargs->file_srl = $file->file_srl; + $clargs->file_size = $file->file_size; + $clargs->uploaded_filename = $file->uploaded_filename; + $output = executeQuery('file.insertFileChangelog', $clargs); + if(!$output->toBool()) + { + $oDB->rollback(); + return $output; + } + } + // If successfully deleted, remove the file Rhymix\Framework\Storage::delete(FileHandler::getRealPath($file->uploaded_filename)); - + // Call a trigger (after) ModuleHandler::triggerCall('file.deleteFile', 'after', $file); - + // Remove empty directories Rhymix\Framework\Storage::deleteEmptyDirectory(dirname(FileHandler::getRealPath($file->uploaded_filename)), true); + + // Remove thumbnail + if ($file->thumbnail_filename) + { + Rhymix\Framework\Storage::delete(FileHandler::getRealPath($file->thumbnail_filename)); + Rhymix\Framework\Storage::deleteEmptyDirectory(dirname(FileHandler::getRealPath($file->thumbnail_filename)), true); + } } - + + $oDB->commit(); return new BaseObject(); } - + /** * Delete all attachments of a particular document * * @param int $upload_target_srl Upload target srl to delete files - * @return Object + * @return BaseObject */ function deleteFiles($upload_target_srl) { // Get a list of attachements - $oFileModel = getModel('file'); - $file_list = $oFileModel->getFiles($upload_target_srl); - + $file_list = FileModel::getFiles($upload_target_srl); + // Success returned if no attachement exists if(empty($file_list)) { return new BaseObject(); } - + // Delete the file return $this->deleteFile($file_list); } - + /** * Delete the attachment of a particular module * * @param int $module_srl Sequence of module to delete files - * @return Object + * @return BaseObject */ function deleteModuleFiles($module_srl) { // Get a full list of attachments $args = new stdClass; $args->module_srl = $module_srl; - $output = executeQueryArray('file.getModuleFiles', $args); - if(!$output->toBool() || empty($file_list = $output->data)) + $output = executeQueryArray('file.getModuleFilesProper', $args); + if (!$output->toBool()) { return $output; } - - // Delete the file - return $this->deleteFile($file_list); + if (!$output->data) + { + return; + } + + // Delete each file. + return $this->deleteFile($output->data); } - + /** * Move an attachement to the other document * * @param int $source_srl Sequence of target to move * @param int $target_module_srl New squence of module * @param int $target_srl New sequence of target - * @return void + * @return ?BaseObject */ function moveFile($source_srl, $target_module_srl, $target_srl) { if($source_srl == $target_srl) return; - $oFileModel = getModel('file'); - $file_list = $oFileModel->getFiles($source_srl); + $file_list = FileModel::getFiles($source_srl); if(!$file_list) return; - $file_count = count($file_list); + $config = FileModel::getFileConfig(); + $oDB = DB::getInstance(); + $oDB->begin(); - for($i=0;$i<$file_count;$i++) + foreach($file_list as $i => $file_info) { - unset($file_info); - $file_info = $file_list[$i]; $old_file = $file_info->uploaded_filename; + // Determine the file path by checking if the file is an image or other kinds if (Rhymix\Framework\Filters\FilenameFilter::isDirectDownload($file_info->source_filename)) { @@ -1159,31 +1792,55 @@ class fileController extends file $random_filename = basename($file_info->uploaded_filename) ?: Rhymix\Framework\Security::getRandom(32, 'hex'); $new_file = $path . $random_filename; } + // Pass if a target document to move is same if($old_file === $new_file) continue; + // Create a directory FileHandler::makeDir($path); + // Move the file FileHandler::rename($old_file, $new_file); + // Delete old path Rhymix\Framework\Storage::deleteEmptyDirectory(dirname(FileHandler::getRealPath($old_file)), true); + // Update DB information $args = new stdClass; $args->file_srl = $file_info->file_srl; $args->uploaded_filename = $new_file; $args->module_srl = $file_info->module_srl; $args->upload_target_srl = $target_srl; - executeQuery('file.updateFile', $args); + $output = executeQuery('file.updateFile', $args); + + if($config->save_changelog === 'Y') + { + $clargs = new stdClass; + $clargs->change_type = 'M'; + $clargs->file_srl = $file_info->file_srl; + $clargs->file_size = $file_info->file_size; + $clargs->uploaded_filename = $new_file; + $clargs->previous_filename = $old_file; + $output = executeQuery('file.insertFileChangelog', $clargs); + if(!$output->toBool()) + { + $oDB->rollback(); + return $output; + } + } } + + $oDB->commit(); + return new BaseObject(); } - + function copyFile($source_file, $module_srl, $upload_target_srl, &$content = null) { $file_info = array(); $file_info['name'] = $source_file->source_filename; $file_info['tmp_name'] = $source_file->uploaded_filename; $copied_file = $this->insertFile($file_info, $module_srl, $upload_target_srl, 0, true); - + if($content) { // if image/video files @@ -1200,46 +1857,60 @@ class fileController extends file $content = str_replace('sid=' . $source_file->sid, 'sid=' . $copied_file->get('sid'), $content); } } - + return $copied_file; } - + function copyFiles($source_file_list, $module_srl, $upload_target_srl, &$content = null) { if(!is_array($source_file_list)) { - $source_file_list = getModel('file')->getFiles($source_file_list, array(), 'file_srl', true); + $source_file_list = FileModel::getFiles($source_file_list, array(), 'file_srl', true); } - + foreach($source_file_list as $source_file) { $this->copyFile($source_file, $module_srl, $upload_target_srl, $content); } } - + public function procFileSetCoverImage() { $vars = Context::getRequestVars(); - $logged_info = Context::get('logged_info'); - if(!$vars->editor_sequence) throw new Rhymix\Framework\Exceptions\InvalidRequest; + // Exit a session if there is neither upload permission nor information + $editor_sequence = $vars->editor_sequence ?? 0; + if (!$vars->editor_sequence) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + if (!$_SESSION['upload_info'][$editor_sequence]->enabled) + { + throw new Rhymix\Framework\Exceptions\NotPermitted; + } + $upload_target_srl = $_SESSION['upload_info'][$editor_sequence]->upload_target_srl; + if (!$upload_target_srl) + { + throw new Rhymix\Framework\Exceptions\TargetNotFound; + } - $upload_target_srl = $_SESSION['upload_info'][$vars->editor_sequence]->upload_target_srl; - - $oFileModel = getModel('file'); - $file_info = $oFileModel->getFile($vars->file_srl); - - if(!$file_info) throw new Rhymix\Framework\Exceptions\TargetNotFound; - - if(!$this->manager && !$file_info->member_srl === $logged_info->member_srl) throw new Rhymix\Framework\Exceptions\NotPermitted; + $file_info = FileModel::getFile($vars->file_srl); + if (!$file_info || $file_info->upload_target_srl != $upload_target_srl) + { + throw new Rhymix\Framework\Exceptions\TargetNotFound; + } + if(!$this->grant->manager && $file_info->member_srl != $this->user->member_srl) + { + throw new Rhymix\Framework\Exceptions\NotPermitted; + } $args = new stdClass(); $args->file_srl = $vars->file_srl; $args->upload_target_srl = $upload_target_srl; - $oDB = &DB::getInstance(); + $oDB = DB::getInstance(); $oDB->begin(); - + $args->cover_image = 'N'; $output = executeQuery('file.updateClearCoverImage', $args); if(!$output->toBool()) @@ -1250,7 +1921,6 @@ class fileController extends file if($file_info->cover_image != 'Y') { - $args->cover_image = 'Y'; $output = executeQuery('file.updateCoverImage', $args); if(!$output->toBool()) @@ -1258,7 +1928,6 @@ class fileController extends file $oDB->rollback(); return $output; } - } $oDB->commit(); @@ -1269,10 +1938,10 @@ class fileController extends file $thumbnail_path = sprintf('files/thumbnails/%s', getNumberingPath($upload_target_srl, 3)); Filehandler::removeFilesInDir($thumbnail_path); } - + /** * Determine storage path based on file.folder_structure configuration. - * + * * @param string $file_type images or binary * @param int $file_srl * @param int $module_srl @@ -1281,26 +1950,25 @@ class fileController extends file * @param bool $absolute_path * @return string */ - public function getStoragePath($file_type, $file_srl, $module_srl = 0, $upload_target_srl = 0, $regdate = '', $absolute_path = true) + public static function getStoragePath($file_type, $file_srl, $module_srl = 0, $upload_target_srl = 0, $regdate = '', $absolute_path = true) { // 변수 확인 및 넘어오지 않은 변수 기본값 지정 $file_srl = intval($file_srl); $module_srl = intval($module_srl); $upload_target_srl = $upload_target_srl ?: $file_srl; $regdate = $regdate ?: date('YmdHis'); - + // 시스템 설정 참고 (기존 사용자는 1, 신규 설치시 2가 기본값임) $folder_structure = config('file.folder_structure'); - + // 기본 경로 지정 $prefix = $absolute_path ? \RX_BASEDIR : './'; - + // 2: 년월일 단위로 정리 if ($folder_structure == 2) { return sprintf('%sfiles/attach/%s/%04d/%02d/%02d/', $prefix, $file_type, substr($regdate, 0, 4), substr($regdate, 4, 2), substr($regdate, 6, 2)); } - // 1 or 0: module_srl 및 업로드 대상 번호에 따라 3자리씩 끊어서 정리 else { @@ -1321,38 +1989,49 @@ class fileController extends file { return; } - + function triggerMoveDocument($obj) { $obj->upload_target_srls = $obj->document_srls; - executeQuery('file.updateFileModule', $obj); - executeQuery('file.updateFileModuleComment', $obj); + $output = executeQuery('file.updateFileModule', $obj); + if (!$output->toBool()) + { + return $output; + } + $output = executeQuery('file.updateFileModuleComment', $obj); + if (!$output->toBool()) + { + return $output; + } } - + function triggerAddCopyDocument(&$obj) { if(!$obj->source->uploaded_count) { return; } - + $this->copyFiles($obj->source->document_srl, $obj->copied->module_srl, $obj->copied->document_srl, $obj->copied->content); + $this->setFilesValid($obj->copied->document_srl, 'doc'); + DocumentController::getInstance()->updateUploadedCount($obj->copied->document_srl); } - + function triggerAddCopyCommentByDocument(&$obj) { if(!$obj->source->uploaded_count) { return; } - + $this->copyFiles($obj->source->comment_srl, $obj->copied->module_srl, $obj->copied->comment_srl, $obj->copied->content); + $this->setFilesValid($obj->copied->comment_srl, 'com'); + CommentController::getInstance()->updateUploadedCount($obj->copied->comment_srl); } - + function triggerCopyModule(&$obj) { - $oModuleModel = getModel('module'); - $fileConfig = $oModuleModel->getModulePartConfig('file', $obj->originModuleSrl); + $fileConfig = ModuleModel::getModulePartConfig('file', $obj->originModuleSrl); $oModuleController = getController('module'); if(is_array($obj->moduleSrlList)) diff --git a/modules/file/file.model.php b/modules/file/file.model.php index f3b48d35f..ba69f6c6e 100644 --- a/modules/file/file.model.php +++ b/modules/file/file.model.php @@ -4,13 +4,13 @@ * Model class of the file module * @author NAVER (developers@xpressengine.com) */ -class fileModel extends file +class FileModel extends File { /** * Initialization * @return void */ - function init() + public function init() { } @@ -20,119 +20,295 @@ class fileModel extends file * It is used when a file list of the upload_target_srl is requested for creating/updating a document. * Attempt to replace with sever-side session if upload_target_srl is not yet determined * - * @return void + * @return object */ - function getFileList() + public function getFileList($editor_sequence = null, $upload_target_srl = null, $upload_target_type = null) { - $oModuleModel = getModel('module'); - - $mid = Context::get('mid'); - $editor_sequence = Context::get('editor_sequence'); - $upload_target_srl = Context::get('upload_target_srl'); - if(!$upload_target_srl) $upload_target_srl = $_SESSION['upload_info'][$editor_sequence]->upload_target_srl; + $file_list = []; + $attached_size = 0; + $editor_sequence = $editor_sequence ?? intval(Context::get('editor_sequence')); + $upload_target_srl = $upload_target_srl ?? $_SESSION['upload_info'][$editor_sequence]->upload_target_srl ?? 0; + // Get uploaded files if($upload_target_srl) { - $oDocumentModel = getModel('document'); - $oCommentModel = getModel('comment'); - $logged_info = Context::get('logged_info'); - - $oDocument = $oDocumentModel->getDocument($upload_target_srl); - - // comment 권한 확인 - if(!$oDocument->isExists()) + if (!$upload_target_type || $upload_target_type === 'doc' || $upload_target_type === 'document') { - $oComment = $oCommentModel->getComment($upload_target_srl); - if($oComment->isExists() && !$oComment->isAccessible()) - { - throw new Rhymix\Framework\Exceptions\NotPermitted; - } - - $oDocument = $oDocumentModel->getDocument($oComment->get('document_srl')); + $oDocument = DocumentModel::getDocument($upload_target_srl); + } + else + { + $oDocument = null; } - // document 권한 확인 - if($oDocument->isExists() && !$oDocument->isAccessible()) + // Check permissions of the comment + if(!$oDocument || !$oDocument->isExists()) + { + if (!$upload_target_type || $upload_target_type === 'com' || $upload_target_type === 'comment') + { + $oComment = CommentModel::getComment($upload_target_srl); + if($oComment->isExists()) + { + if(!$oComment->isAccessible()) + { + throw new Rhymix\Framework\Exceptions\NotPermitted; + } + $oDocument = DocumentModel::getDocument($oComment->get('document_srl')); + } + } + } + + // Check permissions of the document + if($oDocument && $oDocument->isExists() && !$oDocument->isAccessible()) { throw new Rhymix\Framework\Exceptions\NotPermitted; } - // 모듈 권한 확인 - if($oDocument->isExists()) + // Check permissions of the module + $module_srl = isset($oComment) ? $oComment->get('module_srl') : ($oDocument ? $oDocument->get('module_srl') : 0); + if ($module_srl) { - $grant = $oModuleModel->getGrant($oModuleModel->getModuleInfoByModuleSrl($oDocument->get('module_srl')), $logged_info); + $module_info = ModuleModel::getModuleInfoByModuleSrl($module_srl); + if(empty($module_info->module_srl)) + { + throw new Rhymix\Framework\Exceptions\NotPermitted; + } + $grant = ModuleModel::getGrant($module_info, Context::get('logged_info')); if(!$grant->access) { throw new Rhymix\Framework\Exceptions\NotPermitted; } } - $tmp_files = $this->getFiles($upload_target_srl); - if($tmp_files instanceof BaseObject && !$tmp_files->toBool()) return $tmp_files; - $files = array(); - - foreach($tmp_files as $file_info) + // Set file list + $filter_type = $_SESSION['upload_info'][$editor_sequence]->upload_target_type ?? null; + $files = self::getFiles($upload_target_srl, [], 'file_srl', false, $filter_type, true); + foreach ($files as $file_info) { - if(!$file_info->file_srl) continue; - $obj = new stdClass; $obj->file_srl = $file_info->file_srl; $obj->source_filename = $file_info->source_filename; + $obj->thumbnail_filename = $file_info->thumbnail_filename; $obj->file_size = $file_info->file_size; $obj->disp_file_size = FileHandler::filesize($file_info->file_size); - if($file_info->direct_download == 'N') + $obj->mime_type = $file_info->mime_type; + $obj->original_type = $file_info->original_type; + $obj->width = $file_info->width; + $obj->height = $file_info->height; + $obj->duration = $file_info->duration; + $obj->direct_download = $file_info->direct_download; + $obj->cover_image = ($file_info->cover_image === 'Y') ? true : false; + if($obj->direct_download === 'Y' && self::isDownloadable($file_info)) { - $obj->download_url = $this->getDownloadUrl($file_info->file_srl, $file_info->sid, $file_info->module_srl); + $obj->download_url = self::getDirectFileUrl($file_info->uploaded_filename); } else { - $obj->download_url = $this->getDirectFileUrl($file_info->uploaded_filename); + $obj->download_url = self::getDirectFileUrl($file_info->download_url); } - $obj->direct_download = $file_info->direct_download; - $obj->cover_image = ($file_info->cover_image === 'Y') ? true : false; - $files[] = $obj; + + $file_list[] = $obj; $attached_size += $file_info->file_size; } } + + // Initialize return value + $result = new \stdClass; + $result->files = $file_list; + $result->editor_sequence = $editor_sequence; + $result->upload_target_srl = $upload_target_srl; + $result->upload_status = self::getUploadStatus($attached_size); + + // Set upload config + $upload_config = self::getUploadConfig(); + $result->attached_size = FileHandler::filesize($attached_size); + $result->left_size = max(0, ($upload_config->allowed_attach_size * 1024 * 1024) - $attached_size); + if($this->user->isAdmin()) + { + $result->allowed_filesize = sprintf('%s (%s)', lang('common.unlimited'), lang('common.admin')); + $result->allowed_attach_size = sprintf('%s (%s)', lang('common.unlimited'), lang('common.admin')); + $result->allowed_extensions = []; + } else { - $upload_target_srl = 0; - $attached_size = 0; - $files = array(); + $result->allowed_filesize = FileHandler::filesize($upload_config->allowed_filesize * 1024 * 1024); + $result->allowed_attach_size = FileHandler::filesize($upload_config->allowed_attach_size * 1024 * 1024); + $result->allowed_extensions = $upload_config->allowed_extensions; } - // Display upload status - $upload_status = $this->getUploadStatus($attached_size); - // Check remained file size until upload complete - //$config = $oModuleModel->getModuleInfoByMid($mid); //perhaps config varialbles not used + if(!$this->user->isAdmin()) + { + if (isset($_SESSION['upload_info'][$editor_sequence]->allowed_filesize)) + { + $result->allowed_filesize = FileHandler::filesize($_SESSION['upload_info'][$editor_sequence]->allowed_filesize); + $result->allowed_attach_size = FileHandler::filesize($_SESSION['upload_info'][$editor_sequence]->allowed_filesize); + } + if (isset($_SESSION['upload_info'][$editor_sequence]->allowed_extensions)) + { + $result->allowed_extensions = $_SESSION['upload_info'][$editor_sequence]->allowed_extensions; + } + } + $result->allowed_filetypes = $upload_config->allowed_filetypes ?? null; - $file_config = $this->getUploadConfig(); - $left_size = $file_config->allowed_attach_size*1024*1024 - $attached_size; - // Settings of required information - $attached_size = FileHandler::filesize($attached_size); - $allowed_attach_size = FileHandler::filesize($file_config->allowed_attach_size*1024*1024); - $allowed_filesize = FileHandler::filesize($file_config->allowed_filesize*1024*1024); - $allowed_filetypes = $file_config->allowed_filetypes; - $this->add("files",$files); - $this->add("editor_sequence",$editor_sequence); - $this->add("upload_target_srl",$upload_target_srl); - $this->add("upload_status",$upload_status); - $this->add("left_size",$left_size); - $this->add('attached_size', $attached_size); - $this->add('allowed_attach_size', $allowed_attach_size); - $this->add('allowed_filesize', $allowed_filesize); - $this->add('allowed_filetypes', $allowed_filetypes); + // for compatibility + foreach ($result as $key => $val) + { + $this->add($key, $val); + } + return $result; + } + + /** + * Check if the file is downloadable + * + * @param object $file_info + * @param object $member_info + * @return bool + */ + public static function isDownloadable($file_info, $member_info = null) + { + if(!$member_info) + { + $member_info = Rhymix\Framework\Session::getMemberInfo(); + } + if(self::isDeletable($file_info, $member_info)) + { + return true; + } + + // Check the validity + if($file_info->isvalid !== 'Y') + { + return false; + } + + // Check download groups + $config = self::getFileConfig($file_info->module_srl); + if($config->download_groups) + { + if(empty($member_info->member_srl)) + { + return false; + } + if(!isset($member_info->group_list)) + { + $member_info->group_list = MemberModel::getMemberGroups($member_info->member_srl); + } + $is_group = false; + foreach($config->download_groups as $group_srl) + { + if(isset($member_info->group_list[$group_srl])) + { + $is_group = true; + break; + } + } + if(!$is_group) + { + return false; + } + } + + return true; + } + + /** + * Check if the file is indexable + * @param string $filename + * @param object $file_module_config + * @return bool + */ + public static function isIndexable($filename, $file_module_config) + { + if($file_module_config->allow_indexing_format) + { + $allow_indexing_format_array = array(); + $allow_indexing_format_array = explode(',', $file_module_config->allow_indexing_format); + if(!is_array($allow_indexing_format_array)) $allow_indexing_format_array[0] = $file_module_config->allow_indexing_format; + + foreach($allow_indexing_format_array as $val) + { + $val = trim($val); + if(preg_match("/\.{$val}$/i", $filename)) + { + return true; + } + } + } + + return false; + } + + /** + * Check if the file is deletable + * + * @param object $file_info + * @param object $member_info + * @return bool + */ + public static function isDeletable($file_info, $member_info = null) + { + if(!$member_info) + { + $member_info = Rhymix\Framework\Session::getMemberInfo(); + } + if($member_info->is_admin === 'Y' || $member_info->member_srl == $file_info->member_srl) + { + return true; + } + if(isset($_SESSION['__XE_UPLOADING_FILES_INFO__'][$file_info->file_srl])) + { + return true; + } + + // Check permissions of the module + $module_info = ModuleModel::getModuleInfoByModuleSrl($file_info->module_srl); + if(empty($module_info->module_srl)) + { + return false; + } + $grant = ModuleModel::getGrant($module_info, $member_info); + if($grant->manager) + { + return true; + } + + // Check permissions of the document + $oDocument = DocumentModel::getDocument($file_info->upload_target_srl); + if($oDocument->isExists() && $oDocument->isGranted()) + { + return true; + } + + // Check permissions of the comment + $oComment = CommentModel::getComment($file_info->upload_target_srl); + if($oComment->isExists() && $oComment->isGranted()) + { + return true; + } + + return false; } /** * Return number of attachments which belongs to a specific document * * @param int $upload_target_srl The sequence to get a number of files + * @param ?string $upload_target_type + * @param bool $include_null_target_type * @return int Returns a number of files */ - function getFilesCount($upload_target_srl) + public static function getFilesCount($upload_target_srl, $upload_target_type = null, $include_null_target_type = false) { $args = new stdClass(); $args->upload_target_srl = $upload_target_srl; + if ($upload_target_type) + { + $args->upload_target_type = $upload_target_type; + if ($include_null_target_type) + { + $args->include_null_target_type = true; + } + } $output = executeQuery('file.getFilesCount', $args); return (int)$output->data->count; } @@ -142,73 +318,82 @@ class fileModel extends file * * @param int $file_srl The sequence of file to get url * @param string $sid + * @param int $unused (unused) + * @param string $source_filename * @return string Returns a url */ - function getDownloadUrl($file_srl, $sid, $module_srl="") + public static function getDownloadUrl($file_srl, $sid, $unused = 0, $source_filename = null) { - return sprintf('?module=%s&act=%s&file_srl=%s&sid=%s&module_srl=%s', 'file', 'procFileDownload', $file_srl, $sid, $module_srl); + if ($source_filename && config('use_rewrite') && self::getFileConfig()->download_short_url === 'Y') + { + return sprintf('files/download/link/%d/%s/%s', $file_srl, $sid, rawurlencode(preg_replace('/\.\.+/', '.', $source_filename))); + } + else + { + return sprintf('index.php?module=%s&act=%s&file_srl=%s&sid=%s', 'file', 'procFileDownload', $file_srl, $sid); + } } - + /** * Return direct download file url * * @param string $path * @return string */ - function getDirectFileUrl($path) + public static function getDirectFileUrl($path) { - if(dirname($_SERVER['SCRIPT_NAME']) == '/' || dirname($_SERVER['SCRIPT_NAME']) == '\\') - { - return '/' . substr($path, 2); - } - - return dirname($_SERVER['SCRIPT_NAME']) . '/' . substr($path, 2); + return \RX_BASEURL . ltrim($path, './'); } - + /** * Get file configurations * * @param int $module_srl If set this, returns specific module's configuration. Otherwise returns global configuration. * @return object Returns configuration. */ - function getFileConfig($module_srl = null) + public static function getFileConfig($module_srl = null) { - // Get configurations (using module model object) - $oModuleModel = getModel('module'); - - $file_module_config = $oModuleModel->getModuleConfig('file'); - - if($module_srl) $file_config = $oModuleModel->getModulePartConfig('file',$module_srl); - if(!$file_config) $file_config = $file_module_config; - - $config = new stdClass(); - - if($file_config) + $config = ModuleModel::getModuleConfig('file'); + $config = is_object($config) ? clone $config : new stdClass(); + if($module_srl) { - $config->allowed_filesize = $file_config->allowed_filesize; - $config->allowed_attach_size = $file_config->allowed_attach_size; - $config->allowed_filetypes = $file_config->allowed_filetypes; - $config->inline_download_format = $file_config->inline_download_format; - $config->download_grant = $file_config->download_grant; - $config->allow_outlink = $file_config->allow_outlink; - $config->allow_outlink_site = $file_config->allow_outlink_site; - $config->allow_outlink_format = $file_config->allow_outlink_format; + $module_config = ModuleModel::getModulePartConfig('file', $module_srl); + foreach((array)$module_config as $key => $value) + { + $config->$key = $value; + } } - // Property for all files comes first than each property - if(!$config->allowed_filesize) $config->allowed_filesize = $file_module_config->allowed_filesize; - if(!$config->allowed_attach_size) $config->allowed_attach_size = $file_module_config->allowed_attach_size; - if(!$config->allowed_filetypes) $config->allowed_filetypes = $file_module_config->allowed_filetypes; - if(!$config->allow_outlink) $config->allow_outlink = $file_module_config->allow_outlink; - if(!$config->allow_outlink_site) $config->allow_outlink_site = $file_module_config->allow_outlink_site; - if(!$config->allow_outlink_format) $config->allow_outlink_format = $file_module_config->allow_outlink_format; - if(!$config->download_grant) $config->download_grant = $file_module_config->download_grant; + // Default setting if not exists - if(!$config->allowed_filesize) $config->allowed_filesize = '2'; - if(!$config->allowed_attach_size) $config->allowed_attach_size = '3'; - if(!$config->allowed_filetypes) $config->allowed_filetypes = '*.*'; - if(!$config->allow_outlink) $config->allow_outlink = 'Y'; - if(!$config->download_grant) $config->download_grant = array(); - if(!$config->inline_download_format) $config->inline_download_format = array(); + $config->allowed_filesize = $config->allowed_filesize ?? '2'; + $config->allowed_attach_size = $config->allowed_attach_size ?? '3'; + $config->allowed_filetypes = $config->allowed_filetypes ?? '*.*'; + $config->allow_outlink = $config->allow_outlink ?? 'Y'; + $config->download_grant = $config->download_grant ?? []; + $config->download_short_url = $config->download_short_url ?? 'N'; + $config->inline_download_format = $config->inline_download_format ?? []; + $config->image_autoconv = $config->image_autoconv ?? []; + $config->image_quality_adjustment = $config->image_quality_adjustment ?? 75; + $config->video_mp4_gif_time = $config->video_mp4_gif_time ?? 0; + $config->ffmpeg_command = $config->ffmpeg_command ?? '/usr/bin/ffmpeg'; + $config->ffprobe_command = $config->ffprobe_command ?? '/usr/bin/ffprobe'; + $config->magick_command = $config->magick_command ?? ''; + + // Set allowed_extensions + if(!isset($config->allowed_extensions)) + { + $config->allowed_extensions = []; + $config->allowed_filetypes = trim($config->allowed_filetypes); + if($config->allowed_filetypes !== '*.*') + { + $config->allowed_extensions = array_map(function($ext) { + return strtolower(substr(strrchr(trim($ext), '.'), 1)); + }, explode(';', $config->allowed_filetypes)); + } + } + + // Set download_groups + $config->download_groups = is_array($config->download_grant) ? array_filter($config->download_grant) : []; return $config; } @@ -218,9 +403,9 @@ class fileModel extends file * * @param int $file_srl The sequence of file to get information * @param array $columnList The list of columns to get from DB - * @return Object|object|array If error returns an instance of Object. If result set is one returns a object that contins file information. If result set is more than one returns array of object. + * @return object|array If error returns an instance of Object. If result set is one returns a object that contins file information. If result set is more than one returns array of object. */ - function getFile($file_srl, $columnList = array()) + public static function getFile($file_srl, $columnList = array()) { $args = new stdClass(); $args->file_srl = $file_srl; @@ -231,7 +416,7 @@ class fileModel extends file if(count($output->data) == 1) { $file = $output->data[0]; - $file->download_url = $this->getDownloadUrl($file->file_srl, $file->sid, $file->module_srl); + $file->download_url = self::getDownloadUrl($file->file_srl, $file->sid, 0, $file->source_filename); return $file; } @@ -244,7 +429,7 @@ class fileModel extends file foreach($output->data as $key=>$value) { $file = $value; - $file->download_url = $this->getDownloadUrl($file->file_srl, $file->sid, $file->module_srl); + $file->download_url = self::getDownloadUrl($file->file_srl, $file->sid, 0, $file->source_filename); $fileList[] = $file; } } @@ -258,27 +443,53 @@ class fileModel extends file * @param int $upload_target_srl The sequence of target to get file list * @param array $columnList The list of columns to get from DB * @param string $sortIndex The column that used as sort index + * @param bool $valid_files_only + * @param ?string $upload_target_type + * @param bool $include_null_target_type * @return array Returns array of object that contains file information. If no result returns null. */ - function getFiles($upload_target_srl, $columnList = array(), $sortIndex = 'file_srl', $ckValid = false) + public static function getFiles($upload_target_srl, $columnList = array(), $sortIndex = 'file_srl', $valid_files_only = false, $upload_target_type = null, $include_null_target_type = false) { $args = new stdClass(); $args->upload_target_srl = $upload_target_srl; $args->sort_index = $sortIndex; - if($ckValid) $args->isvalid = 'Y'; + if ($valid_files_only) + { + $args->isvalid = 'Y'; + } + if ($upload_target_type) + { + $args->upload_target_type = $upload_target_type; + if ($include_null_target_type) + { + $args->include_null_target_type = true; + } + } + $output = executeQueryArray('file.getFiles', $args, $columnList); if(!$output->data) { return array(); } - + $fileList = array(); + $nullList = array(); foreach ($output->data as $file) { + $file->download_url = self::getDownloadUrl($file->file_srl, $file->sid, 0, $file->source_filename); $file->source_filename = escape($file->source_filename, false); - $file->download_url = $this->getDownloadUrl($file->file_srl, $file->sid, $file->module_srl); $fileList[] = $file; + if ($file->upload_target_type === null) + { + $nullList[] = $file->file_srl; + } } + + if (count($nullList) && $upload_target_type) + { + FileController::getInstance()->updateTargetType($nullList, $upload_target_type); + } + return $fileList; } @@ -287,28 +498,22 @@ class fileModel extends file * * @return object Returns a file configuration of current module. If user is admin, returns PHP's max file size and allow all file types. */ - function getUploadConfig() + public static function getUploadConfig($module_srl = 0) { - $logged_info = Context::get('logged_info'); - - $module_srl = Context::get('module_srl'); - // Get the current module if module_srl doesn't exist - if(!$module_srl) + if (!$module_srl) { - $current_module_info = Context::get('current_module_info'); - $module_srl = $current_module_info->module_srl; + $module_srl = Context::get('module_srl') ?: (Context::get('current_module_info')->module_srl ?? 0); } - $file_config = $this->getFileConfig($module_srl); - - if($logged_info->is_admin == 'Y') + $config = self::getFileConfig($module_srl); + if (Rhymix\Framework\Session::isAdmin()) { - $oModuleModel = getModel('module'); - $module_config = $oModuleModel->getModuleConfig('file'); - $file_config->allowed_filesize = max($file_config->allowed_filesize, $module_config->allowed_filesize); - $file_config->allowed_attach_size = max($file_config->allowed_attach_size, $module_config->allowed_attach_size); - $file_config->allowed_filetypes = '*.*'; + $module_config = ModuleModel::getModuleConfig('file'); + $config->allowed_filesize = max($config->allowed_filesize, $module_config->allowed_filesize); + $config->allowed_attach_size = max($config->allowed_attach_size, $module_config->allowed_attach_size); + $config->allowed_extensions = []; + $config->allowed_filetypes = '*.*'; } - return $file_config; + return $config; } /** @@ -317,9 +522,9 @@ class fileModel extends file * @param int $attached_size * @return string */ - function getUploadStatus($attached_size = 0) + public static function getUploadStatus($attached_size = 0) { - $file_config = $this->getUploadConfig(); + $file_config = self::getUploadConfig(); if (Context::get('allow_chunks') === 'Y') { $allowed_filesize = $file_config->allowed_filesize * 1024 * 1024; @@ -328,7 +533,7 @@ class fileModel extends file { $allowed_filesize = min($file_config->allowed_filesize * 1024 * 1024, FileHandler::returnBytes(ini_get('upload_max_filesize')), FileHandler::returnBytes(ini_get('post_max_size'))); } - + // Display upload status $upload_status = sprintf( '%s : %s/ %s
                %s : %s (%s : %s)', @@ -344,45 +549,23 @@ class fileModel extends file } /** - * Return file configuration of the module + * method for compatibility * - * @param int $module_srl The sequence of module to get configuration - * @return object + * @deprecated */ - function getFileModuleConfig($module_srl) + public static function getFileModuleConfig($module_srl) { - return $this->getFileConfig($module_srl); + return self::getFileConfig($module_srl); } /** - * Returns a grant of file + * method for compatibility * - * @param object $file_info The file information to get grant - * @param object $member_info The member information to get grant - * @return object Returns a grant of file + * @deprecated */ - function getFileGrant($file_info, $member_info) + public static function getFileGrant($file_info, $member_info) { - if(!$file_info) return null; - - $file_grant = new stdClass; - - if($_SESSION['__XE_UPLOADING_FILES_INFO__'][$file_info->file_srl]) - { - $file_grant->is_deletable = true; - return $file_grant; - } - - $oModuleModel = getModel('module'); - $grant = $oModuleModel->getGrant($oModuleModel->getModuleInfoByModuleSrl($file_info->module_srl), $member_info); - - $oDocumentModel = getModel('document'); - $oDocument = $oDocumentModel->getDocument($file_info->upload_target_srl); - if($oDocument->isExists()) $document_grant = $oDocument->isGranted(); - - $file_grant->is_deletable = ($document_grant || $member_info->is_admin == 'Y' || $member_info->member_srl == $file_info->member_srl || $grant->manager); - - return $file_grant; + return (object)['is_deletable' => self::isDeletable($file_info, $member_info)]; } } /* End of file file.model.php */ diff --git a/modules/file/file.view.php b/modules/file/file.view.php index abe56274d..8f58f7eee 100644 --- a/modules/file/file.view.php +++ b/modules/file/file.view.php @@ -4,7 +4,7 @@ * The view class file module * @author NAVER (developers@xpressengine.com) */ -class fileView extends file +class FileView extends File { /** * Initialization @@ -24,27 +24,41 @@ class fileView extends file function triggerDispFileAdditionSetup(&$obj) { $current_module_srl = Context::get('module_srl'); - $current_module_srls = Context::get('module_srls'); - - if(!$current_module_srl && !$current_module_srls) + if(!$current_module_srl) { // Get information of the current module - $current_module_info = Context::get('current_module_info'); - $current_module_srl = $current_module_info->module_srl; - if(!$current_module_srl) return; + $current_module_srl = Context::get('current_module_info')->module_srl ?? 0; + if(!$current_module_srl) + { + return; + } } + // Get file configurations of the module - $oFileModel = getModel('file'); - $file_config = $oFileModel->getFileModuleConfig($current_module_srl); - Context::set('file_config', $file_config); + $config = FileModel::getFileConfig($current_module_srl); + if (!isset($config->use_default_file_config)) + { + $config->use_default_file_config = 'Y'; + } + if (!isset($config->use_image_default_file_config)) + { + $config->use_image_default_file_config = 'Y'; + } + if (!isset($config->use_video_default_file_config)) + { + $config->use_video_default_file_config = 'Y'; + } + Context::set('config', $config); + Context::set('is_ffmpeg', function_exists('exec') && !empty($config->ffmpeg_command) && Rhymix\Framework\Storage::isExecutable($config->ffmpeg_command) && !empty($config->ffprobe_command) && Rhymix\Framework\Storage::isExecutable($config->ffprobe_command)); + Context::set('is_magick', function_exists('exec') && !empty($config->magick_command) && Rhymix\Framework\Storage::isExecutable($config->magick_command)); + // Get a permission for group setting - $oMemberModel = getModel('member'); - $site_module_info = Context::get('site_module_info'); - $group_list = $oMemberModel->getGroups($site_module_info->site_srl); + $group_list = MemberModel::getGroups(); Context::set('group_list', $group_list); + // Set a template file - $oTemplate = &TemplateHandler::getInstance(); - $tpl = $oTemplate->compile($this->module_path.'tpl', 'file_module_config'); + $oTemplate = TemplateHandler::getInstance(); + $tpl = $oTemplate->compile($this->module_path . 'tpl', 'file_module_config'); $obj .= $tpl; } } diff --git a/modules/file/lang/en.php b/modules/file/lang/en.php index 16eb7b425..64190e13f 100644 --- a/modules/file/lang/en.php +++ b/modules/file/lang/en.php @@ -3,6 +3,8 @@ $lang->file = 'File'; $lang->file_management = 'File Management'; $lang->file_upload = 'File Upload'; $lang->file_upload_config = 'File Upload Settings'; +$lang->file_download_config = 'File Download Settings'; +$lang->file_other_config = 'Other Settings'; $lang->file_name = 'File Name'; $lang->file_size = 'File Size'; $lang->download_count = 'Number of Downloads'; @@ -10,43 +12,65 @@ $lang->status = 'Status'; $lang->is_valid = 'Valid'; $lang->is_stand_by = 'Stand by'; $lang->file_list = 'Attachment List'; +$lang->allow_indexing_format = 'Allowed formats to be indexed'; $lang->allow_outlink = 'Allow External Link to Download URL'; $lang->allow_outlink_site = 'Allowed Websites'; $lang->allow_outlink_format = 'Allowed Formats'; +$lang->allow_multimedia_direct_download = 'Allow Direct Link to Multimedia Attachments'; $lang->allowed_filesize = 'Maximum File Size'; +$lang->allowed_filesize_exceeded = 'The file is too large. The maximum allowed filesize is %s.'; $lang->allowed_attach_size = 'Maximum Attachments'; $lang->allowed_filetypes = 'Allowed extentsions'; +$lang->download_short_url = 'Use short URL'; $lang->inline_download_format = 'Open in current window'; $lang->inline_download_image = 'Image'; $lang->inline_download_audio = 'Audio'; $lang->inline_download_video = 'Video'; $lang->inline_download_text = 'Text (except HTML)'; $lang->inline_download_pdf = 'PDF'; +$lang->file_save_changelog = 'Save changelog'; +$lang->use_default_file_config = 'Use Default Settings'; +$lang->about_use_default_file_config = 'Follow the default settings from the File module.'; +$lang->about_download_short_url = 'Using short URLs can fix broken filenames in Android and some other platforms.
                Short URLs must be enabled, and rewrite rules must be updated to the latest version if you use nginx.'; $lang->about_inline_download_format = 'Selected types of files will be opened in the current window instead of a download dialog when a user clicks the download link.'; $lang->enable_download_group = 'Downloadable Groups'; -$lang->about_allow_outlink = 'You can block other websites from accessing your download URLs directly.
                This does not apply to images and other files that can be embedded directly in a document.'; +$lang->about_allow_indexing_format = 'These file formats will be allowed to be indexed by search engines like Google.
                This can increase server load and traffic, and you should be careful not to expose files containing confidential information.
                Please use a comma (,) to separate items: e.g. doc, zip, pdf'; +$lang->about_allow_outlink = 'Allow other websites to link directly to your download URLs.
                Rhymix does not control links to image files that can be embedded directly in a document.
                in order to block external links to such images, you may need to modify your web server configuration.'; $lang->about_allow_outlink_format = 'These file formats will always be allowed.
                Please use a comma (,) to separate items: e.g. doc, zip, pdf'; $lang->about_allow_outlink_site = 'These referers will always be allowed.
                Please enter one full address per line: e.g. https://www.rhymix.org/'; +$lang->about_allow_multimedia_direct_download = 'Use directly accessible links for audio and video attachments.
                This helps reduce server load, as the download of such large files will not go through PHP.
                However, unprivileged users may be able to download audio and video files if the link is exposed.'; $lang->about_allowed_filesize = 'You can limit the size of each attached file.
                Administrators are limited to this setting or the limit set in the file module, whichever is greater.'; $lang->about_allowed_attach_size = 'You can limit the total size of all attached files in one document.
                Administrators are limited to this setting or the limit set in the file module, whichever is greater.'; $lang->about_allowed_filesize_global = 'This is the global limit on the size of each attachment.'; $lang->about_allowed_attach_size_global = 'This is the global limit on the combined size of all attachments in one document.'; $lang->about_allowed_size_limits = 'The file size will be limited to the value set in php.ini (%sB) in IE9 and below and older Android browsers.'; -$lang->about_allowed_filetypes = 'To allow an extension, use "*.[extention]". To allow multiple extensions, use ";" between each extension. ex) *.* or *.jpg;*.gif; '; +$lang->about_allowed_filetypes = 'Rhymix no longer uses the old *.* syntax. Simply list the extensions you wish to allow.
                Please use a comma (,) to separate items: e.g. doc, zip, pdf'; +$lang->about_save_changelog = 'Keep a log of new and deleted files in the database.'; $lang->cmd_delete_checked_file = 'Delete Selected Item(s)'; $lang->cmd_move_to_document = 'Move to Document'; $lang->cmd_download = 'Download'; +$lang->msg_invalid_upload_info = 'Invalid upload target information. (%s)'; $lang->msg_not_permitted_download = 'You do not have a permission to download.'; $lang->msg_file_cart_is_null = 'Please select a file(s) to delete.'; $lang->msg_checked_file_is_deleted = '%d attachment(s) was(were) deleted.'; $lang->msg_exceeds_limit_size = 'This file exceeds the attachment limit.'; +$lang->msg_not_allowed_filetype = 'This file format is not allowed to be uploaded.'; +$lang->msg_exceeds_max_image_size = 'This image is too large. Images must be no larger than %dx%dpx.'; +$lang->msg_exceeds_max_image_width = 'This image is too large. The maximum permitted width is %dpx.'; +$lang->msg_exceeds_max_image_height = 'This image is too large. The maximum permitted height is %dpx.'; +$lang->msg_exceeds_max_video_size = 'This video is too large. Videos must be no larger than %dx%dpx.'; +$lang->msg_exceeds_max_video_width = 'This video is too large. The maximum permitted width is %dpx.'; +$lang->msg_exceeds_max_video_height = 'This video is too large. The maximum permitted height is %dpx.'; +$lang->msg_exceeds_max_video_duration = 'This video is too long. The maximum permitted duration is %d seconds.'; $lang->msg_file_not_found = 'Could not find requested file.'; +$lang->msg_file_key_expired = 'This download link is expired. Please initiate the download again.'; $lang->file_search_target_list['filename'] = 'File Name'; $lang->file_search_target_list['filesize_more'] = 'File Size(byte, more)'; $lang->file_search_target_list['filesize_mega_more'] = 'File Size(mbyte, more)'; $lang->file_search_target_list['filesize_less'] = 'File Size(byte, less)'; $lang->file_search_target_list['filesize_mega_less'] = 'File Size(Mb, less)'; $lang->file_search_target_list['download_count'] = 'Downloads(more)'; +$lang->file_search_target_list['download_count_less'] = 'Downloads(less)'; $lang->file_search_target_list['user_id'] = 'User UD'; $lang->file_search_target_list['user_name'] = 'User Name'; $lang->file_search_target_list['nick_name'] = 'Nickname'; @@ -61,3 +85,56 @@ $lang->msg_32bit_max_2047mb = 'On 32-bit servers, the file size limit cannot exc $lang->no_files = 'No Files'; $lang->file_manager = 'Manage selected files'; $lang->selected_file = 'Selected files'; +$lang->use_image_default_file_config = 'Use Default Settings Of Image File'; +$lang->about_use_image_default_file_config = 'Follow the default settings of image file from the File module.'; +$lang->use_video_default_file_config = 'Use Default Settings Of Video File'; +$lang->about_use_video_default_file_config = 'Follow the video settings of image file from the File module.'; +$lang->image_autoconv = 'Convert Type'; +$lang->about_image_autoconv = 'Automatically convert uploaded images. This may help you handle image formats that are not widely supported or waste disk space.'; +$lang->max_image_size = 'Limit Image Size'; +$lang->about_max_image_size = 'Limit the dimensions of uploaded images. Note that this is only indirectly related to file size.'; +$lang->max_image_size_action_nothing = 'If exceeded, do nothing'; +$lang->max_image_size_action_block = 'If exceeded, block upload'; +$lang->max_image_size_action_resize = 'If exceeded, resize automatically'; +$lang->max_image_size_action_cut = 'If exceeded, cut automatically'; +$lang->max_image_size_same_format_Y = 'Maintain file format'; +$lang->max_image_size_same_format_to_jpg = 'Convert to JPG'; +$lang->max_image_size_same_format_to_webp = 'Convert to WebP'; +$lang->max_image_size_admin = 'Also apply to administrator'; +$lang->image_quality_adjustment = 'Image Quality'; +$lang->about_image_quality_adjustment = 'adjust the quality of images that will is converted by other settings.
                If set to more than 75% (Standard), the file size may be larger than the original.'; +$lang->image_autorotate = 'Fix Image Rotation'; +$lang->about_image_autorotate = 'correct images that are rotated by mobile devices.'; +$lang->image_remove_exif_data = 'Remove EXIF'; +$lang->about_image_remove_exif_data = 'remove EXIF data including camera, GPS information, and more in image file for privacy.
                Even if this option is not used, EXIF ​​data may be removed when the image is converted by other settings.'; +$lang->image_always_reencode = 'Always Reencode'; +$lang->about_image_always_reencode = 'Reencode images to a constant quality even if they do not meet one of the conditions above. This may help save disk space and traffic.'; +$lang->image_autoconv_gif2mp4 = 'Convert GIF to MP4'; +$lang->about_image_autoconv_gif2mp4 = 'convert animated GIF images into MP4 videos to save storage and bandwidth.
                This requires ffmpeg settings below. Videos may not play properly in older browsers.'; +$lang->max_video_size = 'Limit Video Size'; +$lang->about_max_video_size = 'Limit the dimensions of uploaded videos. Note that this is only indirectly related to file size.'; +$lang->max_video_duration = 'Limit Video Duration'; +$lang->about_max_video_duration = 'Restrict the maximum length of videos. This is independent of the limit of filesize (e.g., MB, GB) or two-dimensional size for videos.'; +$lang->video_autoconv_any2mp4 = 'Convert to MP4'; +$lang->about_video_autoconv_any2mp4 = 'Convert all other types of videos to MP4 format that can be played on the web.
                Supported original formats vary by ffmpeg version and system environment, but usually include AVI and MOV.'; +$lang->video_always_reencode = 'Always Reencode'; +$lang->about_video_always_reencode = 'Reencode videos to a constant quality even if they do not meet one of the conditions above. This may help save disk space and traffic.'; +$lang->video_thumbnail = 'Video Thumbnail'; +$lang->about_video_thumbnail = 'extract a thumbnail image from uploaded video.'; +$lang->video_mp4_gif_time = 'Play Like GIF'; +$lang->about_video_mp4_gif_time = 'treat silent MP4 videos with duration less than the set time as GIF images, and play with auto and loop.'; +$lang->external_program_paths = 'Paths to External Programs'; +$lang->ffmpeg_path = 'Absolute Path to ffmpeg'; +$lang->ffprobe_path = 'Absolute Path to ffprobe'; +$lang->magick_path = 'Absolute Path to magick'; +$lang->about_ffmpeg_path = 'Rhymix uses ffmpeg to convert video files.'; +$lang->about_magick_path = 'Rhymix uses magick to convert newer image formats such as AVIF and HEIC.
                Note that the \'convert\' command from previous versions of ImageMagick doesn\'t support these formats.
                The latest version can be downloaded from their official site.'; +$lang->msg_cannot_use_exec = 'The exec() function is disabled on this server.'; +$lang->msg_cannot_use_ffmpeg = 'In order to use this feature, PHP must be able to execute \'ffmpeg\' and \'ffprobe\' commands.'; +$lang->msg_cannot_use_exif = 'In order to use this feature, PHP must be installed with the \'exif\' extension.'; +$lang->msg_need_magick = 'In order to handle AVIF and HEIC formats, PHP must be able to execute the \'magick\' command from ImageMagick 7.x or higher.'; +$lang->image_conversion = 'Image conversion'; +$lang->image_size = 'Image size'; +$lang->image_format = 'Image format'; +$lang->msg_image_converted = 'The image has been converted. (%s → %s)'; +$lang->msg_image_conversion_failed = 'The image conversion has failed.'; diff --git a/modules/file/lang/es.php b/modules/file/lang/es.php index 1b3286f9b..76696667c 100644 --- a/modules/file/lang/es.php +++ b/modules/file/lang/es.php @@ -17,7 +17,7 @@ $lang->about_allow_outlink = 'Enlaces externos a Rusia Ripper puede bloquear el $lang->about_allow_outlink_site = 'Archivos, independientemente de la configuración para permitir a los enlaces externos es la dirección del sitio. Entrada múltiples gubunhaeju un cambio en la línea, por favor. Ej.) https://www.rhymix.org/'; $lang->about_allowed_filesize = 'Puede definir el límite del tamaño del archivo adjunto.(exceptuando el administrador)'; $lang->about_allowed_attach_size = 'Puede definir el límite del tamaño total de los archivos adjuntos por documento.(exceptuando el administrador)'; -$lang->about_allowed_filetypes = 'Para permitir una extensión use "*.extensión". Para permitir más de una extensión use ";". ej) *.* o *.jpg;*.gif;etc.'; +$lang->about_allowed_filetypes = 'Rhymix ya no usa la antigua sintaxis *. *. Simplemente enumere las extensiones que desea permitir.
                Utilice una coma (,) para separar los elementos: doc, zip, pdf'; $lang->cmd_delete_checked_file = 'Eliminar el archivo seleccionado'; $lang->cmd_move_to_document = 'Mover hacia el doncumento'; $lang->cmd_download = 'Descargar'; @@ -29,6 +29,7 @@ $lang->file_search_target_list['filename'] = 'Nombre del archivo'; $lang->file_search_target_list['filesize_more'] = 'Tamaño del archivo(Byte, sobre)'; $lang->file_search_target_list['filesize_mega_more'] = 'Tamaño del archivo(Mb, o mb)'; $lang->file_search_target_list['download_count'] = 'Descargados(Sobre)'; +$lang->file_search_target_list['download_count_less'] = 'Descargados(less)'; $lang->file_search_target_list['user_id'] = 'ID'; $lang->file_search_target_list['user_name'] = 'Nombre'; $lang->file_search_target_list['nick_name'] = 'Apodo'; diff --git a/modules/file/lang/fr.php b/modules/file/lang/fr.php index c0629cd88..72d98cf3d 100644 --- a/modules/file/lang/fr.php +++ b/modules/file/lang/fr.php @@ -13,7 +13,7 @@ $lang->allowed_filetypes = 'Extensions consentis seulement peuvent etre attaches $lang->enable_download_group = 'Groupe permis de telecharger'; $lang->about_allowed_filesize = 'Vous pouvez designer la limite de mesure pour chaque fichier.(Exclure administrateurs)'; $lang->about_allowed_attach_size = 'Vous pouvez designer la limite de mesure pour chaque document.(Exclure administrateurs)'; -$lang->about_allowed_filetypes = 'Pour consentir une extension, utilisez "*.[extention]". Pour consentir plusieurs extensions, utilisez ";" entre chaque extension. ex) *.* ou *.jpg;*.gif;'; +$lang->about_allowed_filetypes = 'Rhymix n\'utilise plus l\'ancienne syntaxe *. *. Indiquez simplement les extensions que vous souhaitez autoriser.
                Utilisez une virgule (,) pour séparer les éléments: par exemple. doc, zip, pdf'; $lang->cmd_delete_checked_file = 'Supprimer item(s) slectionne(s)'; $lang->cmd_move_to_document = 'Bouger au Document'; $lang->cmd_download = 'Telecharger'; @@ -24,6 +24,7 @@ $lang->msg_exceeds_limit_size = 'La mesure de l\'(des) Annexe(s) est plus grande $lang->file_search_target_list['filename'] = 'Nom de Fichier'; $lang->file_search_target_list['filesize_more'] = 'Mesure de Fichier(octet, surplus)'; $lang->file_search_target_list['download_count'] = 'Telecharges(surplus)'; +$lang->file_search_target_list['download_count_less'] = 'Telecharges(less)'; $lang->file_search_target_list['regdate'] = 'Enrgistre'; $lang->file_search_target_list['ipaddress'] = 'Adresse IP'; $lang->msg_not_allowed_outlink = 'It is not allowed to download files not from this site.'; diff --git a/modules/file/lang/ja.php b/modules/file/lang/ja.php index 87598b65f..4eafa565b 100644 --- a/modules/file/lang/ja.php +++ b/modules/file/lang/ja.php @@ -22,7 +22,7 @@ $lang->about_allow_outlink_format = '外部からのファイルリンク設定 $lang->about_allow_outlink_site = '外部からのファイルリンク設定に関係なく、常に外部からのリンクを許可するURLです。
                複数登録時には、改行で記入してください。 ex) https://www.rhymix.org/'; $lang->about_allowed_filesize = '一つのファイルに対して、アップロード可能なファイルの最大サイズを指定します(管理者は除く)。'; $lang->about_allowed_attach_size = '一つの書き込みに対して、管理者以外のユーザーが添付可能な最大サイズを指定します。'; -$lang->about_allowed_filetypes = '"*.拡張子"で指定し、 ";"で区切って任意の拡張子を追加して指定できます。ex) *.* or *.jpg;*.gif; '; +$lang->about_allowed_filetypes = 'アップロードを許容する拡張者リストです。 前のバージョンの*.* 文法は使用しません。
                いろいろな入力時にdoc,zip,pdfのようにコンマ(,)に区分してください。'; $lang->cmd_delete_checked_file = '選択リスト削除'; $lang->cmd_move_to_document = '書き込みに移動する'; $lang->cmd_download = 'ダウンロード'; @@ -37,6 +37,7 @@ $lang->file_search_target_list['filesize_mega_more'] = 'ファイルサイズ(Mb $lang->file_search_target_list['filesize_less'] = 'ファイルサイズ(byte, 以下)'; $lang->file_search_target_list['filesize_mega_less'] = 'ファイルサイズ(Mb, 以下)'; $lang->file_search_target_list['download_count'] = 'ダウンロード数(以上)'; +$lang->file_search_target_list['download_count_less'] = 'ダウンロード数(以下)'; $lang->file_search_target_list['user_id'] = 'ユーザーID'; $lang->file_search_target_list['user_name'] = '名前'; $lang->file_search_target_list['nick_name'] = 'ニックネーム'; diff --git a/modules/file/lang/ko.php b/modules/file/lang/ko.php index e271e80e7..3014e884c 100644 --- a/modules/file/lang/ko.php +++ b/modules/file/lang/ko.php @@ -3,6 +3,8 @@ $lang->file = '파일'; $lang->file_management = '파일 관리'; $lang->file_upload = '파일 업로드'; $lang->file_upload_config = '파일 업로드 설정'; +$lang->file_download_config = '파일 다운로드 설정'; +$lang->file_other_config = '기타 설정'; $lang->file_name = '파일 이름'; $lang->file_size = '파일 크기'; $lang->download_count = '다운로드 받은 수'; @@ -10,44 +12,65 @@ $lang->status = '상태'; $lang->is_valid = '유효'; $lang->is_stand_by = '대기'; $lang->file_list = '첨부 파일 목록'; +$lang->allow_indexing_format = '외부 색인 허용 확장자'; $lang->allow_outlink = '다운로드 링크 외부 접근 허용'; -$lang->allow_outlink_site = '다운로드 링크 허용 사이트'; -$lang->allow_outlink_format = '다운로드 링크 허용 확장자'; -$lang->allowed_filesize = '파일 크기 제한'; +$lang->allow_outlink_site = '외부 접근 허용 사이트'; +$lang->allow_outlink_format = '외부 접근 허용 확장자'; +$lang->allowed_filesize = '파일 용량 제한'; +$lang->allowed_filesize_exceeded = '파일이 너무 큽니다. 용량 제한은 %s입니다.'; $lang->allowed_attach_size = '문서 첨부 제한'; $lang->allowed_filetypes = '허용 확장자'; +$lang->allow_multimedia_direct_download = '멀티미디어 파일 직접 접근 허용'; +$lang->download_short_url = '다운로드시 짧은주소 사용'; $lang->inline_download_format = '다운로드시 현재 창 사용'; $lang->inline_download_image = '이미지'; $lang->inline_download_audio = '오디오'; -$lang->inline_download_video = '비디오'; +$lang->inline_download_video = '동영상'; $lang->inline_download_text = '텍스트 (HTML 제외)'; $lang->inline_download_pdf = 'PDF'; +$lang->file_save_changelog = '변경 내역 기록'; +$lang->use_default_file_config = '기본 파일 설정 사용'; +$lang->about_use_default_file_config = '파일 모듈의 기본 설정을 따릅니다.'; +$lang->about_download_short_url = '안드로이드 등 일부 환경에서 첨부파일 다운로드시 파일명이 깨지는 문제를 해결할 수 있습니다.
                짧은주소 사용이 활성화되어 있어야 하며, nginx 사용시 rewrite 규칙을 최신 버전으로 업데이트하여야 합니다.'; $lang->about_inline_download_format = '선택한 종류의 파일은 다운로드 링크를 클릭하더라도 다운로드 창을 열지 않고 현재 창에 표시합니다.'; $lang->enable_download_group = '다운로드 가능 그룹'; -$lang->about_allow_outlink = '다른 사이트에서 파일 다운로드 링크에 직접 접근하는 것을 차단합니다.
                다운로드 링크를 사용하지 않고 본문에 바로 삽입할 수 있는 이미지 파일 등에는 적용되지 않습니다.'; +$lang->about_allow_indexing_format = '구글 등 검색엔진에 의한 색인을 허용하는 파일 확장자입니다.
                서버 부하와 트래픽이 증가할 수 있으며, 개인정보가 포함된 파일이 노출되지 않게 주의해야 합니다.
                여러 개 입력시 쉼표(,)을 이용해서 구분해 주세요. 예) doc, zip, pdf'; +$lang->about_allow_outlink = '다른 사이트에서 파일 다운로드 링크에 직접 접근하는 것을 허용합니다.
                본문에 바로 삽입할 수 있는 이미지 파일은 라이믹스에서 접근을 통제할 수 없으며, 이를 차단하려면 웹서버 설정이 필요합니다.'; $lang->about_allow_outlink_format = '파일 외부 링크 설정에 상관없이 허용하는 파일 확장자입니다.
                여러 개 입력시 쉼표(,)을 이용해서 구분해 주세요. 예) doc, zip, pdf'; $lang->about_allow_outlink_site = '파일 외부 링크 설정에 상관없이 허용하는 사이트 주소입니다.
                여러 개 입력시 줄을 바꿔서 구분해 주세요. 예) https://www.rhymix.org/'; +$lang->about_allow_multimedia_direct_download = '오디오, 동영상 등의 멀티미디어 파일 링크를 본문에 삽입할 때 직접 접근이 가능한 링크를 사용합니다.
                다운로드시 PHP를 거치지 않게 되므로 서버 부하가 줄어들지만, 권한이 없는 사람에게 링크가 노출되지 않도록 주의해야 합니다.
                "아니오"를 선택하면 본문에 삽입된 멀티미디어 파일이 자동 재생되지 않을 수 있습니다.'; $lang->about_allowed_filesize = '각 파일의 용량을 제한할 수 있습니다.
                관리자에게는 이 게시판의 제한과 파일 모듈의 제한 중 높은 쪽이 적용됩니다.'; $lang->about_allowed_attach_size = '하나의 문서에 첨부할 수 있는 최대 용량을 제한할 수 있습니다.
                관리자에게는 이 게시판의 제한과 파일 모듈의 제한 중 높은 쪽이 적용됩니다.'; -$lang->about_allowed_filesize_global = '관리자를 포함하여 사이트 전체에 적용되는 파일 크기 제한입니다.'; +$lang->about_allowed_filesize_global = '관리자를 포함하여 사이트 전체에 적용되는 파일 용량 제한입니다.'; $lang->about_allowed_attach_size_global = '관리자를 포함하여 사이트 전체에 적용되는 문서당 총 첨부 용량 제한입니다.'; $lang->about_allowed_size_limits = 'IE9 이하, 구버전 안드로이드 등에서는 php.ini에서 지정한 %sB로 제한됩니다.'; -$lang->about_allowed_filetypes = '"*.확장자"로 지정할 수 있고 ";" 으로 여러 개 지정이 가능합니다. 예) *.* or *.jpg;*.gif;'; +$lang->about_allowed_filetypes = '업로드를 허용할 확장자 목록입니다. 구 버전의 *.* 문법은 사용하지 않습니다.
                여러 개 입력시 쉼표(,)을 이용해서 구분해 주세요. 예) doc, zip, pdf'; +$lang->about_save_changelog = '파일 저장 및 삭제 내역을 DB에 기록합니다.'; $lang->cmd_delete_checked_file = '선택항목 삭제'; $lang->cmd_move_to_document = '문서로 이동'; $lang->cmd_download = '다운로드'; +$lang->msg_invalid_upload_info = '업로드 대상 정보가 일치하지 않습니다. (%s)'; $lang->msg_not_permitted_download = '다운로드할 수 있는 권한이 없습니다.'; $lang->msg_file_cart_is_null = '삭제할 파일을 선택해주세요.'; $lang->msg_checked_file_is_deleted = '%d개의 첨부 파일이 삭제되었습니다.'; $lang->msg_exceeds_limit_size = '허용된 용량을 초과하여 첨부가 되지 않았습니다.'; $lang->msg_not_allowed_filetype = '업로드할 수 없는 파일 형식입니다.'; +$lang->msg_exceeds_max_image_size = '이미지가 너무 큽니다. %dx%dpx 이하의 이미지만 허용됩니다.'; +$lang->msg_exceeds_max_image_width = '이미지가 너무 큽니다. 폭 %dpx 이하의 이미지만 허용됩니다.'; +$lang->msg_exceeds_max_image_height = '이미지가 너무 큽니다. 높이 %dpx 이하의 이미지만 허용됩니다.'; +$lang->msg_exceeds_max_video_size = '동영상이 너무 큽니다. %dx%dpx 이하의 동영상만 허용됩니다.'; +$lang->msg_exceeds_max_video_width = '동영상이 너무 큽니다. 폭 %dpx 이하의 동영상만 허용됩니다.'; +$lang->msg_exceeds_max_video_height = '동영상이 너무 큽니다. 높이 %dpx 이하의 동영상만 허용됩니다.'; +$lang->msg_exceeds_max_video_duration = '동영상이 너무 깁니다. %d초 이하의 동영상만 허용됩니다.'; $lang->msg_file_not_found = '요청한 파일을 찾을 수 없습니다.'; +$lang->msg_file_key_expired = '다운로드 링크의 유효기간이 지났습니다. 다시 다운로드하여 주시기 바랍니다.'; $lang->file_search_target_list['filename'] = '파일 이름'; $lang->file_search_target_list['filesize_more'] = '파일 크기(byte, 이상)'; $lang->file_search_target_list['filesize_mega_more'] = '파일 크기(MB, 이상)'; $lang->file_search_target_list['filesize_less'] = '파일 크기(byte, 이하)'; $lang->file_search_target_list['filesize_mega_less'] = '파일 크기(MB, 이하)'; $lang->file_search_target_list['download_count'] = '다운로드 횟수(이상)'; +$lang->file_search_target_list['download_count_less'] = '다운로드 횟수(이하)'; $lang->file_search_target_list['user_id'] = '아이디'; $lang->file_search_target_list['user_name'] = '이름'; $lang->file_search_target_list['nick_name'] = '닉네임'; @@ -62,3 +85,56 @@ $lang->msg_32bit_max_2047mb = '32비트 서버에서는 파일 크기 제한이 $lang->no_files = '파일이 없습니다.'; $lang->file_manager = '선택한 파일 관리'; $lang->selected_file = '선택한 파일'; +$lang->use_image_default_file_config = '이미지 파일 기본 설정 사용'; +$lang->about_use_image_default_file_config = '파일 모듈의 이미지 파일 기본 설정을 따릅니다.'; +$lang->use_video_default_file_config = '동영상 파일 기본 설정 사용'; +$lang->about_use_video_default_file_config = '파일 모듈의 동영상 파일 기본 설정을 따릅니다.'; +$lang->image_autoconv = '이미지 자동 변환'; +$lang->about_image_autoconv = '업로드된 이미지의 타입을 변환합니다. 다양한 환경에서 호환되지 않거나, 용량을 낭비하는 이미지를 처리할 수 있습니다.'; +$lang->max_image_size = '이미지 크기 제한'; +$lang->about_max_image_size = '업로드된 이미지의 크기를 제한하거나 조정합니다. 파일 용량과는 직접적인 관계가 없으니 참고하세요.'; +$lang->max_image_size_action_nothing = '초과시 아무 것도 하지 않음'; +$lang->max_image_size_action_block = '초과시 업로드 금지'; +$lang->max_image_size_action_resize = '초과시 자동 크기 조정'; +$lang->max_image_size_action_cut = '초과시 자르기'; +$lang->max_image_size_same_format_Y = '동일한 포맷 유지'; +$lang->max_image_size_same_format_to_jpg = 'JPG로 변환'; +$lang->max_image_size_same_format_to_webp = 'WebP로 변환'; +$lang->max_image_size_admin = '관리자에게도 적용'; +$lang->image_quality_adjustment = '이미지 화질'; +$lang->about_image_quality_adjustment = '다른 설정에 의해 이미지가 변환될 경우 화질을 조정합니다.
                75% (표준) 이상으로 설정시 오히려 원본보다 용량이 늘어날 수 있습니다.'; +$lang->image_autorotate = '이미지 회전 수정'; +$lang->about_image_autorotate = '모바일 기기 등에서 잘못 회전된 이미지를 바로잡습니다.'; +$lang->image_remove_exif_data = 'EXIF 제거'; +$lang->about_image_remove_exif_data = '프라이버시를 위해 이미지 파일에서 카메라, GPS 정보 등이 포함되어 있는 EXIF 데이터를 삭제합니다.
                이 옵션을 사용하지 않아도 다른 설정에 의해 이미지가 변환될 경우에도 EXIF 데이터가 삭제될 수 있습니다.'; +$lang->image_always_reencode = '무조건 재인코딩'; +$lang->about_image_always_reencode = '위에서 설정한 조건에 해당되지 않더라도 무조건 일정한 화질로 재인코딩하여 용량과 트래픽을 절약합니다.'; +$lang->image_autoconv_gif2mp4 = 'GIF → MP4 변환'; +$lang->about_image_autoconv_gif2mp4 = '움직이는 GIF 이미지를 MP4 동영상으로 변환하여 용량 및 트래픽을 절약합니다.
                아래에서 ffmpeg 설정을 해야 하며, 구형 브라우저에서는 동영상이 재생되지 않을 수도 있습니다.'; +$lang->max_video_size = '동영상 크기 제한'; +$lang->about_max_video_size = '업로드된 동영상의 크기를 제한하거나 조정합니다. 파일 용량과는 직접적인 관계가 없으니 참고하세요.'; +$lang->max_video_duration = '동영상 길이 제한'; +$lang->about_max_video_duration = '업로드된 동영상의 재생 시간을 제한하거나 조정합니다. 파일 용량과는 직접적인 관계가 없으니 참고하세요.'; +$lang->video_autoconv_any2mp4 = 'MP4로 변환'; +$lang->about_video_autoconv_any2mp4 = 'MP4 이외의 동영상 포맷은 모두 웹에서 재생할 수 있는 MP4 포맷으로 변환합니다.
                지원하는 원본 포맷은 ffmpeg 버전 및 서버 환경에 따라 다르지만 일반적으로 AVI, MOV 등이 해당됩니다.'; +$lang->video_always_reencode = '무조건 재인코딩'; +$lang->about_video_always_reencode = '위에서 설정한 조건에 해당되지 않더라도 무조건 일정한 화질로 재인코딩하여 용량과 트래픽을 절약합니다.'; +$lang->video_thumbnail = '동영상 섬네일 추출'; +$lang->about_video_thumbnail = '업로드된 동영상에서 섬네일 이미지를 추출합니다.'; +$lang->video_mp4_gif_time = 'GIF처럼 취급'; +$lang->about_video_mp4_gif_time = '설정된 시간 이하의 길이를 가진 짧고 소리 없는 MP4 동영상은 GIF처럼 자동 재생 및 반복 재생 상태로 삽입합니다.'; +$lang->external_program_paths = '외부 프로그램 경로'; +$lang->ffmpeg_path = 'ffmpeg 절대경로'; +$lang->ffprobe_path = 'ffprobe 절대경로'; +$lang->magick_path = 'magick 절대경로'; +$lang->about_ffmpeg_path = '동영상 변환에 사용합니다.'; +$lang->about_magick_path = 'AVIF, HEIC 등 일부 이미지 변환에 사용합니다.
                구 버전 ImageMagick의 convert 명령은 이러한 포맷을 지원하지 않습니다.
                새 버전은 공식 사이트에서 다운받을 수 있습니다.'; +$lang->msg_cannot_use_exec = '이 서버에서 exec() 함수를 사용할 수 없습니다.'; +$lang->msg_cannot_use_ffmpeg = '이 기능을 사용하려면 PHP에서 ffmpeg 및 ffprobe 명령을 실행할 수 있어야 합니다.'; +$lang->msg_cannot_use_exif = '이 기능을 사용하려면 PHP exif 확장모듈이 필요합니다.'; +$lang->msg_need_magick = 'AVIF, HEIC 변환을 위해서는 PHP에서 ImageMagick 7.x 이상의 magick 명령을 실행할 수 있어야 합니다.'; +$lang->image_conversion = '이미지 변환'; +$lang->image_size = '이미지 크기'; +$lang->image_format = '이미지 포맷'; +$lang->msg_image_converted = '이미지가 변환되었습니다. (%s → %s)'; +$lang->msg_image_conversion_failed = '이미지 변환에 실패했습니다.'; diff --git a/modules/file/lang/ru.php b/modules/file/lang/ru.php index ce04e487c..8b9839969 100644 --- a/modules/file/lang/ru.php +++ b/modules/file/lang/ru.php @@ -30,6 +30,7 @@ $lang->file_search_target_list['filesize_mega_more'] = 'Размер файла( $lang->file_search_target_list['filesize_less'] = 'Размер файла(Байт, ниже)'; $lang->file_search_target_list['filesize_mega_less'] = 'Размер файла(Мегабайт, ниже)'; $lang->file_search_target_list['download_count'] = 'Скачано(свыше)'; +$lang->file_search_target_list['download_count_less'] = 'Скачано(ниже)'; $lang->file_search_target_list['user_id'] = 'ID'; $lang->file_search_target_list['user_name'] = 'Имя'; $lang->file_search_target_list['nick_name'] = 'Ник'; diff --git a/modules/file/lang/tr.php b/modules/file/lang/tr.php index 85962a4a1..8b254201c 100644 --- a/modules/file/lang/tr.php +++ b/modules/file/lang/tr.php @@ -34,6 +34,7 @@ $lang->file_search_target_list['filesize_mega_more'] = 'Dosya Boyutu(mbyte, üst $lang->file_search_target_list['filesize_less'] = 'Dosya Boyutu(byte, düşük)'; $lang->file_search_target_list['filesize_mega_less'] = 'Dosya Boyutu(Mb, düşük)'; $lang->file_search_target_list['download_count'] = 'İndirmeler(daha fazla)'; +$lang->file_search_target_list['download_count_less'] = 'İndirmeler(düşük)'; $lang->file_search_target_list['user_id'] = 'Kullanıcı Kimliği'; $lang->file_search_target_list['user_name'] = 'Kullanıcı İsmi'; $lang->file_search_target_list['nick_name'] = 'Rumuz'; diff --git a/modules/file/lang/vi.php b/modules/file/lang/vi.php index 8b826bcec..5162a52b1 100644 --- a/modules/file/lang/vi.php +++ b/modules/file/lang/vi.php @@ -33,6 +33,7 @@ $lang->file_search_target_list['filesize_mega_more'] = 'Dung lượng(Tối thi $lang->file_search_target_list['filesize_less'] = 'Dung lượng(Tối đa Byte)'; $lang->file_search_target_list['filesize_mega_less'] = 'Dung lượng(Tối đa MB)'; $lang->file_search_target_list['download_count'] = 'Lượt Download'; +$lang->file_search_target_list['download_count_less'] = 'Lượt Download(Tối đa Lượt)'; $lang->file_search_target_list['user_id'] = 'ID đăng nhập'; $lang->file_search_target_list['user_name'] = 'Tên Sử dụng'; $lang->file_search_target_list['regdate'] = 'Ngày gửi'; diff --git a/modules/file/lang/zh-CN.php b/modules/file/lang/zh-CN.php index 0e15fb82e..b284834db 100644 --- a/modules/file/lang/zh-CN.php +++ b/modules/file/lang/zh-CN.php @@ -17,7 +17,7 @@ $lang->about_allow_outlink = '根据反向链接防止盗链。(*.wmv, *.mp3等 $lang->about_allow_outlink_site = '可以设置允许外链的站点。多个站点以换行来区分,即一行一个。 ex)https://www.rhymix.org/'; $lang->about_allowed_filesize = '最大单个上传文件大小(管理员不受此限制)。'; $lang->about_allowed_attach_size = '每个主题最大上传文件大小(管理员不受此限制)。'; -$lang->about_allowed_filetypes = '可以用"*.扩展名"来指定或用 ";"来 区分多个扩展名 例) *.* or *.jpg;*.gif;'; +$lang->about_allowed_filetypes = '允许上传的扩展者目录。 不使用前版本的*.*语法。
                输入多个时,请与doc,zip,pdf一起用逗号区分。'; $lang->cmd_delete_checked_file = '删除所选项目'; $lang->cmd_move_to_document = '查看源主题'; $lang->cmd_download = '下载'; @@ -32,6 +32,7 @@ $lang->file_search_target_list['filesize_mega_more'] = '文件大小(Mb, 以上) $lang->file_search_target_list['filesize_less'] = '文件大小(byte, 以下)'; $lang->file_search_target_list['filesize_mega_less'] = '文件大小(Mb, 以下)'; $lang->file_search_target_list['download_count'] = '下载次数(以上)'; +$lang->file_search_target_list['download_count_less'] = '下载次数(以下)'; $lang->file_search_target_list['user_id'] = '用户名'; $lang->file_search_target_list['user_name'] = '姓名'; $lang->file_search_target_list['nick_name'] = '昵称'; diff --git a/modules/file/lang/zh-TW.php b/modules/file/lang/zh-TW.php index 9235e3cad..e68d04dcd 100644 --- a/modules/file/lang/zh-TW.php +++ b/modules/file/lang/zh-TW.php @@ -19,7 +19,7 @@ $lang->about_allow_outlink_format = '設定允許外部連結的檔案格式。 $lang->about_allow_outlink_site = '可設置允許外部檔案連結的網站名單。當數量太多時,可換行輸入。 例) https://www.rhymix.org/'; $lang->about_allowed_filesize = '最大單一上傳檔案大小(管理員不受此限制)。'; $lang->about_allowed_attach_size = '每個主題最大上傳檔案大小(管理員不受此限制)。'; -$lang->about_allowed_filetypes = '可以用 "*.副檔名" 來指定或用分號 ";" 來區隔多個副檔名。 例) *.* or *.jpg; *.gif;'; +$lang->about_allowed_filetypes = '允許上傳的擴展者目錄。 不使用前版本的*.*語法。
                輸入多個時,請與doc,zip,pdf一起用逗號區分。'; $lang->cmd_delete_checked_file = '刪除所選項目'; $lang->cmd_move_to_document = '檢視原始主題'; $lang->cmd_download = '下載'; @@ -34,6 +34,7 @@ $lang->file_search_target_list['filesize_mega_more'] = '檔案大小(Mb, 以上) $lang->file_search_target_list['filesize_less'] = '檔案大小(byte, 以下)'; $lang->file_search_target_list['filesize_mega_less'] = '檔案大小(Mb, 以下)'; $lang->file_search_target_list['download_count'] = '下載次數(以上)'; +$lang->file_search_target_list['download_count_less'] = '下載次數(以下)'; $lang->file_search_target_list['user_id'] = '帳號'; $lang->file_search_target_list['user_name'] = '姓名'; $lang->file_search_target_list['nick_name'] = '暱稱'; diff --git a/modules/file/queries/getFileList.xml b/modules/file/queries/getFileList.xml index 023366f29..de5232747 100644 --- a/modules/file/queries/getFileList.xml +++ b/modules/file/queries/getFileList.xml @@ -9,27 +9,31 @@ + + + - + - + + - - - + + + - + diff --git a/modules/file/queries/getFileListByTargetStatus.xml b/modules/file/queries/getFileListByTargetStatus.xml index 7092da25f..fcc60f87a 100644 --- a/modules/file/queries/getFileListByTargetStatus.xml +++ b/modules/file/queries/getFileListByTargetStatus.xml @@ -1,4 +1,4 @@ - +
                @@ -35,19 +35,19 @@ - + - - - + + + - + diff --git a/modules/file/queries/getFiles.xml b/modules/file/queries/getFiles.xml index d0e326fb3..c40f265f7 100644 --- a/modules/file/queries/getFiles.xml +++ b/modules/file/queries/getFiles.xml @@ -4,6 +4,10 @@ + + + + diff --git a/modules/file/queries/getFilesCount.xml b/modules/file/queries/getFilesCount.xml index 81a257cd1..71a2d81f4 100644 --- a/modules/file/queries/getFilesCount.xml +++ b/modules/file/queries/getFilesCount.xml @@ -7,6 +7,10 @@ + + + + diff --git a/modules/file/queries/getFilesCountByGroupValid.xml b/modules/file/queries/getFilesCountByGroupValid.xml index 869d7a3fd..509a4d0c6 100644 --- a/modules/file/queries/getFilesCountByGroupValid.xml +++ b/modules/file/queries/getFilesCountByGroupValid.xml @@ -1,4 +1,4 @@ - +
                @@ -12,7 +12,7 @@ - + diff --git a/modules/file/queries/getModuleFilesProper.xml b/modules/file/queries/getModuleFilesProper.xml new file mode 100644 index 000000000..bbfb72108 --- /dev/null +++ b/modules/file/queries/getModuleFilesProper.xml @@ -0,0 +1,31 @@ + + +
                +
                + + + +
                + + + + +
                + + + + + + + + + + + + + + + + + + diff --git a/modules/file/queries/getModuleList.xml b/modules/file/queries/getModuleList.xml new file mode 100644 index 000000000..0fbffa3b7 --- /dev/null +++ b/modules/file/queries/getModuleList.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + +
                + + + + + + + diff --git a/modules/file/queries/insertFile.xml b/modules/file/queries/insertFile.xml index dfe38bbe7..de6bc9d21 100644 --- a/modules/file/queries/insertFile.xml +++ b/modules/file/queries/insertFile.xml @@ -1,22 +1,29 @@ - -
                - - - - - - - - - - - - - - - - - - + +
                + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/file/queries/insertFileChangelog.xml b/modules/file/queries/insertFileChangelog.xml new file mode 100644 index 000000000..90189e0d0 --- /dev/null +++ b/modules/file/queries/insertFileChangelog.xml @@ -0,0 +1,13 @@ + + +
                + + + + + + + + + + diff --git a/modules/file/queries/updateFile.xml b/modules/file/queries/updateFile.xml index b9c0c06d3..16831fe17 100644 --- a/modules/file/queries/updateFile.xml +++ b/modules/file/queries/updateFile.xml @@ -3,11 +3,19 @@
                + - + + + + + + + + diff --git a/modules/file/queries/updateFileDownloadCount.xml b/modules/file/queries/updateFileDownloadCount.xml index ba8c82a23..877fcc9db 100644 --- a/modules/file/queries/updateFileDownloadCount.xml +++ b/modules/file/queries/updateFileDownloadCount.xml @@ -1,4 +1,4 @@ - +
                diff --git a/modules/file/queries/updateFileModuleComment.xml b/modules/file/queries/updateFileModuleComment.xml index f053a1d4b..52c4e4b47 100644 --- a/modules/file/queries/updateFileModuleComment.xml +++ b/modules/file/queries/updateFileModuleComment.xml @@ -1,13 +1,13 @@ -
                -
                +
                +
                - + - - + + diff --git a/modules/file/queries/updateFileName.xml b/modules/file/queries/updateFileName.xml new file mode 100644 index 000000000..86cd08415 --- /dev/null +++ b/modules/file/queries/updateFileName.xml @@ -0,0 +1,11 @@ + + +
                + + + + + + + + diff --git a/modules/file/queries/updateFileTargetType.xml b/modules/file/queries/updateFileTargetType.xml index 334d20825..02683449a 100644 --- a/modules/file/queries/updateFileTargetType.xml +++ b/modules/file/queries/updateFileTargetType.xml @@ -6,6 +6,6 @@ - + - \ No newline at end of file + diff --git a/modules/file/queries/updateFileValid.xml b/modules/file/queries/updateFileValid.xml index 86bcddff6..52a807d4c 100644 --- a/modules/file/queries/updateFileValid.xml +++ b/modules/file/queries/updateFileValid.xml @@ -1,11 +1,14 @@ - -
                - - - - - - - + +
                + + + + + + + + + + diff --git a/modules/file/ruleset/fileModuleConfig.xml b/modules/file/ruleset/fileModuleConfig.xml index 963180a3e..067c5c072 100644 --- a/modules/file/ruleset/fileModuleConfig.xml +++ b/modules/file/ruleset/fileModuleConfig.xml @@ -1,11 +1,7 @@ - - - - diff --git a/modules/file/ruleset/imageResize.xml b/modules/file/ruleset/imageResize.xml deleted file mode 100644 index ad0ab675c..000000000 --- a/modules/file/ruleset/imageResize.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/modules/file/ruleset/insertConfig.xml b/modules/file/ruleset/insertConfig.xml index 963180a3e..067c5c072 100644 --- a/modules/file/ruleset/insertConfig.xml +++ b/modules/file/ruleset/insertConfig.xml @@ -1,11 +1,7 @@ - - - - diff --git a/modules/file/schemas/files.xml b/modules/file/schemas/files.xml index 75e0cf644..07c82b344 100644 --- a/modules/file/schemas/files.xml +++ b/modules/file/schemas/files.xml @@ -1,18 +1,24 @@
                - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + +
                diff --git a/modules/file/schemas/files_changelog.xml b/modules/file/schemas/files_changelog.xml new file mode 100644 index 000000000..e65b00350 --- /dev/null +++ b/modules/file/schemas/files_changelog.xml @@ -0,0 +1,9 @@ + + + + + + + + +
                diff --git a/modules/file/scripts/cleanEmptyDirs.php b/modules/file/scripts/cleanEmptyDirs.php new file mode 100644 index 000000000..ced824662 --- /dev/null +++ b/modules/file/scripts/cleanEmptyDirs.php @@ -0,0 +1,61 @@ + 'N', + 'list_count' => 50, + 'regdate_before' => date('YmdHis', time() - ($days * 86400)), + ]); + + if ($output->toBool()) + { + if ($output->data) + { + $oDB->begin(); + foreach ($output->data as $file_info) + { + $oFileController->deleteFile($file_info->file_srl); + } + $oDB->commit(); + + if ($output->page_navigation && $output->page_navigation->total_count == count($output->data)) + { + break; + } + } + else + { + break; + } + } + else + { + echo "Error while deleting garbage files older than $days days.\n"; + echo $output->getMessage() . "\n"; + $exit_status = 11; + break; + } +} +if ($exit_status == 0) +{ + echo "Successfully deleted all garbage files older than $days days.\n"; +} + +// Find and delete temporary chunks. +$dirname = RX_BASEDIR . 'files/attach/chunks'; +$threshold = time() - ($days * 86400); +$chunks = Rhymix\Framework\Storage::readDirectory($dirname); +if ($chunks) +{ + foreach ($chunks as $chunk) + { + if (@filemtime($chunk) < $threshold) + { + $result = Rhymix\Framework\Storage::delete($chunk); + if (!$result) + { + $exit_status = 12; + } + } + } +} +if ($exit_status == 0) +{ + echo "Successfully deleted temporary file chunks older than $days days.\n"; +} +else +{ + echo "Error while deleting temporary file chunks older than $days days.\n"; +} + +// Set the exit status if there were any errors. +if ($exit_status != 0) +{ + exit($exit_status); +} diff --git a/modules/file/scripts/cleanThumbnails.php b/modules/file/scripts/cleanThumbnails.php new file mode 100644 index 000000000..20cda9a32 --- /dev/null +++ b/modules/file/scripts/cleanThumbnails.php @@ -0,0 +1,44 @@ + - -
                -

                {$XE_VALIDATOR_MESSAGE}

                -
                - -
                - - - -
                - -
                - - -

                {$lang->about_allow_outlink}

                -
                -
                -
                - -
                - -

                {$lang->about_allow_outlink_format}

                -
                -
                -
                - -
                - -

                {$lang->about_allow_outlink_site}

                -
                -
                -
                - -
                - - - - - -

                {$lang->about_inline_download_format}

                -
                -
                -
                - -
                - MB -

                {$lang->about_allowed_filesize_global}
                {sprintf($lang->about_allowed_size_limits, ini_get('upload_max_filesize'))}

                -
                -
                -
                - -
                - MB -

                {$lang->about_allowed_attach_size_global}
                {sprintf($lang->about_allowed_size_limits, ini_get('upload_max_filesize'))}

                -
                -
                -
                - -
                - -

                {$lang->about_allowed_filetypes}

                -
                -
                -
                -
                - -
                -
                -
                diff --git a/modules/file/tpl/css/config.css b/modules/file/tpl/css/config.css new file mode 100644 index 000000000..cce191397 --- /dev/null +++ b/modules/file/tpl/css/config.css @@ -0,0 +1,24 @@ +.use_default_file_config { + margin-top: -10px; + border-bottom: 1px solid #ccc; + background: #f5f5f5; +} + +.image_autoconv_types { + margin-bottom: 5px; +} +.image_autoconv_types:last-of-type { + margin-bottom: 10px; +} +.image_autoconv_types .x_inline { + display: inline-block; + min-width: 40px; +} +.image_autoconv_types select { + min-width: 0 !important; + margin-left: 16px; +} + +.x_help-block .preset_size { + font-size: 12px; +} diff --git a/modules/file/tpl/download_config.html b/modules/file/tpl/download_config.html new file mode 100644 index 000000000..bc09e2c61 --- /dev/null +++ b/modules/file/tpl/download_config.html @@ -0,0 +1,72 @@ + + +
                +

                {$XE_VALIDATOR_MESSAGE}

                +
                + +
                + + + +
                + +
                + + +

                {$lang->about_allow_outlink}

                +
                +
                +
                + +
                + +

                {$lang->about_allow_outlink_format}

                +
                +
                +
                + +
                + +

                {$lang->about_allow_outlink_site}

                +
                +
                +
                + +
                + + +

                {$lang->about_allow_multimedia_direct_download}

                +
                +
                +
                + +
                + + +

                {$lang->about_download_short_url}

                +
                +
                +
                + +
                + + + + + +

                {$lang->about_inline_download_format}

                +
                +
                +
                + +
                + +

                {$lang->about_allow_indexing_format}

                +
                +
                +
                +
                + +
                +
                +
                diff --git a/modules/file/tpl/file_edit.html b/modules/file/tpl/file_edit.html new file mode 100644 index 000000000..cc135a155 --- /dev/null +++ b/modules/file/tpl/file_edit.html @@ -0,0 +1,83 @@ + + +
                +

                {$XE_VALIDATOR_MESSAGE}

                +
                + +
                +

                {$XE_VALIDATOR_MESSAGE}

                +
                + +
                +

                {$lang->file_name}

                +
                + + + + +
                + +
                + +
                +
                +
                +
                + +
                +
                +
                +
                + + + + {@ list($file->width, $file->height) = getimagesize($file->uploaded_filename)} + + {@ $extension = strtolower($matches[1]); if ($extension === 'jpeg') $extension = 'jpg'; } +
                +

                {$lang->image_conversion}

                +
                + + + + + + +
                + +
                + x + +

                + + + + + +

                +
                +
                +
                + +
                + +
                +
                +
                + +
                + % +
                +
                +
                +
                + +
                +
                +
                +
                + diff --git a/modules/file/tpl/file_list.html b/modules/file/tpl/file_list.html index 8179355a0..ba4ec7439 100644 --- a/modules/file/tpl/file_list.html +++ b/modules/file/tpl/file_list.html @@ -17,18 +17,43 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}'; {$lang->is_valid}({number_format($total_count)}) | {$lang->is_stand_by}({number_format($total_count)}) - + {$lang->delete} {$lang->file} - {$lang->file_size} - {$lang->cmd_download} + + + {$lang->file_size} + + + + + + + {$lang->image_size} + + + + {$lang->cmd_download} + + + + + {$lang->author} - {$lang->date} + + + {$lang->date} + + + + + {$lang->ipaddress} {$lang->status} + {$lang->cmd_edit} @@ -36,16 +61,16 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}'; - + {@ $document_srl = $val->target_document_srl} {@ $move_uri = getUrl('', 'document_srl', $document_srl).'#comment_'.$val->upload_target_srl} - + {@ $document_srl = $val->upload_target_srl} - {@ $move_uri = getUrl('', 'document_srl', $document_srl)} + {@ $move_uri = getUrl('', 'mid', $module_list[$val->module_srl]->mid, 'document_srl', $document_srl)} {@ $cur_upload_target_srl = $val->upload_target_srl} - + {$lang->is_valid} @@ -55,12 +80,22 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}'; [{$lang->comment}] [{$lang->module}] + [{$lang->member_message}] - [{$lang->cmd_temp_save}] - [{$lang->cmd_trash}] - {$module_list[$val->module_srl]->browser_title} - - - {$document_list[$document_srl]->getTitle()}{$document_list[$document_srl]->getTitle()} + [{$lang->cmd_temp_save}] + [{$lang->cmd_trash}] + + + + {$module_list[$val->module_srl]->browser_title} - + + {@ $module_url = ModuleModel::getDomainByModuleSrl($val->module_srl); $move_uri = $module_url . $move_uri; } + {$module_list[$val->module_srl]->browser_title} - + + + + + {$document_list[$document_srl]->getTitle()}{$document_list[$document_srl]->getTitle()} @@ -68,11 +103,21 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}'; - - + + + + - {htmlspecialchars($val->source_filename, ENT_COMPAT | ENT_HTML401, 'UTF-8', false)} + {escape($val->source_filename, false)} {FileHandler::filesize($val->file_size)} + + + {$val->width}x{$val->height} + + +
                ({$val->duration}{$lang->unit_sec}) + + {$val->download_count} @@ -86,6 +131,9 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}'; {zdate($val->regdate,"Y-m-d H:i")} {$val->ipaddress} {$lang->is_valid}{$lang->is_stand_by} + + {$lang->cmd_edit} + @@ -143,10 +191,15 @@ xe.lang.msg_empty_search_keyword = '{$lang->msg_empty_search_keyword}';
              diff --git a/modules/file/tpl/iframe.html b/modules/file/tpl/iframe.html index a2d41bd9f..6fecde816 100644 --- a/modules/file/tpl/iframe.html +++ b/modules/file/tpl/iframe.html @@ -10,6 +10,13 @@ uploaded_fileinfo.source_filename = '{$uploaded_fileinfo->get('source_filename')}'; uploaded_fileinfo.upload_target_srl = '{$uploaded_fileinfo->get('upload_target_srl')}'; uploaded_fileinfo.uploaded_filename = '{$uploaded_fileinfo->get('uploaded_filename')}'; + + uploaded_fileinfo.download_url = '{FileModel::getDirectFileUrl($uploaded_fileinfo->get("uploaded_filename"))}'; + + uploaded_fileinfo.download_url = '{FileModel::getDownloadUrl($uploaded_fileinfo->get("file_srl"), $uploaded_fileinfo->get("sid"), 0, $uploaded_fileinfo->get("source_filename"))}'; + + uploaded_fileinfo.thumbnail_filename = '{$uploaded_fileinfo->get("thumbnail_filename")}'; + uploaded_fileinfo.original_type = '{$uploaded_fileinfo->get("original_type")}'; uploaded_fileinfo.error = -1; uploaded_fileinfo.message = '{$uploaded_fileinfo->message}'; diff --git a/modules/file/tpl/js/config.js b/modules/file/tpl/js/config.js new file mode 100644 index 000000000..7b7a5b8ac --- /dev/null +++ b/modules/file/tpl/js/config.js @@ -0,0 +1,25 @@ +(function($) { + $(function() { + $('#use_default_file_config').on('change', function() { + if ($(this).is(':checked')) { + $('.use_custom_file_config').hide(); + } else { + $('.use_custom_file_config').show(); + } + }); + $('#use_image_default_file_config').on('change', function() { + if ($(this).is(':checked')) { + $('.use_custom_image_file_config').hide(); + } else { + $('.use_custom_image_file_config').show(); + } + }); + $('#use_video_default_file_config').on('change', function() { + if ($(this).is(':checked')) { + $('.use_custom_video_file_config').hide(); + } else { + $('.use_custom_video_file_config').show(); + } + }); + }); +})(jQuery); diff --git a/modules/file/tpl/js/file_admin.js b/modules/file/tpl/js/file_admin.js index 4fe392b97..f1a9e19d2 100644 --- a/modules/file/tpl/js/file_admin.js +++ b/modules/file/tpl/js/file_admin.js @@ -6,14 +6,13 @@ function getFileList() { var fileListTable = jQuery('#fileListTable'); var cartList = []; fileListTable.find(':checkbox[name=cart]').each(function(){ - if(this.checked) cartList.push(this.value); + if(this.checked) cartList.push(this.value); }); var params = new Array(); - var response_tags = ['error','message', 'file_list']; params["file_srls"] = cartList.join(","); - exec_xml('file','procFileGetList',params, completeGetFileList, response_tags); + exec_json('file.procFileGetList', params, completeGetFileList); } function completeGetFileList(ret_obj, response_tags) @@ -27,7 +26,7 @@ function completeGetFileList(ret_obj, response_tags) } else { - var file_list = ret_obj['file_list']['item']; + var file_list = ret_obj['file_list']['item'] ? ret_obj['file_list']['item'] : ret_obj['file_list']; if(!jQuery.isArray(file_list)) file_list = [file_list]; for(var x in file_list) { @@ -39,7 +38,7 @@ function completeGetFileList(ret_obj, response_tags) '' + ''; } - jQuery('#selectedFileCount').html(file_list.length); + jQuery('#selectedFileCount').html(file_list.length + ' (' + ret_obj['file_size_total_human'] + ')'); } jQuery('#fileManageListTable>tbody').html(htmlListBuffer); } @@ -59,3 +58,24 @@ function checkSearch(form) } */ } + +(function() { + $(function() { + $('.preset_size').on('click', function() { + const preset_size = parseInt($(this).text(), 10); + const width = parseInt($('input[name=original_width]').val(), 10); + const height = parseInt($('input[name=original_height]').val(), 10); + let new_width = 0; + let new_height = 0; + if (width > height) { + new_width = preset_size; + new_height = Math.round(preset_size * (height / width)); + } else { + new_width = Math.round(preset_size * (width / height)); + new_height = preset_size; + } + $('input[name=new_width]').val(new_width); + $('input[name=new_height]').val(new_height); + }); + }); +})(jQuery); diff --git a/modules/file/tpl/other_config.html b/modules/file/tpl/other_config.html new file mode 100644 index 000000000..505d517c9 --- /dev/null +++ b/modules/file/tpl/other_config.html @@ -0,0 +1,24 @@ + + +
              +

              {$XE_VALIDATOR_MESSAGE}

              +
              + + + + + +
              + +
              + + +

              {$lang->about_save_changelog}

              +
              +
              +
              +
              + +
              +
              + diff --git a/modules/file/tpl/upload_config.html b/modules/file/tpl/upload_config.html new file mode 100644 index 000000000..d87571d0f --- /dev/null +++ b/modules/file/tpl/upload_config.html @@ -0,0 +1,294 @@ + + +
              +

              {$XE_VALIDATOR_MESSAGE}

              +
              + +
              + + + + +
              + +
              + MB +

              {$lang->about_allowed_filesize_global}
              {sprintf($lang->about_allowed_size_limits, ini_get('upload_max_filesize'))}

              +
              +
              +
              + +
              + MB +

              {$lang->about_allowed_attach_size_global}
              {sprintf($lang->about_allowed_size_limits, ini_get('upload_max_filesize'))}

              +
              +
              +
              + +
              + +

              {$lang->about_allowed_filetypes}

              +
              +
              +
              +

              {$lang->image}

              +
              + +
              + {@ $source_types = ['bmp', 'jpg', 'png', 'webp', 'avif', 'heic']} + +
              + → + +
              + +

              + {$lang->about_image_autoconv}
              + {$lang->msg_need_magick} +

              +
              +
              +
              + +
              + × + px   + +

              + {$lang->about_max_image_size} +

              +

              + + + + +

              +
              +
              +
              + +
              + +

              {$lang->about_image_quality_adjustment}

              +
              +
              +
              + +
              + + +

              {$lang->about_image_autorotate}

              +

              {$lang->msg_cannot_use_exif}

              +
              +
              +
              + +
              + + +

              {$lang->about_image_remove_exif_data}

              +

              {$lang->msg_cannot_use_exif}

              +
              +
              +
              + +
              + + +

              {$lang->about_image_always_reencode}

              +
              +
              +
              + +
              + + +

              {$lang->about_image_autoconv_gif2mp4}

              +

              {$lang->msg_cannot_use_ffmpeg}

              +
              +
              +
              +
              +

              {$lang->video}

              +
              + +
              + × + px   + +

              + {$lang->about_max_video_size} +
              {$lang->msg_cannot_use_ffmpeg}
              +

              +

              + +

              +
              +
              +
              + +
              + {$lang->unit_sec}   + +

              + {$lang->about_max_video_duration} +
              {$lang->msg_cannot_use_ffmpeg}
              +

              +

              + +

              +
              +
              +
              + +
              + + +

              {$lang->about_video_autoconv_any2mp4}

              +

              {$lang->msg_cannot_use_ffmpeg}

              +
              +
              +
              + +
              + + +

              {$lang->about_video_always_reencode}

              +

              {$lang->msg_cannot_use_ffmpeg}

              +
              +
              +
              + +
              + + +

              {$lang->about_video_thumbnail}

              +

              {$lang->msg_cannot_use_ffmpeg}

              +
              +
              +
              + +
              + {$lang->unit_sec} +

              {$lang->about_video_mp4_gif_time}

              +

              {$lang->msg_cannot_use_ffmpeg}

              +
              +
              +
              +
              +

              {lang('file.external_program_paths')}

              +
              + +
              + +

              {$lang->about_ffmpeg_path}

              +

              {$lang->msg_cannot_use_exec}

              +
              +
              +
              + +
              + +

              {$lang->about_ffmpeg_path}

              +

              {$lang->msg_cannot_use_exec}

              +
              +
              +
              + +
              + +

              {$lang->about_magick_path}

              +

              {$lang->msg_cannot_use_exec}

              +
              +
              +
              + +
              +
              + +
              +
              +
              diff --git a/modules/importer/conf/info.xml b/modules/importer/conf/info.xml index e56e68dfa..773923701 100644 --- a/modules/importer/conf/info.xml +++ b/modules/importer/conf/info.xml @@ -18,8 +18,8 @@ Запись информации пользователей или форума, используя XML-файл. 利用 XML 檔案匯入會員或討論板資料。 Bu modül, XML dosyasından üye ve makale verilerinin içeri aktarımı içindir. - 1.7 - 2013-11-27 + RX_VERSION + RX_CORE migration diff --git a/modules/importer/importer.admin.controller.php b/modules/importer/importer.admin.controller.php index 112e50406..49cc0a020 100644 --- a/modules/importer/importer.admin.controller.php +++ b/modules/importer/importer.admin.controller.php @@ -18,11 +18,6 @@ class importerAdminController extends importer * @var int */ var $unit_count = 300; - /** - * Xml parser - * @var XmlParser - */ - var $oXmlParser = null; /** * Initialization @@ -303,7 +298,7 @@ class importerAdminController extends importer $columnList = array('module_srl', 'module'); $target_module_info = $oModuleModel->getModuleInfoByModuleSrl($target_module, $columnList); - $ttimporter = FileHandler::exists(_XE_PATH_ . 'modules/importer/ttimport.class.php'); + $ttimporter = FileHandler::exists(RX_BASEDIR . 'modules/importer/ttimport.class.php'); if($ttimporter) require_once($ttimporter); $oTT = new ttimport(); @@ -346,8 +341,6 @@ class importerAdminController extends importer function importMember($key, $cur, $index_file) { if(!$cur) $cur = 0; - // Create the xmlParser object - $oXmlParser = new XmlParser(); // Create objects for importing member information $this->oMemberController = getController('member'); $this->oMemberModel = getModel('member'); @@ -368,7 +361,7 @@ class importerAdminController extends importer // Find a given location $target_file = trim(fgets($f, 1024)); // Load and parse the file - $xmlObj = $oXmlParser->loadXmlFile($target_file); + $xmlObj = Rhymix\Framework\Parsers\XEXMLParser::loadXMLFile($target_file); FileHandler::removeFile($target_file); if(!$xmlObj) continue; // List Objects @@ -391,7 +384,7 @@ class importerAdminController extends importer $obj->signature = base64_decode($xmlObj->member->signature->body); $obj->regdate = base64_decode($xmlObj->member->regdate->body); $obj->last_login = base64_decode($xmlObj->member->last_login->body); - + $obj->extra_vars = new stdClass(); if($xmlObj->member->extra_vars) { foreach($xmlObj->member->extra_vars as $key => $val) @@ -416,7 +409,6 @@ class importerAdminController extends importer { $obj->email_address = $obj->user_id . '@example.com'; } - list($obj->email_id, $obj->email_host) = explode('@', $obj->email); // Set the mailing option if($obj->allow_mailing!='Y') $obj->allow_mailing = 'N'; // Set the message option @@ -426,10 +418,8 @@ class importerAdminController extends importer if(!$obj->last_login) $obj->last_login = $obj->regdate; // Set the list order $obj->list_order = -1 * $obj->member_srl; - // List extra vars - $extra_vars = $obj->extra_vars; - unset($obj->extra_vars); - $obj->extra_vars = serialize($extra_vars); + // Serialize extra vars + $obj->extra_vars = serialize($obj->extra_vars); // Check if the same user ID exists $args = new stdClass; $args->user_id = $obj->user_id; @@ -455,9 +445,18 @@ class importerAdminController extends importer $obj->email_address = $obj->user_id . '@example.com'; } + list($obj->email_id, $obj->email_host) = explode('@', $obj->email_address); + if (!$obj->email_id) + { + $obj->email_id = ''; + } + if (!$obj->email_host) + { + $obj->email_host = ''; + } + // Add a member $output = executeQuery('member.insertMember', $obj); - if($output->toBool() && !($obj->password)) { // Send a mail telling the user to reset his password. @@ -533,8 +532,6 @@ class importerAdminController extends importer function importMessage($key, $cur, $index_file) { if(!$cur) $cur = 0; - // Create the xmlParser object - $oXmlParser = new XmlParser(); // Open an index file $f = fopen($index_file,"r"); // Pass if already read @@ -546,7 +543,7 @@ class importerAdminController extends importer // Find a location $target_file = trim(fgets($f, 1024)); // Load and parse the file - $xmlObj = $oXmlParser->loadXmlFile($target_file); + $xmlObj = Rhymix\Framework\Parsers\XEXMLParser::loadXMLFile($target_file); FileHandler::removeFile($target_file); if(!$xmlObj) continue; // List objects @@ -632,8 +629,6 @@ class importerAdminController extends importer */ function importModule($key, $cur, $index_file, $module_srl) { - // Pre-create the objects needed - $this->oXmlParser = new XmlParser(); // Get category information of the target module $oDocumentController = getController('document'); $oDocumentModel = getModel('document'); @@ -645,9 +640,7 @@ class importerAdminController extends importer if(file_exists($category_file)) { $buff = FileHandler::readFile($category_file); - - // Create the xmlParser object - $xmlDoc = $this->oXmlParser->loadXmlFile($category_file); + $xmlDoc = Rhymix\Framework\Parsers\XEXMLParser::loadXMLFile($category_file); $categories = $xmlDoc->items->category; if($categories) @@ -662,7 +655,7 @@ class importerAdminController extends importer $sequence = $v->attrs->sequence; $parent = $v->attrs->parent; - $obj = null; + $obj = new stdClass; $obj->title = $category; $obj->module_srl = $module_srl; if($parent) $obj->parent_srl = $match_sequence[$parent]; @@ -751,7 +744,7 @@ class importerAdminController extends importer if($started) $buff[] = $str; } - $xmlDoc = $this->oXmlParser->parse(implode('', $buff)); + $xmlDoc = Rhymix\Framework\Parsers\XEXMLParser::loadXMLString(implode('', $buff)); $category = base64_decode($xmlDoc->post->category->body); if($category_titles[$category]) $obj->category_srl = $category_titles[$category]; @@ -762,10 +755,10 @@ class importerAdminController extends importer $obj->status = base64_decode($xmlDoc->post->is_secret->body)=='Y'?$oDocumentModel->getConfigStatus('secret'):$oDocumentModel->getConfigStatus('public'); $obj->title = base64_decode($xmlDoc->post->title->body); $obj->content = base64_decode($xmlDoc->post->content->body); - $obj->readed_count = base64_decode($xmlDoc->post->readed_count->body); - $obj->voted_count = base64_decode($xmlDoc->post->voted_count->body); - $obj->blamed_count = base64_decode($xmlDoc->post->blamed_count->body); - $obj->password = base64_decode($xmlDoc->post->password->body); + $obj->readed_count = intval(base64_decode($xmlDoc->post->readed_count->body ?? '')); + $obj->voted_count = intval(base64_decode($xmlDoc->post->voted_count->body ?? '')); + $obj->blamed_count = intval(base64_decode($xmlDoc->post->blamed_count->body ?? '')); + $obj->password = base64_decode($xmlDoc->post->password->body) ?: null; $obj->user_name = base64_decode($xmlDoc->post->user_name->body); $obj->nick_name = base64_decode($xmlDoc->post->nick_name->body); if(!$obj->user_name) $obj->user_name = $obj->nick_name; @@ -895,7 +888,7 @@ class importerAdminController extends importer // If , insert to the DB if(trim($str) == '') { - $xmlDoc = $this->oXmlParser->parse($buff); + $xmlDoc = Rhymix\Framework\Parsers\XEXMLParser::loadXMLString($buff); $obj = new stdClass; $obj->trackback_srl = getNextSequence(); @@ -957,7 +950,7 @@ class importerAdminController extends importer // If is, insert to the DB if(trim($str) == '') { - $xmlDoc = $this->oXmlParser->parse($buff); + $xmlDoc = Rhymix\Framework\Parsers\XEXMLParser::loadXMLString($buff); $sequence = base64_decode($xmlDoc->comment->sequence->body); $sequences[$sequence] = $obj->comment_srl; @@ -972,9 +965,9 @@ class importerAdminController extends importer $obj->is_secret = base64_decode($xmlDoc->comment->is_secret->body)=='Y'?'Y':'N'; $obj->notify_message = base64_decode($xmlDoc->comment->notify_message->body)=='Y'?'Y':'N'; $obj->content = base64_decode($xmlDoc->comment->content->body); - $obj->voted_count = base64_decode($xmlDoc->comment->voted_count->body); - $obj->blamed_count = base64_decode($xmlDoc->comment->blamed_count->body); - $obj->password = base64_decode($xmlDoc->comment->password->body); + $obj->voted_count = intval(base64_decode($xmlDoc->comment->voted_count->body ?? '')); + $obj->blamed_count = intval(base64_decode($xmlDoc->comment->blamed_count->body ?? '')); + $obj->password = base64_decode($xmlDoc->comment->password->body) ?: null; $obj->user_name =base64_decode($xmlDoc->comment->user_name->body); $obj->nick_name = base64_decode($xmlDoc->comment->nick_name->body); if(!$obj->user_name) $obj->user_name = $obj->nick_name; @@ -1025,7 +1018,7 @@ class importerAdminController extends importer } else { - // Get parent comment infomation + // Get parent comment information $parent_args = new stdClass(); $parent_args->comment_srl = $obj->parent_srl; $parent_output = executeQuery('comment.getCommentListItem', $parent_args); @@ -1097,10 +1090,11 @@ class importerAdminController extends importer } if($started) $buff .= $str; + // If it ends with , handle attachements if(trim($str) == '') { - $xmlDoc = $this->oXmlParser->parse($buff.$str); + $xmlDoc = Rhymix\Framework\Parsers\XEXMLParser::loadXMLString($buff); $file_obj->source_filename = base64_decode($xmlDoc->attach->filename->body); $file_obj->download_count = base64_decode($xmlDoc->attach->download_count->body); @@ -1169,14 +1163,13 @@ class importerAdminController extends importer $file_obj->comment = NULL; $file_obj->member_srl = 0; $file_obj->sid = $random->createSecureSalt(32, 'hex'); + $file_obj->mime_type = Rhymix\Framework\MIME::getTypeByFilename($filename); $file_obj->isvalid = 'Y'; $output = executeQuery('file.insertFile', $file_obj); if($output->toBool()) { $uploaded_count++; - $tmp_obj = null; - $tmp_obj->source_filename = $file_obj->source_filename; if($file_obj->direct_download == 'Y') $files[$file_obj->source_filename] = $file_obj->uploaded_filename; else $files[$file_obj->source_filename] = getUrl('','module','file','act','procFileDownload','file_srl',$file_obj->file_srl,'sid',$file_obj->sid); } @@ -1245,14 +1238,13 @@ class importerAdminController extends importer if(!$buff) return array(); $buff = ''.$buff; - $oXmlParser = new XmlParser(); - $xmlDoc = $this->oXmlParser->parse($buff); - if(!count($xmlDoc->extra_vars->key)) return array(); + $xmlDoc = Rhymix\Framework\Parsers\XEXMLParser::loadXMLString($buff); + if(empty($xmlDoc->extra_vars->key)) return array(); $index = 1; foreach($xmlDoc->extra_vars->key as $k => $v) { - unset($vobj); + $vobj = new stdClass(); if($v->var_idx) { $vobj->var_idx = base64_decode($v->var_idx->body); diff --git a/modules/importer/lang/es.php b/modules/importer/lang/es.php index a7a65d535..dddc31ca2 100644 --- a/modules/importer/lang/es.php +++ b/modules/importer/lang/es.php @@ -33,6 +33,6 @@ $lang->about_ttxml_user_id = 'Por favor, de entrada ID de usuario establecer com $lang->about_type_module = 'Seleccione esta opción si estas transfeririendo información del documento de los tableros'; $lang->about_type_syncmember = 'Seleccione esta opción cuando tenga que sincronizar la información del usuario luego de haber transferido la información del usuario y del artículo.'; $lang->about_importer = 'Es posible trasferir los datos de Zeroboard4, zb5beta o de otros programas a Rhymix. -Para la transferencia debe utilizar XML Exporter para transformar los datos en archivo XML, y luego subir ese archivo.'; +Para la transferencia debe utilizar XML Exporter para transformar los datos en archivo XML, y luego subir ese archivo.'; $lang->about_target_path = 'Para descargar los archivos adjuntos de ZeroBoard4, ingresa la ubicación de ZeroBoard4 instalado. Si esta en el mismo servidor escriba la ubicación de ZeroBoard4 como por ejemplo: /home/ID/public_html/bbs o si esta en otro servidor escriba la ubicación de ZeroBoard4 instalado como por ejemplo: http://dominio/bbs'; diff --git a/modules/importer/lang/ko.php b/modules/importer/lang/ko.php index 5b1b85fec..d15b13bbe 100644 --- a/modules/importer/lang/ko.php +++ b/modules/importer/lang/ko.php @@ -48,7 +48,7 @@ $lang->about_type_ttxml = '데이터 이전 대상이 TTXML(textcube계열)일 $lang->about_ttxml_user_id = '글쓴이로 설정할 사용자 아이디를 입력해주세요. (가입된 아이디만 가능)'; $lang->about_type_module = '데이터 이전 대상이 게시판 등의 게시물 정보일 경우 선택해주세요.'; $lang->about_type_syncmember = '회원정보와 게시물정보 등을 이전 후, 회원정보를 동기화해야 할 때 선택해주세요.'; -$lang->about_importer = '다른 프로그램의 데이터를 XML 형식으로 변환 후 업로드하면 Rhymix로 이전할 수 있습니다. XML Exporter를 이용하면 XML파일로 변환할 수 있습니다.'; +$lang->about_importer = '다른 프로그램의 데이터를 XML 형식으로 변환 후 업로드하면 Rhymix로 이전할 수 있습니다. XML Exporter를 이용하면 XML파일로 변환할 수 있습니다.'; $lang->about_target_path = '첨부 파일을 받기 위해 제로보드4가 설치된 위치를 입력해주세요. 같은 서버에 있을 경우 /home/아이디/public_html/bbs 등과 같이 제로보드4의 위치를 입력하고 다른 서버일 경우 http:도메인/bbs 처럼 제로보드4가 설치된 곳의 url을 입력해주세요.'; diff --git a/modules/importer/ttimport.class.php b/modules/importer/ttimport.class.php index 180b2939a..1e6855ae3 100644 --- a/modules/importer/ttimport.class.php +++ b/modules/importer/ttimport.class.php @@ -13,12 +13,6 @@ */ class ttimport { - /** - * Xml Parse - * @var XmlParser - */ - var $oXmlParser = null; - /** * Import data in module.xml format * @param int $key @@ -33,8 +27,6 @@ class ttimport */ function importModule($key, $cur, $index_file, $unit_count, $module_srl, $guestbook_module_srl, $user_id, $module_name=null) { - // Pre-create the objects needed - $this->oXmlParser = new XmlParser(); // Get category information of the target module $oDocumentController = getController('document'); $oDocumentModel = getModel('document'); @@ -45,8 +37,8 @@ class ttimport $category_file = preg_replace('/index$/i', 'category.xml', $index_file); if(file_exists($category_file)) { - // Create the xmlParser object - $xmlDoc = $this->oXmlParser->loadXmlFile($category_file); + $xmlDoc = Rhymix\Framework\Parsers\XEXMLParser::loadXmlFile($category_file); + // List category information if($xmlDoc->categories->category) { @@ -62,7 +54,7 @@ class ttimport $obj = null; $obj->title = $category; - $obj->module_srl = $module_srl; + $obj->module_srl = $module_srl; if($v->parent) $obj->parent_srl = $match_sequence[$v->parent]; $output = $oDocumentController->insertCategory($obj); @@ -126,7 +118,7 @@ class ttimport if($started) $buff .= $str; } - $xmlDoc = $this->oXmlParser->parse(''.$buff); + $xmlDoc = Rhymix\Framework\Parsers\XEXMLParser::loadXmlString(''.$buff); $author_xml_id = $xmlDoc->post->author->body; @@ -250,7 +242,7 @@ class ttimport // Save state if not published if(!in_array($xmlDoc->post->visibility->body, $status_published)) { - $obj->module_srl = $member_info->member_srl; + $obj->module_srl = $member_info->member_srl; } } // Document @@ -288,8 +280,8 @@ class ttimport $guestbook_file = preg_replace('/index$/i', 'guestbook.xml', $index_file); if(file_exists($guestbook_file)) { - // Create the xmlParser object - $xmlDoc = $this->oXmlParser->loadXmlFile($guestbook_file); + $xmlDoc = Rhymix\Framework\Parsers\XEXMLParser::loadXmlFile($guestbook_file); + // Handle guest book information if($guestbook_module_srl && $xmlDoc->guestbook->comment) { @@ -480,7 +472,7 @@ class ttimport $buff .= ''; - $xmlDoc = $this->oXmlParser->parse($buff); + $xmlDoc = Rhymix\Framework\Parsers\XEXMLParser::loadXmlString($buff); $file_obj->source_filename = $xmlDoc->attachment->label->body; $file_obj->download_count = $xmlDoc->attachment->downloads->body; @@ -516,7 +508,7 @@ class ttimport { $uploaded_count++; $tmp_obj = null; - if($file_obj->direct_download == 'Y') $files[$name]->url = $file_obj->uploaded_filename; + if($file_obj->direct_download == 'Y') $files[$name]->url = $file_obj->uploaded_filename; else $files[$name]->url = getUrl('','module','file','act','procFileDownload','file_srl',$file_obj->file_srl,'sid',$file_obj->sid); $files[$name]->direct_download = $file_obj->direct_download; $files[$name]->source_filename = $file_obj->source_filename; @@ -583,7 +575,7 @@ class ttimport if(preg_match('/\.(jpg|gif|jpeg|png)$/i', $obj->source_filename)) { return sprintf('%s', $obj->url, str_replace('"','\\"',$matches[4])); - // If other multimedia file but image is, + // If other multimedia file but image is, } else { @@ -606,7 +598,7 @@ class ttimport $key = $matches[1]; if(!$key) return $matches[0]; - return + return ''. ''. ''. diff --git a/modules/install/conf/info.xml b/modules/install/conf/info.xml index b42929f70..94d1ddef8 100644 --- a/modules/install/conf/info.xml +++ b/modules/install/conf/info.xml @@ -1,6 +1,6 @@ - 설치관리 + 설치 관리 Manage installation Quản lý cài đặt 安装管理 @@ -18,8 +18,8 @@ Модуль для управления установкой 安裝管理模組 Kurulumu yönetmek için olan modüldür - 1.7 - 2013-11-27 + RX_VERSION + RX_CORE system diff --git a/modules/install/install.admin.controller.php b/modules/install/install.admin.controller.php index 524530a73..d67dc5453 100644 --- a/modules/install/install.admin.controller.php +++ b/modules/install/install.admin.controller.php @@ -24,7 +24,8 @@ class installAdminController extends install $oInstallController = getController('install'); $oInstallController->installModule($module_name, './modules/'.$module_name); - + $oModuleController = getController('module'); + $oModuleController->registerActionForwardRoutes($module_name); $this->setMessage('success_installed'); } @@ -35,17 +36,54 @@ class installAdminController extends install { @set_time_limit(0); $module_name = Context::get('module_name'); - if(!$module_name) throw new Rhymix\Framework\Exceptions\InvalidRequest; + if(!$module_name) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } - $oModule = getModule($module_name, 'class'); - if(!$oModule) throw new Rhymix\Framework\Exceptions\InvalidRequest; - - $output = $oModule->moduleUpdate(); + Rhymix\Framework\Session::close(); + + $oModuleController = ModuleController::getInstance(); + $oModule = ModuleModel::getModuleInstallClass($module_name); + if($oModule && method_exists($oModule, 'moduleUpdate')) + { + $output = $oModule->moduleUpdate(); + if($output instanceof BaseObject && !$output->toBool()) + { + Rhymix\Framework\Session::start(); + return $output; + } + } + + $output = $oModuleController->registerActionForwardRoutes($module_name); if($output instanceof BaseObject && !$output->toBool()) { + Rhymix\Framework\Session::start(); return $output; } - + + $output = $oModuleController->registerEventHandlers($module_name); + if($output instanceof BaseObject && !$output->toBool()) + { + Rhymix\Framework\Session::start(); + return $output; + } + + $output = $oModuleController->registerNamespaces($module_name); + if($output instanceof BaseObject && !$output->toBool()) + { + Rhymix\Framework\Session::start(); + return $output; + } + + $output = $oModuleController->registerPrefixes($module_name); + if($output instanceof BaseObject && !$output->toBool()) + { + Rhymix\Framework\Session::start(); + return $output; + } + + Rhymix\Framework\Session::start(); $this->setMessage('success_updated'); } diff --git a/modules/install/install.class.php b/modules/install/install.class.php index 613d08a28..577cabe0d 100644 --- a/modules/install/install.class.php +++ b/modules/install/install.class.php @@ -12,7 +12,7 @@ class install extends ModuleObject */ function moduleInstall() { - + } /** @@ -28,7 +28,7 @@ class install extends ModuleObject */ function moduleUpdate() { - + } /** diff --git a/modules/install/install.controller.php b/modules/install/install.controller.php index c4a2b8980..8e1e7c995 100644 --- a/modules/install/install.controller.php +++ b/modules/install/install.controller.php @@ -19,7 +19,7 @@ class installController extends install { throw new Rhymix\Framework\Exception('msg_already_installed'); } - + // Increase time limit. @set_time_limit(0); } @@ -31,7 +31,13 @@ class installController extends install { // Get DB config variables. $config = Context::gets('db_type', 'db_host', 'db_port', 'db_user', 'db_pass', 'db_database', 'db_prefix'); - + + // Disallow installation using the root account. + if (trim($config->db_user) === 'root' && !preg_match('/Development Server$/', $_SERVER['SERVER_SOFTWARE'])) + { + return new BaseObject(-1, 'msg_dbroot_disallowed'); + } + // Create a temporary setting object. Rhymix\Framework\Config::set('db.master', array( 'type' => $config->db_type, @@ -40,22 +46,22 @@ class installController extends install 'user' => $config->db_user, 'pass' => $config->db_pass, 'database' => $config->db_database, - 'prefix' => rtrim($config->db_prefix, '_') . '_', + 'prefix' => $config->db_prefix ? (rtrim($config->db_prefix, '_') . '_') : '', )); - + // Check connection to the DB. $oDB = DB::getInstance(); $output = $oDB->getError(); - if (!$output->toBool() || !$oDB->isConnected()) + if (!$output->toBool() || !$oDB->getHandle()) { return $output; } - + // Check MySQL server capabilities. if(stripos($config->db_type, 'mysql') !== false) { // Check if InnoDB is supported. - $show_engines = $oDB->_fetch($oDB->_query('SHOW ENGINES')); + $show_engines = $oDB->query('SHOW ENGINES')->fetchAll(); foreach($show_engines as $engine_info) { if ($engine_info->Engine === 'InnoDB' && $engine_info->Support !== 'NO') @@ -64,12 +70,12 @@ class installController extends install break; } } - + // Check if utf8mb4 is supported. $oDB->charset = $oDB->getBestSupportedCharset(); $config->db_charset = $oDB->charset; } - + // Check if tables already exist. $table_check = array('documents', 'comments', 'modules', 'sites'); foreach ($table_check as $table_name) @@ -79,10 +85,10 @@ class installController extends install throw new Rhymix\Framework\Exception('msg_table_already_exists'); } } - + // Save DB config in session. $_SESSION['db_config'] = $config; - + // Continue the installation. if(!in_array(Context::getRequestMethod(), array('XMLRPC','JSON'))) { @@ -101,13 +107,13 @@ class installController extends install { throw new Rhymix\Framework\Exception('msg_already_installed'); } - + // Get install parameters. $config = Rhymix\Framework\Config::getDefaults(); if ($install_config) { $install_config = (array)$install_config; - $config['db']['master']['type'] = str_replace('_innodb', '', $install_config['db_type']); + $config['db']['master']['type'] = 'mysql'; $config['db']['master']['host'] = $install_config['db_hostname']; $config['db']['master']['port'] = $install_config['db_port']; $config['db']['master']['user'] = $install_config['db_userid']; @@ -115,7 +121,7 @@ class installController extends install $config['db']['master']['database'] = $install_config['db_database']; $config['db']['master']['prefix'] = $install_config['db_table_prefix']; $config['db']['master']['charset'] = $install_config['db_charset']; - $config['db']['master']['engine'] = strpos($install_config['db_type'], 'innodb') !== false ? 'innodb' : (strpos($install_config['db_type'], 'mysql') !== false ? 'myisam' : null); + $config['db']['master']['engine'] = strpos($install_config['db_type'], 'innodb') !== false ? 'innodb' : 'myisam'; $config['use_rewrite'] = $install_config['use_rewrite'] === 'Y' ? true : false; $config['url']['ssl'] = $install_config['use_ssl'] ?: 'none'; $time_zone = $install_config['time_zone']; @@ -127,7 +133,7 @@ class installController extends install } else { - $config['db']['master']['type'] = str_replace('_innodb', '', $_SESSION['db_config']->db_type); + $config['db']['master']['type'] = 'mysql'; $config['db']['master']['host'] = $_SESSION['db_config']->db_host; $config['db']['master']['port'] = $_SESSION['db_config']->db_port; $config['db']['master']['user'] = $_SESSION['db_config']->db_user; @@ -135,29 +141,29 @@ class installController extends install $config['db']['master']['database'] = $_SESSION['db_config']->db_database; $config['db']['master']['prefix'] = $_SESSION['db_config']->db_prefix; $config['db']['master']['charset'] = $_SESSION['db_config']->db_charset; - $config['db']['master']['engine'] = strpos($_SESSION['db_config']->db_type, 'innodb') !== false ? 'innodb' : (strpos($_SESSION['db_config']->db_type, 'mysql') !== false ? 'myisam' : null); + $config['db']['master']['engine'] = strpos($_SESSION['db_config']->db_type, 'innodb') !== false ? 'innodb' : 'myisam'; $config['use_rewrite'] = $_SESSION['use_rewrite'] === 'Y' ? true : false; $config['url']['ssl'] = Context::get('use_ssl') ?: 'none'; $time_zone = Context::get('time_zone'); $user_info = Context::gets('email_address', 'password', 'nick_name', 'user_id'); } - + // Fix the database table prefix. $config['db']['master']['prefix'] = rtrim($config['db']['master']['prefix'], '_'); if ($config['db']['master']['prefix'] !== '') { $config['db']['master']['prefix'] .= '_'; } - + // Create new crypto keys. $config['crypto']['encryption_key'] = Rhymix\Framework\Security::getRandom(64, 'alnum'); $config['crypto']['authentication_key'] = Rhymix\Framework\Security::getRandom(64, 'alnum'); $config['crypto']['session_key'] = Rhymix\Framework\Security::getRandom(64, 'alnum'); - + // Set the default language. $config['locale']['default_lang'] = Context::getLangType(); $config['locale']['enabled_lang'] = array($config['locale']['default_lang']); - + // Set the default time zone. if (strpos($time_zone, '/') !== false) { @@ -177,7 +183,7 @@ class installController extends install $config['locale']['default_timezone'] = 'Etc/GMT' . ($user_timezone > 0 ? '-' : '+') . abs($user_timezone); } } - + // Set the internal time zone. if ($config['locale']['default_timezone'] === 'Asia/Seoul') { @@ -191,24 +197,24 @@ class installController extends install { $config['locale']['internal_timezone'] = 0; } - + // Set the default URL. $config['url']['default'] = Context::getRequestUri(); - + // Set the default umask. $config['file']['umask'] = Rhymix\Framework\Storage::recommendUmask(); - + // Load the new configuration. Rhymix\Framework\Config::setAll($config); Context::loadDBInfo($config); - + // Check DB. - $oDB = DB::getInstance(); - if (!$oDB->isConnected()) + $oDB = DB::getInstance('master'); + if (!$oDB->getHandle()) { return $oDB->getError(); } - + // Assign a temporary administrator while installing. foreach ($user_info as $key => $val) { @@ -216,7 +222,7 @@ class installController extends install } $user_info->is_admin = 'Y'; Context::set('logged_info', $user_info); - + // Install all the modules. try { @@ -229,9 +235,9 @@ class installController extends install $oDB->rollback(); throw new Rhymix\Framework\Exception($e->getMessage()); } - + // Execute the install script. - $scripts = FileHandler::readDir(_XE_PATH_ . 'modules/install/script', '/(\.php)$/'); + $scripts = FileHandler::readDir(RX_BASEDIR . 'modules/install/script', '/(\.php)$/'); if(count($scripts)) { sort($scripts); @@ -241,9 +247,8 @@ class installController extends install $output = include($script_path . $script); } } - - // Apply site lock. + // Apply site lock. if (Context::get('use_sitelock') === 'Y') { $user_ip_range = getView('install')->detectUserIPRange(); @@ -251,17 +256,23 @@ class installController extends install Rhymix\Framework\Config::set('lock.message', 'This site is locked.'); Rhymix\Framework\Config::set('lock.allow', array('127.0.0.1', $user_ip_range)); } - + + // Use APC cache if available. + if (function_exists('apcu_exists')) + { + Rhymix\Framework\Config::set('cache.type', 'apc'); + } + // Save the new configuration. Rhymix\Framework\Config::save(); - + // Unset temporary session variables. unset($_SESSION['use_rewrite']); unset($_SESSION['db_config']); - + // Redirect to the home page. $this->setMessage('msg_install_completed'); - + $returnUrl = Context::get('success_return_url') ? Context::get('success_return_url') : RX_BASEURL; $this->setRedirectUrl($returnUrl); return new BaseObject(); @@ -286,7 +297,7 @@ class installController extends install } // Check DB - if(DB::getEnableList()) + if(extension_loaded('pdo_mysql')) { $checklist['db_support'] = true; } @@ -296,7 +307,7 @@ class installController extends install } // Check permission - if(is_writable('./')||is_writable('./files')) + if(is_writable(RX_BASEDIR) || is_writable(RX_BASEDIR . 'files')) { $checklist['permission'] = true; } @@ -305,10 +316,20 @@ class installController extends install $checklist['permission'] = false; } - // Check session.auto_start - if(ini_get('session.auto_start') != 1) + // Check session availability + $license_agreement_time = intval(trim(FileHandler::readFile($this->flagLicenseAgreement))); + if(isset($_SESSION['license_agreement']) && (!$license_agreement_time || ($license_agreement_time == $_SESSION['license_agreement']))) { - $checklist['session'] = true; + $sess_autostart = intval(ini_get('session.auto_start')); + + if($sess_autostart === 0) + { + $checklist['session'] = true; + } + else + { + $checklist['session'] = false; + } } else { @@ -316,7 +337,7 @@ class installController extends install } // Check curl - if(function_exists('curl_init')) + if(function_exists('curl_init') && function_exists('curl_exec')) { $checklist['curl'] = true; } @@ -326,7 +347,7 @@ class installController extends install } // Check GD - if(function_exists('imagecreatefromgif')) + if(function_exists('imagecreatefromjpeg') && function_exists('imagecreatefrompng')) { $checklist['gd'] = true; } @@ -346,7 +367,7 @@ class installController extends install } // Check json - if(function_exists('json_encode')) + if(function_exists('json_encode') && function_exists('json_decode')) { $checklist['json'] = true; } @@ -400,25 +421,15 @@ class installController extends install function procInstallLicenseAgreement() { $vars = Context::getRequestVars(); - - $license_agreement = ($vars->license_agreement == 'Y') ? true : false; - - if($license_agreement) + if($vars->license_agreement !== 'Y') { - $currentTime = $_SERVER['REQUEST_TIME']; - FileHandler::writeFile($this->flagLicenseAgreement, $currentTime); - } - else - { - FileHandler::removeFile($this->flagLicenseAgreement); throw new Rhymix\Framework\Exception('msg_must_accept_license_agreement'); } - if(!in_array(Context::getRequestMethod(),array('XMLRPC','JSON'))) - { - $returnUrl = Context::get('success_return_url') ? Context::get('success_return_url') : getNotEncodedUrl('', 'act', 'dispInstallCheckEnv'); - $this->setRedirectUrl($returnUrl); - } + $license_agreement_time = time(); + $_SESSION['license_agreement'] = $license_agreement_time; + FileHandler::writeFile($this->flagLicenseAgreement, $license_agreement_time . PHP_EOL); + $this->setRedirectUrl(getNotEncodedUrl('', 'act', 'dispInstallCheckEnv')); } /** @@ -447,7 +458,6 @@ class installController extends install */ function installDownloadedModule() { - $oModuleModel = getModel('module'); // Create a table ny finding schemas/*.xml file in each module $module_list = FileHandler::readDir('./modules/', NULL, false, true); $modules = array(); @@ -455,16 +465,30 @@ class installController extends install { // Get module name $module = basename($module_path); - $xml_info = $oModuleModel->getModuleInfoXml($module); - if(!$xml_info) continue; - $modules[$xml_info->category][] = $module; + + // Only install default modules at this time + if (!Context::isDefaultPlugin($module, 'module')) + { + continue; + } + + // Try to group modules by category + $xml_info = ModuleModel::getModuleInfoXml($module); + if (!$xml_info) + { + continue; + } + $modules[$xml_info->category ?: 'other'][] = $module; } + // Install "module" module in advance $this->installModule('module','./modules/module'); - $oModule = getClass('module'); - if($oModule->checkUpdate()) $oModule->moduleUpdate(); + $this->updateModule('module'); + // Determine the order of module installation depending on category $install_step = array('system','content','member'); + $update_order = []; + // Install all the remaining modules foreach($install_step as $category) { @@ -474,16 +498,12 @@ class installController extends install { if($module == 'module') continue; $this->installModule($module, sprintf('./modules/%s', $module)); - - $oModule = getClass($module); - if(is_object($oModule) && method_exists($oModule, 'checkUpdate')) - { - if($oModule->checkUpdate()) $oModule->moduleUpdate(); - } + $update_order[] = $module; } unset($modules[$category]); } } + // Install all the remaining modules if(count($modules)) { @@ -495,17 +515,18 @@ class installController extends install { if($module == 'module') continue; $this->installModule($module, sprintf('./modules/%s', $module)); - - $oModule = getClass($module); - if($oModule && method_exists($oModule, 'checkUpdate') && method_exists($oModule, 'moduleUpdate')) - { - if($oModule->checkUpdate()) $oModule->moduleUpdate(); - } + $update_order[] = $module; } } } } + // Update all modules + foreach ($update_order as $module) + { + $this->updateModule($module); + } + return new BaseObject(); } @@ -519,28 +540,98 @@ class installController extends install // Create a table if the schema xml exists in the "schemas" directory of the module $schema_dir = sprintf('%s/schemas/', $module_path); $schema_files = FileHandler::readDir($schema_dir, NULL, false, true); - - $file_cnt = count($schema_files); - for($i=0;$i<$file_cnt;$i++) + $schema_sorted = []; + foreach ($schema_files as $filename) { - $file = trim($schema_files[$i]); - if(!$file || substr($file,-4)!='.xml') continue; - $output = $oDB->createTableByXmlFile($file); - if($output === false) + if (!preg_match('/\/([a-zA-Z0-9_]+)\.xml$/', $filename, $matches)) { - throw new Exception(lang('msg_create_table_failed') . ': ' . $oDB->getError()->getMessage()); + continue; + } + if (preg_match('/]*deleted="true"/i', file_get_contents($filename))) + { + continue; + } + + $table_name = $matches[1]; + if(isset($schema_sorted[$table_name]) || $oDB->isTableExists($table_name)) + { + continue; + } + + $schema_sorted[$table_name] = $filename; + } + + $schema_sorted = Rhymix\Framework\Parsers\DBTableParser::resolveDependency($schema_sorted); + foreach ($schema_sorted as $table_name => $filename) + { + $output = $oDB->createTable($filename); + if(!$output->toBool()) + { + throw new Exception(lang('msg_create_table_failed') . ': ' . $table_name . ': ' . $oDB->getError()->getMessage()); } } + // Create a table and module instance and then execute install() method - unset($oModule); - $oModule = getClass($module); - if(method_exists($oModule, 'moduleInstall')) $oModule->moduleInstall(); + $oModule = ModuleModel::getModuleInstallClass($module); + if($oModule && method_exists($oModule, 'moduleInstall')) + { + $oModule->moduleInstall(); + } return new BaseObject(); } - + + /** + * Update a module if necessary. + * + * @param string $module + * @return mixed + */ + public function updateModule($module) + { + $oModuleController = ModuleController::getInstance(); + $oModule = ModuleModel::getModuleInstallClass($module); + if (is_object($oModule) && method_exists($oModule, 'checkUpdate') && method_exists($oModule, 'moduleUpdate')) + { + if ($oModule->checkUpdate()) + { + $output = $oModule->moduleUpdate(); + if($output instanceof BaseObject && !$output->toBool()) + { + return $output; + } + } + } + + $output = $oModuleController->registerActionForwardRoutes($module); + if($output instanceof BaseObject && !$output->toBool()) + { + return $output; + } + + $output = $oModuleController->registerEventHandlers($module); + if($output instanceof BaseObject && !$output->toBool()) + { + return $output; + } + + $output = $oModuleController->registerNamespaces($module); + if($output instanceof BaseObject && !$output->toBool()) + { + return $output; + } + + $output = $oModuleController->registerPrefixes($module); + if($output instanceof BaseObject && !$output->toBool()) + { + return $output; + } + + return new BaseObject(); + } + /** * Placeholder for third-party apps that try to manipulate system configuration. - * + * * @return void */ public function makeConfigFile() diff --git a/modules/install/install.model.php b/modules/install/install.model.php index 9bc7b0562..275a0b57b 100644 --- a/modules/install/install.model.php +++ b/modules/install/install.model.php @@ -35,7 +35,7 @@ class installModel extends install function getInstallFTPList() { - if(!($ftp_info = Context::getRequestVars()) || !$ftp_info->ftp_user || !$ftp_info->ftp_password) + if(!($ftp_info = Context::getRequestVars()) || !$ftp_info->ftp_user || !$ftp_info->ftp_password) { return $this->setError('msg_ftp_invalid_auth_info'); } @@ -57,17 +57,17 @@ class installModel extends install if(!$connection) return $this->setError(sprintf(lang('msg_ftp_not_connected'), 'host')); if(! @ftp_login($connection, $ftp_info->ftp_user, $ftp_info->ftp_password)) { - ftp_close($connection); + ftp_close($connection); return $this->setError('msg_ftp_invalid_auth_info'); } - if($ftp_info->ftp_pasv != "N") + if($ftp_info->ftp_pasv != "N") { ftp_pasv($connection, true); } $_list = ftp_rawlist($connection, $this->pwd); - ftp_close($connection); + ftp_close($connection); } else { @@ -92,7 +92,7 @@ class installModel extends install { foreach($_list as $k => $v) { - $src = new stdClass(); + $src = new stdClass(); $src->data = $v; $res = Context::convertEncoding($src); $v = $res->data; diff --git a/modules/install/install.view.php b/modules/install/install.view.php index f539ddcbb..dbf02797b 100644 --- a/modules/install/install.view.php +++ b/modules/install/install.view.php @@ -21,13 +21,13 @@ class installView extends install { throw new Rhymix\Framework\Exception('msg_already_installed'); } - + // Set the browser title. Context::setBrowserTitle(lang('introduce_title')); - + // Specify the template path. $this->setTemplatePath($this->module_path.'tpl'); - + // Check the environment. $oInstallController = getController('install'); self::$checkEnv = $oInstallController->checkInstallEnv(); @@ -46,7 +46,7 @@ class installView extends install if (file_exists(RX_BASEDIR . 'config/install.config.php')) { include RX_BASEDIR . 'config/install.config.php'; - + if (isset($install_config) && is_array($install_config)) { $oInstallController = getController('install'); @@ -61,7 +61,7 @@ class installView extends install } } } - + // Otherwise, display the license agreement screen. Context::set('lang_type', Context::getLangType()); $this->setTemplateFile('license_agreement'); @@ -74,8 +74,8 @@ class installView extends install { // Create a temporary file for mod_rewrite check. self::$rewriteCheckString = Rhymix\Framework\Security::getRandom(32); - FileHandler::writeFile(_XE_PATH_ . self::$rewriteCheckFilePath, self::$rewriteCheckString);; - + FileHandler::writeFile(RX_BASEDIR . self::$rewriteCheckFilePath, self::$rewriteCheckString); + // Check if the web server is nginx. Context::set('use_nginx', stripos($_SERVER['SERVER_SOFTWARE'], 'nginx') !== false); $this->setTemplateFile('check_env'); @@ -91,31 +91,17 @@ class installView extends install { return $this->dispInstallCheckEnv(); } - + // Delete mod_rewrite check file - FileHandler::removeFile(_XE_PATH_ . self::$rewriteCheckFilePath); - + FileHandler::removeFile(RX_BASEDIR . self::$rewriteCheckFilePath); + // Save mod_rewrite check status. if(Context::get('rewrite') === 'Y') { Context::set('use_rewrite', $_SESSION['use_rewrite'] = 'Y'); } - - $defaultDatabase = 'mysqli'; - $disableList = DB::getDisableList(); - if(is_array($disableList)) - { - foreach($disableList as $key => $value) - { - if($value->db_type == $defaultDatabase) - { - $defaultDatabase = 'mysql'; - break; - } - } - } - Context::set('defaultDatabase', $defaultDatabase); - Context::set('error_return_url', getNotEncodedUrl('', 'act', Context::get('act'), 'db_type', Context::get('db_type'))); + + Context::set('error_return_url', getNotEncodedUrl('', 'act', Context::get('act'))); $this->setTemplateFile('db_config'); } @@ -129,19 +115,19 @@ class installView extends install { return $this->dispInstallCheckEnv(); } - + // Get list of time zones. Context::set('timezones', Rhymix\Framework\DateTime::getTimezoneList()); - + // Automatically select a time zone for the user. Context::set('selected_timezone', $this->detectUserTimeZone()); - + // Always use SSL if installing via SSL. Context::set('use_ssl', RX_SSL ? 'always' : 'none'); Context::set('sitelock_ip_range', $this->detectUserIPRange()); $this->setTemplateFile('other_config'); } - + /** * Detect the IP range of the user. */ @@ -162,7 +148,7 @@ class installView extends install return RX_CLIENT_IP; } } - + /** * Detect best time zone for the user. */ diff --git a/modules/install/lang/en.php b/modules/install/lang/en.php index 981de5f8f..840a175e2 100644 --- a/modules/install/lang/en.php +++ b/modules/install/lang/en.php @@ -24,13 +24,14 @@ $lang->install_checklist_title['iconv'] = 'iconv / mbstring'; $lang->install_checklist_title['json'] = 'json'; $lang->install_checklist_title['gd'] = 'gd'; $lang->install_checklist_title['mcrypt'] = 'mcrypt / openssl'; -$lang->install_checklist_title['session'] = 'session.auto_start setting'; +$lang->install_checklist_title['session'] = 'session'; $lang->install_checklist_title['db_support'] = 'DB support'; $lang->install_checklist_desc['php_version'] = '[Required] Rhymix supports only PHP Version %s or higher'; $lang->install_checklist_desc['php_version_warning'] = '[Recommend] Rhymix recommends only PHP Version %s or higher'; $lang->install_checklist_desc['permission'] = '[Required] Please create a \'files\' directory under the installation path and change its permissions to 777.'; $lang->install_checklist_desc['xml'] = '[Required] XML Library is needed for XML communication'; -$lang->install_checklist_desc['session'] = '[Required] PHP setting file\'s (php.ini) \'Session.auto_start\' must equal to zero in order for Rhymix to use the session'; +$lang->install_checklist_desc['session'] = '[Required] PHP Session must be available, and \'session.auto_start\' must be off in order to use the session.'; +$lang->install_checklist_desc['free_domain_warning'] = 'If you are using a free subdomain provided by the hosting company (e.g. ID.hosting-company.com),
              Please clear your browser cookies and return to the previous screen.'; $lang->install_checklist_desc['iconv'] = 'Iconv should be installed in order to convert between UTF-8 and other language sets'; $lang->install_checklist_desc['gd'] = 'GD Library should be installed in order to use functions to convert images'; $lang->install_checklist_xml = 'Install XML Library'; @@ -45,10 +46,6 @@ $lang->cmd_install_refresh_page = 'Refresh'; $lang->cmd_install_next = 'Continue installation'; $lang->cmd_ignore = 'Ignore'; $lang->cmd_recommended = 'Recommended'; -$lang->db_desc['mysqli'] = 'Use MySQL or MariaDB. Rhymix will automatically use InnoDB if it is supported.'; -$lang->db_desc['mysql'] = 'Use MySQL or MariaDB. Rhymix will automatically use InnoDB if it is supported.'; -$lang->db_desc['cubrid'] = 'CUBRID is not supported.'; -$lang->db_desc['mssql'] = 'Microsoft SQL Server is not supported.'; $lang->can_use_when_installed = 'Not installed on this server'; $lang->form_title = 'Database & Administrator Information'; $lang->db_title = 'Please input DB information'; @@ -73,7 +70,7 @@ $lang->about_rewrite = 'If web server provides mod_rewrite, long URL such as checking_rewrite = 'Checking whether "Friendly URL" feature is available...'; $lang->disable_rewrite = '"Friendly URL" feature is not available. Please check with the server administrator about mod_rewrite module support.'; $lang->disable_rewrite_can_proceed = 'It is OK to continue to install and use Rhymix without the "Friendly URL" feature.'; -$lang->about_nginx_rewrite = 'To use this feature at nginx, you need to configure rewrite. See here.'; +$lang->about_nginx_rewrite = 'To use this feature at nginx, you need to configure rewrite. See here.'; $lang->time_zone = 'Time Zone'; $lang->about_time_zone = 'If the server time is different from your time zone, you can use this option to display times in your time zone.'; $lang->use_ssl = 'SSL'; @@ -90,6 +87,7 @@ $lang->msg_db_checking = 'Checking...'; $lang->msg_installing = 'Installing...'; $lang->msg_cannot_proc = 'Installation environment is not proper to proceed.'; $lang->msg_already_installed = 'Rhymix is already installed.'; +$lang->msg_dbroot_disallowed = 'Installing with the DB root account is not permitted. Please create a non-root account and use it.'; $lang->msg_dbconnect_failed = 'An error has occurred while connecting to DB. Please check DB information again.'; $lang->msg_table_is_exists = 'Table is already created in the DB. Config file is recreated.'; $lang->msg_install_completed = 'Installation has been completed. Thank you for choosing Rhymix.'; diff --git a/modules/install/lang/es.php b/modules/install/lang/es.php index b2a1d92c6..784d00143 100644 --- a/modules/install/lang/es.php +++ b/modules/install/lang/es.php @@ -8,10 +8,10 @@ $lang->install_checklist_title['iconv'] = 'iconv / mbstring'; $lang->install_checklist_title['json'] = 'json'; $lang->install_checklist_title['gd'] = 'gd'; $lang->install_checklist_title['mcrypt'] = 'mcrypt / openssl'; -$lang->install_checklist_title['session'] = 'Configuración session.auto_start'; +$lang->install_checklist_title['session'] = 'session'; $lang->install_checklist_desc['permission'] = '[Requerido] La ruta de la instalación de Rhymix o el directorio de ./archivos deberia tener la atribución 777'; $lang->install_checklist_desc['xml'] = '[Requerido] Libreria XML es necesario para la comunicación de XML'; -$lang->install_checklist_desc['session'] = '[Requerido] Para el uso de la sesion de Rhymix, el archivo php.ini deberia estar configurada session.auto_start=0'; +$lang->install_checklist_desc['session'] = '[Requerido] La sesión de PHP debe estar disponible y \'session.auto_start\' debe estar \'off\' para poder utilizar la sesión.'; $lang->install_checklist_desc['iconv'] = 'Para transformar UTF-8 y otra paquete del idioma deberia estar instalado el Iconv.'; $lang->install_checklist_desc['gd'] = 'Libreria GD deberia estar instalado para utilizar la funcion de convertir la imagen'; $lang->install_checklist_xml = 'Instalar Librería XML '; @@ -24,10 +24,6 @@ $lang->install_permission_denied = 'La atribución de la ruta de instalacion no $lang->cmd_install_refresh_page = 'Refrescar'; $lang->cmd_install_next = 'Continuar la instalación'; $lang->cmd_recommended = 'Recomendado'; -$lang->db_desc['mysqli'] = 'Utilizando MySQL o MariaDB. Rhymix automáticamente usará InnoDB si es compatible.'; -$lang->db_desc['mysql'] = 'Utilizando MySQL o MariaDB. Rhymix automáticamente usará InnoDB si es compatible.'; -$lang->db_desc['cubrid'] = 'CUBRID no es compatible.'; -$lang->db_desc['mssql'] = 'Microsoft SQL Server no es compatible.'; $lang->form_title = 'Ingresar BD & Información del Administrador;'; $lang->db_title = 'Por favor escribir información de BD'; $lang->db_type = 'Tipo de BD'; diff --git a/modules/install/lang/fr.php b/modules/install/lang/fr.php index af3ab7bc9..796cf9c4d 100644 --- a/modules/install/lang/fr.php +++ b/modules/install/lang/fr.php @@ -8,10 +8,10 @@ $lang->install_checklist_title['iconv'] = 'iconv / mbstring'; $lang->install_checklist_title['json'] = 'json'; $lang->install_checklist_title['gd'] = 'gd'; $lang->install_checklist_title['mcrypt'] = 'mcrypt / openssl'; -$lang->install_checklist_title['session'] = 'Configuration de session.auto_start'; +$lang->install_checklist_title['session'] = 'session'; $lang->install_checklist_desc['permission'] = '[Obligatoire] Chemin de l\'installation de Rhymix ou la permission de répertoire de ./files doit être 777'; $lang->install_checklist_desc['xml'] = '[Obligatoire] La bibliothèque de XML est nécessaire pour la communication de XML'; -$lang->install_checklist_desc['session'] = '[Obligatoire] \'Session.auto_start\' dans le fichier de configuration pour PHP (php.ini) doit être égal à zéro car Rhymix utilise la session'; +$lang->install_checklist_desc['session'] = '[Obligatoire] La session PHP doit être disponible, et \'session.auto_start\' dans le fichier de configuration pour PHP (php.ini) doit être \'off\' car Rhymix utilise la session'; $lang->install_checklist_desc['iconv'] = 'Iconv doit être installé afin de convertir UTF-8 et des autres assortiments des langues'; $lang->install_checklist_desc['gd'] = 'La bibliothèque de GD doit être installé afin d\'utiliser la fonction à convertir des images'; $lang->install_checklist_xml = 'Installation la bibliothèque de XML'; @@ -24,10 +24,6 @@ $lang->install_permission_denied = 'La permission du chemin d\'installation n\'e $lang->cmd_install_refresh_page = 'Rafraîchir'; $lang->cmd_install_next = 'Continuer à installer'; $lang->cmd_recommended = 'Recommandé'; -$lang->db_desc['mysqli'] = 'Utilisera MySQL ou MariaDB. Rhymix utilisera automatiquement InnoDB s\'il est pris en charge.'; -$lang->db_desc['mysql'] = 'Utilisera MySQL ou MariaDB. Rhymix utilisera automatiquement InnoDB s\'il est pris en charge.'; -$lang->db_desc['cubrid'] = 'CUBRID n\'est pas compatible avec Rhymix.'; -$lang->db_desc['mssql'] = 'Microsoft SQL Server n\'est pas compatible avec Rhymix.'; $lang->can_use_when_installed = 'Pas installé sur ce serveur'; $lang->form_title = 'Entrer des informations de Base de données et Administrateur'; $lang->db_title = 'Entrez l\'information de Base de Données, S.V.P.'; diff --git a/modules/install/lang/ja.php b/modules/install/lang/ja.php index d3be71d5d..480c7acf4 100644 --- a/modules/install/lang/ja.php +++ b/modules/install/lang/ja.php @@ -24,13 +24,13 @@ $lang->install_checklist_title['iconv'] = 'iconv / mbstring'; $lang->install_checklist_title['json'] = 'json'; $lang->install_checklist_title['gd'] = 'gd'; $lang->install_checklist_title['mcrypt'] = 'mcrypt / openssl'; -$lang->install_checklist_title['session'] = 'session.auto_startの設定'; +$lang->install_checklist_title['session'] = 'session'; $lang->install_checklist_title['db_support'] = 'DBサポート'; $lang->install_checklist_desc['php_version'] = '[必修] %s以上のPHPバージョンでインストールが可能です。'; $lang->install_checklist_desc['php_version_warning'] = '[推奨] RhymixはPHP %sバージョン以上をおすすめします。'; $lang->install_checklist_desc['permission'] = '【必須】Rhymixのインストール先、または「./files」ディレクトリのパーミッションを「777」に設定してください。'; $lang->install_checklist_desc['xml'] = '【必須】XML通信のためにXMLライブラリが必要です。'; -$lang->install_checklist_desc['session'] = '【必須】Rhymixでは、セッションを使用しているため、「php.ini」の設定を「session.auto_start=0」にしてください。'; +$lang->install_checklist_desc['session'] = '【必須】PHPセッションを使用することができなければならず、php.iniの設定でsession.auto_start= offである必要があります。'; $lang->install_checklist_desc['iconv'] = 'UTF-8と多言語サポート及び文字コード変換のため、「iconv」をインストールする必要があります。'; $lang->install_checklist_desc['gd'] = 'イメージ変換機能を使用するためには、「GDライブラリ」をインストールする必要があります。'; $lang->install_checklist_xml = 'XMLライブラリのインストール'; @@ -45,10 +45,6 @@ $lang->cmd_install_refresh_page = 'リフレッシュ'; $lang->cmd_install_next = 'インストールを続けます。'; $lang->cmd_ignore = 'FTP設定を省略する'; $lang->cmd_recommended = '推奨'; -$lang->db_desc['mysqli'] = 'MySQLやMariaDBを利用します。InnoDBサポート時に自動的に使用されます。'; -$lang->db_desc['mysql'] = 'MySQLやMariaDBを利用します。InnoDBサポート時に自動的に使用されます。'; -$lang->db_desc['cubrid'] = 'CUBRIDはサポートしていません。'; -$lang->db_desc['mssql'] = 'Microsoft SQL Serverはサポートしていません。'; $lang->can_use_when_installed = 'このサーバーにインストールされていません'; $lang->form_title = 'データベース & 管理者情報入力'; $lang->db_title = 'データベース情報入力'; @@ -71,7 +67,7 @@ $lang->about_rewrite = 'Webサーバで「リライト・モジュール(mod_r $lang->checking_rewrite = '短縮アドレスを使用できるかどうかを確認しています...'; $lang->disable_rewrite = '短縮アドレスを使用できません。ウェブサーバー担当者に mod_rewriteサポート可否を確認してください。'; $lang->disable_rewrite_can_proceed = 'この機能は、Rhymixをインストールと使用するために必要ではありません。'; -$lang->about_nginx_rewrite = 'nginxを使用する場合、短縮アクセスを使用するためにrewrite設定が必要です。設定方法はこのページを参考ください。'; +$lang->about_nginx_rewrite = 'nginxを使用する場合、短縮アクセスを使用するためにrewrite設定が必要です。設定方法はこのページを参考ください。'; $lang->time_zone = 'タイムゾーン'; $lang->about_time_zone = 'サーバの設定時間とサービスしているローカル時間との差がある場合、タイムゾーンを指定して表示時間を合わせることができます。'; $lang->use_ssl = 'SSLを使用'; diff --git a/modules/install/lang/ko.php b/modules/install/lang/ko.php index 4ee72f347..5137fe771 100644 --- a/modules/install/lang/ko.php +++ b/modules/install/lang/ko.php @@ -24,13 +24,14 @@ $lang->install_checklist_title['iconv'] = 'iconv / mbstring'; $lang->install_checklist_title['json'] = 'json'; $lang->install_checklist_title['gd'] = 'gd'; $lang->install_checklist_title['mcrypt'] = 'mcrypt / openssl'; -$lang->install_checklist_title['session'] = 'session.auto_start 설정'; +$lang->install_checklist_title['session'] = '세션 지원'; $lang->install_checklist_title['db_support'] = 'DB 지원'; $lang->install_checklist_desc['php_version'] = '[필수] Rhymix를 설치하려면 PHP 버전이 %s 이상이어야 합니다.'; $lang->install_checklist_desc['php_version_warning'] = '[권장] Rhymix는 PHP %s 이상을 권장합니다.'; $lang->install_checklist_desc['permission'] = '[필수] Rhymix 설치 경로에 files 디렉토리를 생성하고 퍼미션을 777로 바꾸어 주십시오.'; $lang->install_checklist_desc['xml'] = '[필수] XML통신을 위하여 XML 라이브러리가 필요합니다.'; -$lang->install_checklist_desc['session'] = '[필수] Rhymix에서 세션 사용을 위해 php.ini 설정의 session.auto_start=0 이어야 합니다.'; +$lang->install_checklist_desc['session'] = '[필수] PHP 세션을 사용할 수 있어야 하며, php.ini 설정에서 session.auto_start = off 여야 합니다.'; +$lang->install_checklist_desc['free_domain_warning'] = '웹호스팅 업체에서 제공하는 무료도메인(예: ID.cafe24.com)을 사용하신다면
              브라우저 쿠키를 비우고 이전 화면으로 돌아가서 설치를 다시 시작해 주시기 바랍니다.'; $lang->install_checklist_desc['iconv'] = 'UTF-8과 다른 언어셋의 변환을 위한 iconv설치가 필요합니다.'; $lang->install_checklist_desc['gd'] = '이미지변환 기능을 사용하기 위해 GD라이브러리가 설치되어 있어야 합니다.'; $lang->install_checklist_xml = 'XML 라이브러리 설치'; @@ -45,10 +46,6 @@ $lang->cmd_install_refresh_page = '새로고침'; $lang->cmd_install_next = '설치를 진행합니다.'; $lang->cmd_ignore = '무시'; $lang->cmd_recommended = '권장'; -$lang->db_desc['mysqli'] = 'MySQL 또는 MariaDB를 이용합니다. InnoDB 지원시 자동으로 사용합니다.'; -$lang->db_desc['mysql'] = 'MySQL 또는 MariaDB를 이용합니다. InnoDB 지원시 자동으로 사용합니다.'; -$lang->db_desc['cubrid'] = 'CUBRID는 지원하지 않습니다.'; -$lang->db_desc['mssql'] = 'Microsoft SQL Server는 지원하지 않습니다.'; $lang->can_use_when_installed = '설치되어 있지 않음'; $lang->form_title = 'DB & 관리자 정보 입력'; $lang->db_title = 'DB 정보 입력'; @@ -73,7 +70,7 @@ $lang->about_rewrite = '이 기능을 사용하면 http://yourdomain/checking_rewrite = '짧은 주소를 사용할 수 있는지 확인하는 중입니다...'; $lang->disable_rewrite = '짧은 주소를 사용할 수 없습니다. 웹 서버 담당자에게 mod_rewrite 지원 여부를 확인 바랍니다.'; $lang->disable_rewrite_can_proceed = '짧은 주소를 사용할 수 없더라도 Rhymix 설치와 사용에는 지장이 없습니다.'; -$lang->about_nginx_rewrite = 'nginx에서 짧은 주소를 사용하려면 rewrite 설정이 필요합니다. 여기를 참고하세요.'; +$lang->about_nginx_rewrite = 'nginx에서 짧은 주소를 사용하려면 rewrite 설정이 필요합니다. 여기를 참고하세요.'; $lang->time_zone = '표준 시간대'; $lang->about_time_zone = '서버의 시간과 주 사용자의 시간이 다를 경우 (예: 해외 서버인 경우) 원하는 표준 시간대를 선택할 수 있습니다.'; $lang->use_ssl = 'SSL 사용'; @@ -89,6 +86,7 @@ $lang->success_installed = '설치가 되었습니다.'; $lang->msg_cannot_proc = '설치 환경이 갖춰지지 않아 요청을 실행할 수가 없습니다.'; $lang->msg_db_checking = '확인 중입니다...'; $lang->msg_installing = '설치 중입니다...'; +$lang->msg_dbroot_disallowed = 'DB root 계정으로 설치하는 것은 허용되지 않습니다. root가 아닌 일반 DB 계정을 생성하여 사용하시기 바랍니다.'; $lang->msg_already_installed = '이미 설치가 되어 있습니다.'; $lang->msg_dbconnect_failed = 'DB접속 오류가 발생하였습니다. DB정보를 다시 확인해주세요.'; $lang->msg_table_is_exists = '이미 DB에 테이블이 생성되어 있습니다. config 파일을 재생성하였습니다.'; diff --git a/modules/install/lang/ru.php b/modules/install/lang/ru.php index 64482aa5d..cb9b75593 100644 --- a/modules/install/lang/ru.php +++ b/modules/install/lang/ru.php @@ -8,10 +8,10 @@ $lang->install_checklist_title['iconv'] = 'iconv / mbstring'; $lang->install_checklist_title['json'] = 'json'; $lang->install_checklist_title['gd'] = 'gd'; $lang->install_checklist_title['mcrypt'] = 'mcrypt / openssl'; -$lang->install_checklist_title['session'] = 'session.auto_start настройка'; +$lang->install_checklist_title['session'] = 'session'; $lang->install_checklist_desc['permission'] = '[Требуется] Путь установки Rhymix или директория ./files должна иметь права доступа 777'; $lang->install_checklist_desc['xml'] = '[Требуется] XML Библиотека нужна для XML коммуникации'; -$lang->install_checklist_desc['session'] = '[Требуется] Файл настроек PHP (php.ini) \'Session.auto_start\' должен быть равен нулю, чтобы Rhymix могла использовать сессии'; +$lang->install_checklist_desc['session'] = '[Требуется] Сеанс PHP должен быть доступен, и \'session.auto_start\' должен быть \'off\', чтобы использовать сеанс.'; $lang->install_checklist_desc['iconv'] = 'Iconv должна быть установлена для конвертирования между UTF-8 и иными языковыми кодировками'; $lang->install_checklist_desc['gd'] = 'GD Библиотека должна быть установлена для использования функции конвертироваия изображений'; $lang->install_checklist_xml = 'Установить XML библиотеку'; @@ -24,10 +24,6 @@ $lang->install_permission_denied = 'Права доступа пути не ус $lang->cmd_install_refresh_page = 'обновление'; $lang->cmd_install_next = 'Продолжить установку'; $lang->cmd_recommended = 'рекомендуемые'; -$lang->db_desc['mysqli'] = 'Используйте MySQL или MariaDB. Rhymix будет автоматически использовать InnoDB, если он поддерживается.'; -$lang->db_desc['mysql'] = 'Используйте MySQL или MariaDB. Rhymix будет автоматически использовать InnoDB, если он поддерживается.'; -$lang->db_desc['cubrid'] = 'CUBRID не совместим с Rhymix.'; -$lang->db_desc['mssql'] = 'Microsoft SQL Server не совместим с Rhymix.'; $lang->form_title = 'Пожалуйста, введите дазу данных & Административная Информация'; $lang->db_title = 'Пожалуйста, введите информацию базы данных'; $lang->db_type = 'Тип базы данных'; diff --git a/modules/install/lang/tr.php b/modules/install/lang/tr.php index 25792a1b1..8768daf34 100644 --- a/modules/install/lang/tr.php +++ b/modules/install/lang/tr.php @@ -8,10 +8,10 @@ $lang->install_checklist_title['iconv'] = 'iconv / mbstring'; $lang->install_checklist_title['json'] = 'json'; $lang->install_checklist_title['gd'] = 'gd'; $lang->install_checklist_title['mcrypt'] = 'mcrypt / openssl'; -$lang->install_checklist_title['session'] = 'session.auto_start(otomatik.oturum_acma) ayarı'; +$lang->install_checklist_title['session'] = 'session'; $lang->install_checklist_desc['permission'] = '[Gerekli] Rhymix kurulum yolu ya da ./files directory yolunun yetkisi 777 olmalıdır'; $lang->install_checklist_desc['xml'] = '[Gerekli] XML iletişimi için XML kitaplığı gereklidir.'; -$lang->install_checklist_desc['session'] = '[Gerekli] PHP ayar dosyasındaki (php.ini) \'Session.auto_start\' Rhymix\'nin oturumu kullanabilmesi için sıfıra eşit olmalıdır'; +$lang->install_checklist_desc['session'] = '[Gerekli] PHP Oturumu kullanılabilir olmalı ve oturumu kullanmak için \'session.auto_start\' \'off\' olmalıdır.'; $lang->install_checklist_desc['iconv'] = 'Iconv, UTF-8 ve diğer dil ayarlarını değiştirebilmek için kurulmuş olmalıdır'; $lang->install_checklist_desc['gd'] = 'GD Kitaplığı, resim değiştirme özelliğini kullanabilmek için kurulmuş, olmalıdır'; $lang->install_checklist_xml = 'XML Kitaplığını Kur'; @@ -25,10 +25,6 @@ $lang->cmd_install_refresh_page = 'Gerekli koşulları tamamladım.'; $lang->cmd_install_next = 'Kuruluma Devam Et'; $lang->cmd_ignore = 'Önemseme'; $lang->cmd_recommended = 'Tavsiye edilen'; -$lang->db_desc['mysqli'] = 'PHP\'de mysqli_*() özellikleri için MySQL\'ü veritabanı olarak kullanınız.'; -$lang->db_desc['mysql'] = 'PHP\'de mysql_*() özellikleri için MySQL\'ü veritabanı olarak kullanınız.'; -$lang->db_desc['cubrid'] = 'CUBRID\'ü veritabanı olarak kullanın. Daha fazla bilgi için Manueli inceleyiniz'; -$lang->db_desc['mssql'] = 'Microsoft SQL Server\'ü veritabanı olarak kullanın'; $lang->form_title = 'Veritabanı & Yönetici Bilgisi'; $lang->db_title = 'Lütfen Veritabanı bilgisini giriniz'; $lang->db_type = 'Veritabanı Tipi'; diff --git a/modules/install/lang/vi.php b/modules/install/lang/vi.php index aed604fa3..b9203d202 100644 --- a/modules/install/lang/vi.php +++ b/modules/install/lang/vi.php @@ -3,10 +3,10 @@ $lang->introduce_title = 'Cài đặt Rhymix'; $lang->install_progress_menu['language'] = 'Chọn ngôn ngữ cài đặt'; $lang->install_checklist_title['php_version'] = 'Phiên bản PHP'; $lang->install_checklist_title['permission'] = 'Điều khoản thư mục files'; -$lang->install_checklist_title['session'] = 'Thiết lập session.auto_start'; +$lang->install_checklist_title['session'] = 'session'; $lang->install_checklist_desc['permission'] = '[Bắt buộc] Thư mục cài đặt của Rhymix hay ./files directory phải CHMOD thành 777'; $lang->install_checklist_desc['xml'] = '[Bắt buộc] XML Library cần thiết cho việc truyền thông File XML.'; -$lang->install_checklist_desc['session'] = '[Bắt buộc] File thiết lập của PHP (php.ini) \'Session.auto_start\' phải là 0 theo thứ tự số cho phiên làm việc của Rhymix hoạt động.'; +$lang->install_checklist_desc['session'] = '[Bắt buộc] Phiên PHP phải có sẵn và \'session.auto_start\' phải là \'off\' để sử dụng phiên.'; $lang->install_checklist_desc['iconv'] = 'Iconv cần phải được cài đặt cho việc chuyển đổi ngôn ngữ thàng UTFF-8 của những ngôn ngữ khác.'; $lang->install_checklist_desc['gd'] = 'GD Library cần phải được cài đặt cho việc chuyển đổi hình ảnh.'; $lang->install_checklist_xml = 'Cài đặt XML Library'; @@ -20,9 +20,6 @@ $lang->cmd_install_refresh_page = 'Tôi đã thay đổi để phù hợp với $lang->cmd_install_next = 'Tiếp tục cài đặt'; $lang->cmd_ignore = 'Bỏ qua'; $lang->cmd_recommended = 'Đê'; -$lang->db_desc['mysqli'] = 'Dùng chức năng mysqli_*() để sử dụng MySql Database.'; -$lang->db_desc['mysql'] = 'Dùng chức năng mysql_*() để sử dụng MySql Database.'; -$lang->db_desc['cubrid'] = 'Sử dụng CUBRID Database. Hướng dẫn'; $lang->form_title = 'Hãy nhập thông tin Database và thông tin Administrator'; $lang->db_title = 'Xin hãy nhập thông tin Database'; $lang->db_type = 'Định dạng Database'; diff --git a/modules/install/lang/zh-CN.php b/modules/install/lang/zh-CN.php index 8d0945a29..cc5444181 100644 --- a/modules/install/lang/zh-CN.php +++ b/modules/install/lang/zh-CN.php @@ -16,10 +16,10 @@ $lang->install_checklist_title['iconv'] = 'iconv / mbstring'; $lang->install_checklist_title['json'] = 'json'; $lang->install_checklist_title['gd'] = 'gd'; $lang->install_checklist_title['mcrypt'] = 'mcrypt / openssl'; -$lang->install_checklist_title['session'] = 'session.auto_start 设置'; +$lang->install_checklist_title['session'] = 'session'; $lang->install_checklist_desc['permission'] = '[必须] 的安装路径或 ./files目录属性必须是777'; $lang->install_checklist_desc['xml'] = '[必须]为了 XML通讯,将需要XML库'; -$lang->install_checklist_desc['session'] = '[必须] 为了使用缓冲功能,必须在php.ini当中设置 session.auto_start=0'; +$lang->install_checklist_desc['session'] = '[必须] PHP会话必须可用,必须在php.ini当中设置 session.auto_start = off'; $lang->install_checklist_desc['iconv'] = '为了UTF-8和其他语言环境之间的互相转换,必须安装iconv'; $lang->install_checklist_desc['gd'] = '为了使用图片转换功能,必须先得安装GD库'; $lang->install_checklist_xml = '安装XML库'; @@ -33,10 +33,6 @@ $lang->cmd_install_refresh_page = '刷新屏幕'; $lang->cmd_install_next = '开始安装'; $lang->cmd_ignore = '忽略'; $lang->cmd_recommended = '推荐'; -$lang->db_desc['mysqli'] = '使用MySQL或MariaDB。 如果支持,Rhymix将自动使用InnoDB。'; -$lang->db_desc['mysql'] = '使用MySQL或MariaDB。 如果支持,Rhymix将自动使用InnoDB。'; -$lang->db_desc['cubrid'] = '不支持CUBRID。'; -$lang->db_desc['mssql'] = '不支持Microsoft SQL Server。'; $lang->can_use_when_installed = '不是这个服务器上安装'; $lang->form_title = '数据库及管理员基本信息'; $lang->db_title = '输入数据库信息'; diff --git a/modules/install/lang/zh-TW.php b/modules/install/lang/zh-TW.php index a5d7d59de..40ba4c676 100644 --- a/modules/install/lang/zh-TW.php +++ b/modules/install/lang/zh-TW.php @@ -16,10 +16,10 @@ $lang->install_checklist_title['iconv'] = 'iconv / mbstring'; $lang->install_checklist_title['json'] = 'json'; $lang->install_checklist_title['gd'] = 'gd'; $lang->install_checklist_title['mcrypt'] = 'mcrypt / openssl'; -$lang->install_checklist_title['session'] = 'session.auto_start設置'; +$lang->install_checklist_title['session'] = 'session'; $lang->install_checklist_desc['permission'] = '[必須] Rhymix的資料夾或『./files』資料夾權限必須是『777』。'; $lang->install_checklist_desc['xml'] = '[必須] 必須要安裝『XML Library』,才能夠使用 XML 通訊。'; -$lang->install_checklist_desc['session'] = '[必須] 在『php.ini』中必須要設定『session.auto_start=0』,才能使用暫存功能'; +$lang->install_checklist_desc['session'] = '[必須] PHP會話必須可用,在『php.ini』中必須要設定『session.auto_start = off』,才能使用暫存功能'; $lang->install_checklist_desc['iconv'] = '安裝『iconv』,才能使 UTF-8 和其他語言文字作互相轉換。'; $lang->install_checklist_desc['gd'] = '安裝『GD Library』才可以使用圖片轉換功能。'; $lang->install_checklist_xml = '安裝 XML Library'; @@ -33,10 +33,6 @@ $lang->cmd_install_refresh_page = '刷新屏幕'; $lang->cmd_install_next = '開始進行安裝'; $lang->cmd_ignore = '忽略'; $lang->cmd_recommended = '推薦'; -$lang->db_desc['mysqli'] = '使用MySQL或MariaDB。 如果支持,Rhymix將自動使用InnoDB。'; -$lang->db_desc['mysql'] = '使用MySQL或MariaDB。 如果支持,Rhymix將自動使用InnoDB。'; -$lang->db_desc['cubrid'] = '不支持CUBRID。'; -$lang->db_desc['mssql'] = '不支持Microsoft SQL Server。'; $lang->can_use_when_installed = '不是這個服務器上安裝'; $lang->form_title = '輸入資料庫及管理員資訊'; $lang->db_title = '輸入資料庫資訊'; diff --git a/modules/install/ruleset/installFtpInfo.xml b/modules/install/ruleset/installFtpInfo.xml deleted file mode 100644 index 97604ca3f..000000000 --- a/modules/install/ruleset/installFtpInfo.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/modules/install/script/ko.install.php b/modules/install/script/ko.install.php index 3e2484f9f..5fc057482 100644 --- a/modules/install/script/ko.install.php +++ b/modules/install/script/ko.install.php @@ -9,119 +9,59 @@ $oMenuAdminController = getAdminController('menu'); // sitemap $sitemap = array( 'GNB' => array( - 'title' => 'Main menu', + 'title' => 'Main Menu', 'list' => array( array( - 'menu_name' => 'Welcome Page', + 'menu_name' => 'Welcome', 'module_type' => 'WIDGET', 'module_id' => 'index', ), array( - 'menu_name' => 'Board', + 'menu_name' => 'Free Board', 'module_type' => 'board', 'module_id' => 'board', - 'list' => array( - array( - 'menu_name' => 'SAMPLE 1', - 'is_shortcut' => 'Y', - 'shortcut_target' => '#', - 'list' => array( - array( - 'menu_name' => 'SAMPLE 1-1', - 'is_shortcut' => 'Y', - 'shortcut_target' => '#' - ), - ) - ), - array( - 'menu_name' => 'SAMPLE 2', - 'is_shortcut' => 'Y', - 'shortcut_target' => '#' - ), - array( - 'menu_name' => 'SAMPLE 3', - 'is_shortcut' => 'Y', - 'shortcut_target' => '#' - ), - ) ), array( - 'menu_name' => 'XEIcon', - 'module_type' => 'WIDGET', - 'module_id' => 'xeicon', + 'menu_name' => 'Q&A', + 'module_type' => 'board', + 'module_id' => 'qna', ), - ) + array( + 'menu_name' => 'Notice', + 'module_type' => 'board', + 'module_id' => 'notice', + ), + ), ), 'UNB' => array( - 'title' => 'Utility menu', + 'title' => 'Utility Menu', 'list' => array( array( 'menu_name' => 'Rhymix Official Site', 'is_shortcut' => 'Y', 'open_window' => 'Y', - 'shortcut_target' => 'https://www.rhymix.org/' + 'shortcut_target' => 'https://rhymix.org/', ), array( - 'menu_name' => 'GitHub', + 'menu_name' => 'Rhymix GitHub', 'is_shortcut' => 'Y', 'open_window' => 'Y', - 'shortcut_target' => 'https://github.com/rhymix' + 'shortcut_target' => 'https://github.com/rhymix', ), - ) + ), ), 'FNB' => array( 'title' => 'Footer Menu', 'list' => array( array( - 'menu_name' => 'Welcome Page', - 'is_shortcut' => 'Y', - 'shortcut_target' => 'index', - 'list' => array( - array( - 'menu_name' => 'SAMPLE 1', - 'is_shortcut' => 'Y', - 'shortcut_target' => '#' - ), - array( - 'menu_name' => 'SAMPLE 2', - 'is_shortcut' => 'Y', - 'shortcut_target' => '#' - ), - array( - 'menu_name' => 'SAMPLE 3', - 'is_shortcut' => 'Y', - 'shortcut_target' => '#' - ) - ), + 'menu_name' => 'Terms of Service', + 'module_type' => 'ARTICLE', + 'module_id' => 'terms', ), array( - 'menu_name' => 'Board', - 'is_shortcut' => 'Y', - 'shortcut_target' => 'board', - 'list' => array( - array( - 'menu_name' => 'SAMPLE 1', - 'is_shortcut' => 'Y', - 'shortcut_target' => '#' - ), - array( - 'menu_name' => 'SAMPLE 2', - 'is_shortcut' => 'Y', - 'shortcut_target' => '#' - ) - ) - ), - array( - 'menu_name' => 'XEIcon', - 'is_shortcut' => 'Y', - 'shortcut_target' => 'xeicon', - 'list' => array( - array( - 'menu_name' => 'SAMPLE 1', - 'is_shortcut' => 'Y', - 'shortcut_target' => '#' - ) - ) + 'menu_name' => 'Privacy Policy', + 'module_type' => 'ARTICLE', + 'module_id' => 'privacy', ), ), ), @@ -178,6 +118,9 @@ foreach($sitemap as $id => &$val) // create Layout //extra_vars init $extra_vars = new stdClass(); +$extra_vars->use_demo = 'Y'; +$extra_vars->use_ncenter_widget = 'Y'; +$extra_vars->content_fixed_width = 'Y'; $extra_vars->GNB = $sitemap['GNB']['menu_srl']; $extra_vars->UNB = $sitemap['UNB']['menu_srl']; $extra_vars->FNB = $sitemap['FNB']['menu_srl']; @@ -188,7 +131,6 @@ $args->site_srl = 0; $args->layout = 'xedition'; $args->title = 'XEDITION'; $args->layout_type = 'P'; - $oLayoutAdminController = getAdminController('layout'); $output = $oLayoutAdminController->insertLayout($args); if(!$output->toBool()) return $output; @@ -213,11 +155,9 @@ $args->extra_vars = serialize($extra_vars); $output = $oLayoutAdminController->updateLayout($args); if(!$output->toBool()) return $output; - -$siteDesignPath = _XE_PATH_.'files/site_design/'; +$siteDesignPath = RX_BASEDIR.'files/site_design/'; FileHandler::makeDir($siteDesignPath); - $designInfo = new stdClass(); $designInfo->layout_srl = $layout_srl; $designInfo->mlayout_srl = $mlayout_srl; @@ -268,10 +208,10 @@ $obj->nick_name = htmlspecialchars_decode($logged_info->nick_name); $obj->email_address = $logged_info->email_address; $obj->module_srl = $module_srl; -Context::set('version', __XE_VERSION__); +Context::set('version', RX_VERSION); $obj->title = 'Welcome to Rhymix'; -$obj->content = $oTemplateHandler->compile(_XE_PATH_ . 'modules/install/script/welcome_content', 'welcome_content'); +$obj->content = $oTemplateHandler->compile(RX_BASEDIR . 'modules/install/script/welcome_content', 'welcome_content'); $output = $oDocumentController->insertDocument($obj, true); if(!$output->toBool()) return $output; @@ -298,31 +238,12 @@ $domain_args->domain_srl = 0; $domain_args->index_module_srl = $module_srl; executeQuery('module.updateDomain', $domain_args); -// XEIcon page -$moduleInfo = $oModuleModel->getModuleInfoByMenuItemSrl($sitemap['GNB']['list'][2]['menu_srl']); -$xeicon_module_srl = $moduleInfo->module_srl; - -$xeicon_document_srl = array(); -for($i = 1; $i <=4; $i++) +// insert admin favorites +foreach(['advanced_mailer', 'ncenterlite'] as $module_name) { - unset($obj->document_srl); - $obj->title = "XEIcon ({$i})"; - $obj->content = $oTemplateHandler->compile(_XE_PATH_ . 'modules/install/script/xeicon_content', 'xeicon_content_ko_' . $i); - - $output = $oDocumentController->insertDocument($obj, true); - if(!$output->toBool()) return $output; - - $xeicon_document_srl[$i] = $output->get('document_srl'); + $oAdminController->_insertFavorite(0, $module_name); } -// save PageWidget -$oModuleController = getController('module'); /* @var $oModuleController moduleController */ -$module_info = $oModuleModel->getModuleInfoByModuleSrl($xeicon_module_srl); -$module_info->content = '
              '; -$output = $oModuleController->updateModule($module_info); -if(!$output->toBool()) return $output; - - // create menu cache $oMenuAdminController->makeXmlFile($menuSrl); diff --git a/modules/install/tpl/check_env.html b/modules/install/tpl/check_env.html index e2cc54949..f85de03aa 100644 --- a/modules/install/tpl/check_env.html +++ b/modules/install/tpl/check_env.html @@ -9,7 +9,7 @@ {$lang->install_checklist_title[$key]} - ({$phpversion}) + ({$phpversion}) OK @@ -20,6 +20,11 @@ {sprintf($lang->install_checklist_desc[$key], __XE_MIN_PHP_VERSION__)} + + {$lang->install_checklist_desc[$key]} + +
              {$lang->install_checklist_desc['free_domain_warning']} + {$lang->install_checklist_desc[$key]} diff --git a/modules/install/tpl/css/install.css b/modules/install/tpl/css/install.css index 8739cc3f0..9e223f02c 100644 --- a/modules/install/tpl/css/install.css +++ b/modules/install/tpl/css/install.css @@ -34,25 +34,13 @@ img, form, fieldset { border: 0; margin:0; padding: 0; } margin: 0; } .x #header h1 { - font: bold 54px/100% Cambria, Georgia, serif; color: #444; text-align: center; text-shadow: 2px 2px 3px rgba(128, 128, 128, 0.4); margin: 0 0 24px 0; } -.x #header h1 span.green { - color: #55a72f; -} -.x #header h1 span.brown { - color: #905a29; -} -.x #header h1 span.red { - color: #ef5350; -} -.x #header h1 span.colon { - color: #888888; - position: relative; - top: -4px; +.x #header img { + width: 180px; } .x #header h2 { font: normal 16px/110% Arial, sans-serif; @@ -72,7 +60,7 @@ img, form, fieldset { border: 0; margin:0; padding: 0; } background-color: #fff; border-radius: 2px; margin-bottom: 32px; - box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.18), 0px 2px 8px 0px rgba(0, 0, 0, 0.12); + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.18), 0 2px 8px 0 rgba(0, 0, 0, 0.12); } .x #progress { @@ -130,7 +118,7 @@ table tr td.error_description { text-align: center; color: #666; } } button, a.button { display: inline-block; - margin: 0px 4px; + margin: 0 4px; padding: 9px 18px 9px 18px; font-weight: bold !important; line-height: 15px; @@ -141,7 +129,7 @@ button, a.button { color: #fff; background-color: #1976d2; border-radius: 2px; - box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.12), 0px 2px 8px 0px rgba(0, 0, 0, 0.08); + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12), 0 2px 8px 0 rgba(0, 0, 0, 0.08); } button.grey, a.button.grey { background-color: #aaa; @@ -156,7 +144,7 @@ button.grey:hover, a.button.grey:hover { /* Content */ .x #content { clear: both; padding: 16px 16px 4px 16px; } -.x #content a { } + .x #content h2 { font-size: 20px; line-height: 140%; diff --git a/modules/install/tpl/db_config.html b/modules/install/tpl/db_config.html index 901e0e332..6ed0692f3 100644 --- a/modules/install/tpl/db_config.html +++ b/modules/install/tpl/db_config.html @@ -9,23 +9,7 @@ -
              - -
              - -
              -
              -

              {$lang->db_desc[$val->db_type]}

              +
              @@ -48,7 +32,7 @@
              -
              +

              {$lang->db_info_desc}
              {$lang->db_prefix_desc} diff --git a/modules/install/tpl/footer.html b/modules/install/tpl/footer.html index 049cfdd35..616b8799e 100644 --- a/modules/install/tpl/footer.html +++ b/modules/install/tpl/footer.html @@ -6,9 +6,8 @@

              diff --git a/modules/install/tpl/ftp.html b/modules/install/tpl/ftp.html deleted file mode 100644 index e5bfe8d61..000000000 --- a/modules/install/tpl/ftp.html +++ /dev/null @@ -1,56 +0,0 @@ - -
              - -
              -

              {$lang->install_progress_menu['ftp']}

              -
              - -
              - -
              -
              -
              - -
              - -
              -
              -
              - -
              - -
              -
              -
              - -
              - -
              -
              -
              - -
              - -
              -
              - -

              {$lang->msg_ftp_installed_realpath}: {_XE_PATH_}

              -

              - - -

              -
                - -

                {$lang->install_ftp_reason}

                -
                -
                - -
                - - -
                -
                - - diff --git a/modules/install/tpl/header.html b/modules/install/tpl/header.html index 26ca1dde9..2067af5b0 100644 --- a/modules/install/tpl/header.html +++ b/modules/install/tpl/header.html @@ -3,6 +3,6 @@
                diff --git a/modules/install/tpl/js/install.js b/modules/install/tpl/js/install.js index 76943a8a7..ccf2387b9 100644 --- a/modules/install/tpl/js/install.js +++ b/modules/install/tpl/js/install.js @@ -1,11 +1,5 @@ jQuery(function($){ $('.focus').focus(); - if($("#db_type").size()) { - $("#db_type").click(function() { - $("p.db_type").hide(); - $("p.db_type_" + $(this).val()).show(); - }).triggerHandler("click"); - } if($("#mod_rewrite_checking").size()) { var checking = $("#mod_rewrite_checking"); $.ajax({ diff --git a/modules/install/tpl/license_text.en.html b/modules/install/tpl/license_text.en.html index 6dd233ba0..0f19905f4 100644 --- a/modules/install/tpl/license_text.en.html +++ b/modules/install/tpl/license_text.en.html @@ -1,6 +1,6 @@

                - Copyright © Rhymix Developers and Contributors
                - Copyright © NAVER + Copyright © Poesis Inc. and Contributors
                + Copyright © NAVER & XEHub

                @@ -10,7 +10,7 @@

                diff --git a/modules/install/tpl/license_text.ko.html b/modules/install/tpl/license_text.ko.html index a734e70bf..fc1e40b0f 100644 --- a/modules/install/tpl/license_text.ko.html +++ b/modules/install/tpl/license_text.ko.html @@ -1,6 +1,6 @@

                - Copyright © Rhymix Developers and Contributors
                - Copyright © NAVER + Copyright © Poesis Inc. and Contributors
                + Copyright © NAVER & XEHub

                @@ -10,7 +10,7 @@

                diff --git a/modules/install/tpl/other_config.html b/modules/install/tpl/other_config.html index 493c76cb8..bb11b859c 100644 --- a/modules/install/tpl/other_config.html +++ b/modules/install/tpl/other_config.html @@ -50,9 +50,8 @@
                - - - + +
                diff --git a/modules/integration_search/conf/info.xml b/modules/integration_search/conf/info.xml index 29aae5734..40d670480 100644 --- a/modules/integration_search/conf/info.xml +++ b/modules/integration_search/conf/info.xml @@ -45,8 +45,8 @@ 將所選擇的模組當作搜尋對象進行搜尋。 所搜尋的模組中,如有私密文章時將不會被搜尋,因此請注意選擇。 - 1.7 - 2013-11-27 + RX_VERSION + RX_CORE service diff --git a/modules/integration_search/conf/module.xml b/modules/integration_search/conf/module.xml index 89e20d10a..78e0bed7e 100644 --- a/modules/integration_search/conf/module.xml +++ b/modules/integration_search/conf/module.xml @@ -2,11 +2,9 @@ - - + - diff --git a/modules/integration_search/integration_search.admin.controller.php b/modules/integration_search/integration_search.admin.controller.php index 7d2ab6bb8..e68659c61 100644 --- a/modules/integration_search/integration_search.admin.controller.php +++ b/modules/integration_search/integration_search.admin.controller.php @@ -25,14 +25,24 @@ class integration_searchAdminController extends integration_search { // Get configurations (using module model object) $oModuleModel = getModel('module'); - $config = $oModuleModel->getModuleConfig('integration_search'); + $config = $oModuleModel->getModuleConfig('integration_search') ?: new stdClass; $config = (object)get_object_vars($config); $config->skin = Context::get('skin'); $config->mskin = Context::get('mskin'); + $config->block_robots = Context::get('block_robots') === 'N' ? false : true; + $config->target_types = []; + $target_types = Context::get('target_types') ?? []; + foreach (['document', 'comment', 'multimedia', 'file'] as $type) + { + $config->target_types[$type] = in_array($type, $target_types); + } $config->target = Context::get('target'); $config->target_module_srl = Context::get('target_module_srl'); - if(!$config->target_module_srl) $config->target_module_srl = ''; + if (!$config->target_module_srl) + { + $config->target_module_srl = ''; + } $oModuleController = getController('module'); $output = $oModuleController->insertModuleConfig('integration_search', $config); @@ -55,13 +65,13 @@ class integration_searchAdminController extends integration_search // Get skin information (to check extra_vars) $skin_info = $oModuleModel->loadSkinInfo($this->module_path, $config->skin); - + // Check received variables (delete the basic variables such as mo, act, module_srl, page) $obj = Context::getRequestVars(); unset($obj->act); unset($obj->module_srl); unset($obj->page); - + // Separately handle if the extra_vars is an image type in the original skin_info if($skin_info->extra_vars) { @@ -113,8 +123,8 @@ class integration_searchAdminController extends integration_search $obj->{$vars->name} = $filename; } } - - // Serialize and save + + // Serialize and save $config->skin_vars = serialize($obj); $oModuleController = getController('module'); diff --git a/modules/integration_search/integration_search.admin.view.php b/modules/integration_search/integration_search.admin.view.php index 91136461a..09abc9148 100644 --- a/modules/integration_search/integration_search.admin.view.php +++ b/modules/integration_search/integration_search.admin.view.php @@ -55,7 +55,7 @@ class integration_searchAdminView extends integration_search // module_category and module combination if($module_categories) { foreach($mid_list as $module_srl => $module) { - $module_categories[$module->module_category_srl]->list[$module_srl] = $module; + $module_categories[$module->module_category_srl]->list[$module_srl] = $module; } } else { $module_categories[0]->list = $mid_list; @@ -67,7 +67,13 @@ class integration_searchAdminView extends integration_search $security->encodeHTML('skin_list..title', 'mskin_list..title'); // Sample Code - Context::set('sample_code', htmlspecialchars('
                ', ENT_COMPAT | ENT_HTML401, 'UTF-8', false) ); + Context::set('sample_code', escape( + '
                ' . PHP_EOL . + ' ' . PHP_EOL . + ' ' . PHP_EOL . + ' ' . PHP_EOL . + ' ' . PHP_EOL . + '
                ')); $this->setTemplateFile("index"); } @@ -83,7 +89,7 @@ class integration_searchAdminView extends integration_search $skin_info = $oModuleModel->loadSkinInfo($this->module_path, $this->config->skin); $skin_vars = unserialize($this->config->skin_vars); // value for skin_info extra_vars - if(count($skin_info->extra_vars)) + if(!empty($skin_info->extra_vars)) { foreach($skin_info->extra_vars as $key => $val) { diff --git a/modules/integration_search/integration_search.class.php b/modules/integration_search/integration_search.class.php index 93adb3d97..3d143ca29 100644 --- a/modules/integration_search/integration_search.class.php +++ b/modules/integration_search/integration_search.class.php @@ -24,20 +24,13 @@ class integration_search extends ModuleObject * * @return bool */ - function checkUpdate() + function checkUpdate() { - $oModuleModel = getModel('module'); - $config = $oModuleModel->getModuleConfig('integration_search'); - - if($config->skin) + if (!ModuleModel::getActionForward('IS')) { - $config_parse = explode('.', $config->skin); - if(count($config_parse) > 1) - { - $template_path = sprintf('./themes/%s/modules/integration_search/', $config_parse[0]); - if(is_dir($template_path)) return true; - } + return true; } + return false; } @@ -46,24 +39,12 @@ class integration_search extends ModuleObject * * @return Object */ - function moduleUpdate() + function moduleUpdate() { - $oModuleModel = getModel('module'); - $config = $oModuleModel->getModuleConfig('message'); - - if($config->skin) + if (!ModuleModel::getActionForward('IS')) { - $config_parse = explode('.', $config->skin); - if(count($config_parse) > 1) - { - $template_path = sprintf('./themes/%s/modules/integration_search/', $config_parse[0]); - if(is_dir($template_path)) - { - $config->skin = implode('|@|', $config_parse); - $oModuleController = getController('module'); - $oModuleController->updateModuleConfig('integration_search', $config); - } - } + $oModuleController = ModuleController::getInstance(); + $oModuleController->insertActionForward('integration_search', 'view', 'IS'); } } diff --git a/modules/integration_search/integration_search.mobile.php b/modules/integration_search/integration_search.mobile.php index 643e89310..51bace717 100644 --- a/modules/integration_search/integration_search.mobile.php +++ b/modules/integration_search/integration_search.mobile.php @@ -1,20 +1,6 @@ */ -/** - * The view class of the integration_search module - * - * @author MinSoo Kim - */ class integration_searchMobile extends integration_searchView { - /** - * Search Result - * - * @return Object - */ - function IS() - { - parent::IS(); - } -} \ No newline at end of file + +} diff --git a/modules/integration_search/integration_search.model.php b/modules/integration_search/integration_search.model.php index 351802167..6bb0d3cb3 100644 --- a/modules/integration_search/integration_search.model.php +++ b/modules/integration_search/integration_search.model.php @@ -32,37 +32,47 @@ class integration_searchModel extends module { if(!is_array($module_srls_list)) { - $module_srls_list = $module_srl_list ? explode(',', $module_srls_list) : array(); + $module_srls_list = $module_srls_list ? explode(',', $module_srls_list) : array(); } $module_srls_list = array_map('intval', $module_srls_list); - $accessible_modules = array_keys(getModel('module')->getAccessibleModuleList()); + $accessible_modules = getModel('module')->getAccessibleModuleList(); $args = new stdClass(); if($target == 'exclude') { - $args->module_srl = $accessible_modules; + $args->module_srl = array_keys($accessible_modules); $args->exclude_module_srl = $module_srls_list; } else { - $args->module_srl = array_intersect($module_srls_list, $accessible_modules); + $args->module_srl = array_intersect($module_srls_list, array_keys($accessible_modules)); $args->exclude_module_srl = array(0); // exclude 'trash' } $args->module_srl[] = 0; - + $args->page = $page; $args->list_count = $list_count; - $args->page_count = 10; + $args->page_count = Mobile::isFromMobilePhone() ? 5 : 10; $args->search_target = $search_target; $args->search_keyword = $search_keyword; $args->sort_index = 'list_order'; $args->order_type = 'asc'; $args->statusList = array('PUBLIC'); if(!$args->module_srl) unset($args->module_srl); - // Get a list of documents - $oDocumentModel = getModel('document'); + if(!$args->exclude_module_srl) unset($args->exclude_module_srl); - return $oDocumentModel->getDocumentList($args); + // Get a list of documents + $document_list = DocumentModel::getDocumentList($args); + + // Replace title with module title if it belongs to a page + foreach ($document_list->data as $document) + { + if (isset($accessible_modules[$document->get('module_srl')]) && $accessible_modules[$document->get('module_srl')]->module === 'page') + { + $document->add('title', $accessible_modules[$document->get('module_srl')]->browser_title); + } + } + return $document_list; } /** @@ -80,7 +90,7 @@ class integration_searchModel extends module { if(!is_array($module_srls_list)) { - $module_srls_list = $module_srl_list ? explode(',', $module_srls_list) : array(); + $module_srls_list = $module_srls_list ? explode(',', $module_srls_list) : array(); } $module_srls_list = array_map('intval', $module_srls_list); $accessible_modules = array_keys(getModel('module')->getAccessibleModuleList()); @@ -100,68 +110,20 @@ class integration_searchModel extends module $args->page = $page; $args->list_count = $list_count; - $args->page_count = 10; + $args->page_count = Mobile::isFromMobilePhone() ? 5 : 10; $args->search_target = 'content'; $args->search_keyword = $search_keyword; $args->is_secret = 'N'; $args->sort_index = 'list_order'; $args->order_type = 'asc'; $args->statusList = array(1); - // Get a list of documents + $args->document_statusList = array('PUBLIC'); + if(!$args->module_srl) unset($args->module_srl); + if(!$args->exclude_module_srl) unset($args->exclude_module_srl); + + // Get a list of comments $oCommentModel = getModel('comment'); - $output = $oCommentModel->getTotalCommentList($args); - if(!$output->toBool()|| !$output->data) return $output; - return $output; - } - - /** - * Search trackbacks - * - * @param string $target choose target. exclude or include for $module_srls_list - * @param string $module_srls_list module_srl list to string type. ef - 102842,59392,102038 - * @param string $search_target Target - * @param string $search_keyword Keyword - * @param integer $page page of page navigation - * @param integer $list_count list count of page navigation - * - * @return Object output trackback list - */ - function getTrackbacks($target, $module_srls_list, $search_target = "title", $search_keyword, $page=1, $list_count = 20) - { - $oTrackbackModel = getAdminModel('trackback'); - if(!$oTrackbackModel) return new BaseObject(); - - if(!is_array($module_srls_list)) - { - $module_srls_list = $module_srl_list ? explode(',', $module_srls_list) : array(); - } - $module_srls_list = array_map('intval', $module_srls_list); - $accessible_modules = array_keys(getModel('module')->getAccessibleModuleList()); - - $args = new stdClass(); - if($target == 'exclude') - { - $args->module_srl = $accessible_modules; - $args->exclude_module_srl = $module_srls_list; - } - else - { - $args->module_srl = array_intersect($module_srls_list, $accessible_modules); - $args->exclude_module_srl = array(0); // exclude 'trash' - } - $args->module_srl[] = 0; - - $args->page = $page; - $args->list_count = $list_count; - $args->page_count = 10; - $args->search_target = $search_target; - $args->search_keyword = $search_keyword; - $args->sort_index = 'list_order'; - $args->order_type = 'asc'; - // Get a list of documents - $output = $oTrackbackModel->getTotalTrackbackList($args); - if(!$output->toBool()|| !$output->data) return $output; - return $output; + return $oCommentModel->getTotalCommentList($args); } /** @@ -180,7 +142,7 @@ class integration_searchModel extends module { if(!is_array($module_srls_list)) { - $module_srls_list = $module_srl_list ? explode(',', $module_srls_list) : array(); + $module_srls_list = $module_srls_list ? explode(',', $module_srls_list) : array(); } $accessible_modules = array_keys(getModel('module')->getAccessibleModuleList()); @@ -196,10 +158,10 @@ class integration_searchModel extends module $args->exclude_module_srl = array(0); // exclude 'trash' } $args->module_srl[] = 0; - + $args->page = $page; $args->list_count = $list_count; - $args->page_count = 10; + $args->page_count = Mobile::isFromMobilePhone() ? 5 : 10; $args->search_target = 'filename'; $args->search_keyword = $search_keyword; $args->sort_index = 'files.file_srl'; @@ -207,44 +169,40 @@ class integration_searchModel extends module $args->isvalid = 'Y'; $args->direct_download = $direct_download=='Y'?'Y':'N'; $args->exclude_secret = 'Y'; - // Get a list of documents - $oFileAdminModel = getAdminModel('file'); + + // Get a list of files + $oFileAdminModel = FileAdminModel::getInstance(); $output = $oFileAdminModel->getFileList($args); if(!$output->toBool() || !$output->data) return $output; $list = array(); foreach($output->data as $key => $val) { - $obj = new stdClass; + $obj = new \Rhymix\Modules\Integration_Search\Models\FileSearchResult; + $obj->file_srl = $val->file_srl; $obj->filename = $val->source_filename; + $obj->uploaded_filename = $val->uploaded_filename; $obj->download_count = $val->download_count; - if(substr($val->download_url,0,2)=='./') $val->download_url = substr($val->download_url,2); - $obj->download_url = Context::getRequestUri().$val->download_url; + $obj->download_url = \RX_BASEURL . preg_replace('!^\.\/!', '', $val->download_url); $obj->target_srl = $val->upload_target_srl; $obj->file_size = $val->file_size; + // Images - if(preg_match('/\.(jpg|jpeg|gif|png)$/i', $val->source_filename)) + if(preg_match('/\.(jpe?g|gif|png|bmp|webp)$/i', $val->source_filename)) { $obj->type = 'image'; - - $thumbnail_path = sprintf('files/thumbnails/%s',getNumberingPath($val->file_srl, 3)); - if(!is_dir($thumbnail_path)) FileHandler::makeDir($thumbnail_path); - $thumbnail_file = sprintf('%s%dx%d.%s.jpg', $thumbnail_path, 120, 120, 'crop'); - $thumbnail_url = Context::getRequestUri().$thumbnail_file; - if(!file_exists($thumbnail_file)) FileHandler::createImageFile($val->uploaded_filename, $thumbnail_file, 120, 120, 'jpg', 'crop'); - $obj->src = sprintf('%s', $thumbnail_url, htmlspecialchars($obj->filename, ENT_COMPAT | ENT_HTML401, 'UTF-8', false), 120, 120); - // Videos } elseif(Rhymix\Framework\Filters\FilenameFilter::isDirectDownload($val->source_filename)) { $obj->type = 'multimedia'; - $obj->src = sprintf('', $val->uploaded_filename); - // Others + if ($val->thumbnail_filename) + { + $obj->video_thumbnail_url = \RX_BASEURL . preg_replace('!^\.\/!', '', $val->thumbnail_filename); + } } else { $obj->type = 'binary'; - $obj->src = ''; } $list[] = $obj; @@ -316,6 +274,17 @@ class integration_searchModel extends module { return $this->_getFiles($target, $module_srls_list, $search_keyword, $page, $list_count, 'N'); } + + /** + * Search trackbacks + * + * @deprecated + * @return BaseObject + */ + function getTrackbacks() + { + return new BaseObject(); + } } /* End of file integration_search.model.php */ /* Location: ./modules/integration_search/integration_search.model.php */ diff --git a/modules/integration_search/integration_search.view.php b/modules/integration_search/integration_search.view.php index 2e3660201..8de0f1f36 100644 --- a/modules/integration_search/integration_search.view.php +++ b/modules/integration_search/integration_search.view.php @@ -23,7 +23,7 @@ class integration_searchView extends integration_search * * @return void */ - function init() + public function init() { } @@ -32,7 +32,7 @@ class integration_searchView extends integration_search * * @return Object */ - function IS() + public function IS() { $oFile = getClass('file'); $oModuleModel = getModel('module'); @@ -47,15 +47,24 @@ class integration_searchView extends integration_search $this->setRedirectUrl($redirect_url); return; } - + // Check permissions if(!$this->grant->access) { throw new Rhymix\Framework\Exceptions\NotPermitted; } - - // Set skin path + + // Block robots $config = $oModuleModel->getModuleConfig('integration_search') ?: new stdClass; + if (!isset($config->block_robots) || $config->block_robots !== false) + { + if (isCrawler()) + { + throw new Rhymix\Framework\Exceptions\NotPermitted; + } + } + + // Set skin path if(ends_with('Mobile', get_class($this), false)) { if(!$config->mskin || $config->mskin === '/USE_RESPONSIVE/') @@ -91,7 +100,7 @@ class integration_searchView extends integration_search $config->skin = 'default'; } } - + $this->setTemplatePath($template_path); $skin_vars = ($config->skin_vars) ? unserialize($config->skin_vars) : new stdClass; Context::set('module_info', $skin_vars); @@ -109,26 +118,42 @@ class integration_searchView extends integration_search // 검색 대상을 지정하지 않았을 때 검색 제한 if($target === 'include' && !count($module_srl_list)) { - throw new Rhymix\Framework\Exception('msg_not_enabled'); + throw new Rhymix\Framework\Exception('msg_admin_not_enabled'); } // Set a variable for search keyword $is_keyword = Context::get('is_keyword'); + // As the variables from GET or POST will be escaped by setRequestArguments method at Context class, the double_escape variable should be "FALSE", and also the escape function might be useful when this method was called from the other way (for not escaped keyword). + $is_keyword = escape(trim(utf8_normalize_spaces($is_keyword)), false); + if (mb_strlen($is_keyword, 'UTF-8') > 250) + { + $is_keyword = mb_substr($is_keyword, 0, 250); + } + // Set page variables $page = (int)Context::get('page'); if(!$page) $page = 1; + + // Set page title + $title = config('seo.subpage_title') ?: '$SITE_TITLE - $SUBPAGE_TITLE'; + Context::setBrowserTitle($title, array( + 'site_title' => Context::getSiteTitle(), + 'site_subtitle' => Context::getSiteSubtitle(), + 'subpage_title' => lang('cmd_search') . ': ' . $is_keyword, + 'page' => $page, + )); + // Search by search tab $where = Context::get('where'); + Context::set('where', $where); + $target_types = $config->target_types ?? ['document' => true, 'comment' => true, 'multimedia' => true, 'file' => true]; + Context::set('target_types', $target_types); + // Create integration search model object if($is_keyword) { - $oIS = getModel('integration_search'); - $oTrackbackModel = getAdminModel('trackback'); - Context::set('trackback_module_exist', true); - if(!$oTrackbackModel) - { - Context::set('trackback_module_exist', false); - } + $oIS = integration_searchModel::getInstance(); + Context::set('trackback_module_exist', false); switch($where) { @@ -136,41 +161,87 @@ class integration_searchView extends integration_search $search_target = Context::get('search_target'); if(!in_array($search_target, array('title','content','title_content','tag'))) $search_target = 'title_content'; Context::set('search_target', $search_target); - - $output = $oIS->getDocuments($target, $module_srl_list, $search_target, $is_keyword, $page, 10); + if ($target_types['document']) + { + $output = $oIS->getDocuments($target, $module_srl_list, $search_target, $is_keyword, $page, 10); + } + else + { + $output = new BaseObject; + } Context::set('output', $output); $this->setTemplateFile("document", $page); break; case 'comment' : - $output = $oIS->getComments($target, $module_srl_list, $is_keyword, $page, 10); + if ($target_types['comment']) + { + $output = $oIS->getComments($target, $module_srl_list, $is_keyword, $page, 10); + } + else + { + $output = new BaseObject; + } Context::set('output', $output); $this->setTemplateFile("comment", $page); break; - case 'trackback' : - $search_target = Context::get('search_target'); - if(!in_array($search_target, array('title','url','blog_name','excerpt'))) $search_target = 'title'; - Context::set('search_target', $search_target); - - $output = $oIS->getTrackbacks($target, $module_srl_list, $search_target, $is_keyword, $page, 10); - Context::set('output', $output); - $this->setTemplateFile("trackback", $page); - break; case 'multimedia' : - $output = $oIS->getImages($target, $module_srl_list, $is_keyword, $page,20); + if ($target_types['multimedia']) + { + $output = $oIS->getImages($target, $module_srl_list, $is_keyword, $page,20); + } + else + { + $output = new BaseObject; + } Context::set('output', $output); $this->setTemplateFile("multimedia", $page); break; case 'file' : - $output = $oIS->getFiles($target, $module_srl_list, $is_keyword, $page, 20); + if ($target_types['file']) + { + $output = $oIS->getFiles($target, $module_srl_list, $is_keyword, $page, 20); + } + else + { + $output = new BaseObject; + } Context::set('output', $output); $this->setTemplateFile("file", $page); break; default : - $output['document'] = $oIS->getDocuments($target, $module_srl_list, 'title_content', $is_keyword, $page, 5); - $output['comment'] = $oIS->getComments($target, $module_srl_list, $is_keyword, $page, 5); - $output['trackback'] = $oIS->getTrackbacks($target, $module_srl_list, 'title', $is_keyword, $page, 5); - $output['multimedia'] = $oIS->getImages($target, $module_srl_list, $is_keyword, $page, 5); - $output['file'] = $oIS->getFiles($target, $module_srl_list, $is_keyword, $page, 5); + if ($target_types['document']) + { + $output['document'] = $oIS->getDocuments($target, $module_srl_list, 'title_content', $is_keyword, $page, 5); + } + else + { + $output['document'] = new BaseObject; + } + if ($target_types['comment']) + { + $output['comment'] = $oIS->getComments($target, $module_srl_list, $is_keyword, $page, 5); + } + else + { + $output['comment'] = new BaseObject; + } + if ($target_types['multimedia']) + { + $output['multimedia'] = $oIS->getImages($target, $module_srl_list, $is_keyword, $page, 5); + } + else + { + $output['multimedia'] = new BaseObject; + } + if ($target_types['file']) + { + $output['file'] = $oIS->getFiles($target, $module_srl_list, $is_keyword, $page, 5); + } + else + { + $output['file'] = new BaseObject; + } + $output['trackback'] = new BaseObject; Context::set('search_result', $output); Context::set('search_target', 'title_content'); $this->setTemplateFile("index", $page); diff --git a/modules/integration_search/lang/en.php b/modules/integration_search/lang/en.php index bdf9c0992..a8021dffe 100644 --- a/modules/integration_search/lang/en.php +++ b/modules/integration_search/lang/en.php @@ -9,7 +9,10 @@ $lang->msg_not_enabled = 'The integrated search is not available.'; $lang->msg_admin_not_enabled = 'The integrated search is not available. Please select a target module in integrated search settings.'; $lang->is_result_text = 'There are %d result(s) for \'%s\''; $lang->multimedia = 'Images/Video'; -$lang->include_search_target = 'Search for selected modules'; +$lang->integration_search_block_robots = 'Block Robots'; +$lang->integration_search_target_types = 'Search Types'; +$lang->integration_search_target_modules = 'Search Modules'; +$lang->include_search_target = 'Search selected modules only'; $lang->exclude_search_target = 'Exclude selected modules from search'; $lang->is_search_option['document']['title_content'] = 'Subject+Content'; $lang->is_search_option['document']['title'] = 'Subject'; diff --git a/modules/integration_search/lang/ko.php b/modules/integration_search/lang/ko.php index 65bf21697..d36f92c45 100644 --- a/modules/integration_search/lang/ko.php +++ b/modules/integration_search/lang/ko.php @@ -9,8 +9,11 @@ $lang->msg_not_enabled = '통합 검색을 사용할 수 없습니다.'; $lang->msg_admin_not_enabled = '통합 검색을 사용할 수 없습니다. 통합검색 설정에서 대상 모듈을 선택하세요.'; $lang->is_result_text = '\'%s\'에 대한 검색결과 %d건'; $lang->multimedia = '이미지/동영상'; -$lang->include_search_target = '선택된 대상만 검색'; -$lang->exclude_search_target = '선택된 대상을 검색에서 제외'; +$lang->integration_search_block_robots = '로봇 접근 차단'; +$lang->integration_search_target_types = '검색 대상 종류'; +$lang->integration_search_target_modules = '검색 대상 페이지'; +$lang->include_search_target = '선택한 모듈만 검색'; +$lang->exclude_search_target = '선택한 모듈을 검색에서 제외'; $lang->is_search_option['document']['title_content'] = '제목+내용'; $lang->is_search_option['document']['title'] = '제목'; $lang->is_search_option['document']['content'] = '내용'; diff --git a/modules/integration_search/models/FileSearchResult.php b/modules/integration_search/models/FileSearchResult.php new file mode 100644 index 000000000..a047cc298 --- /dev/null +++ b/modules/integration_search/models/FileSearchResult.php @@ -0,0 +1,120 @@ +type !== 'image') + { + return ''; + } + + $thumbnail_path = sprintf('files/thumbnails/%s', getNumberingPath($this->file_srl, 3)); + if(!is_dir($thumbnail_path)) + { + FileHandler::makeDir($thumbnail_path); + } + $thumbnail_file = sprintf('%s%dx%d.%s.jpg', $thumbnail_path, $width, $height ?: $width, $type); + $thumbnail_url = \RX_BASEURL . $thumbnail_file; + if (!file_exists($thumbnail_file)) + { + FileHandler::createImageFile($this->uploaded_filename, $thumbnail_file, $width, $height ?: $width, 'jpg', $type, 50); + } + return $thumbnail_url; + } + + /** + * Display video. + * + * @param int $width + * @param int $height + * @return string + */ + public function displayVideo(int $width = 120, int $height = 0): string + { + if ($this->type !== 'multimedia') + { + return ''; + } + + $options = new \stdClass; + if ($this->video_thumbnail_url) + { + $options->thumbnail = $this->video_thumbnail_url; + } + + return vsprintf('', [ + json_encode(\RX_BASEURL . preg_replace('!^\.\/!', '', $this->uploaded_filename)), + $width, + $height ?: $width, + json_encode($options), + ]); + } + + /** + * Magic method to generate the 'src' attribute for backward compatibility. + * + * For images, it returns a 120x120 thumbnail. + * For videos, it returns a 80x80 preview. + * For other types of files, this method returns an empty string. + */ + public function __get(string $key) + { + if ($key === 'src') + { + if ($this->type === 'image') + { + return vsprintf('%s', [ + $this->getThumbnail(120, 120), + escape($this->filename, false), + ]); + } + elseif ($this->type === 'multimedia') + { + return $this->displayVideo(80, 80); + } + else + { + return ''; + } + } + else + { + return null; + } + } +} diff --git a/modules/integration_search/skins/default/comment.html b/modules/integration_search/skins/default/comment.html index 4fe0c3979..b457db1d8 100644 --- a/modules/integration_search/skins/default/comment.html +++ b/modules/integration_search/skins/default/comment.html @@ -2,7 +2,7 @@

                {$lang->comment} ({number_format($output->total_count)})

                - + {$lang->msg_no_result}
                  @@ -21,10 +21,10 @@
                - + {$lang->msg_no_result} @@ -37,10 +37,10 @@ - + {$lang->msg_no_result} diff --git a/modules/integration_search/skins/misol_town/file.html b/modules/integration_search/skins/misol_town/file.html index a7a0383b5..659c60092 100644 --- a/modules/integration_search/skins/misol_town/file.html +++ b/modules/integration_search/skins/misol_town/file.html @@ -10,7 +10,7 @@
                - + {$lang->msg_no_result} diff --git a/modules/integration_search/skins/misol_town/multimedia.html b/modules/integration_search/skins/misol_town/multimedia.html index 413424325..92734d310 100644 --- a/modules/integration_search/skins/misol_town/multimedia.html +++ b/modules/integration_search/skins/misol_town/multimedia.html @@ -9,7 +9,7 @@
                - + {$lang->msg_no_result} diff --git a/modules/integration_search/skins/misol_town/trackback.html b/modules/integration_search/skins/misol_town/trackback.html index b7d30f5f4..7743d6d3b 100644 --- a/modules/integration_search/skins/misol_town/trackback.html +++ b/modules/integration_search/skins/misol_town/trackback.html @@ -16,7 +16,7 @@ - + {$lang->msg_no_result} diff --git a/modules/integration_search/tpl/index.html b/modules/integration_search/tpl/index.html index 8dc567fc7..3749b59c2 100644 --- a/modules/integration_search/tpl/index.html +++ b/modules/integration_search/tpl/index.html @@ -13,7 +13,7 @@
                - +

                {$lang->about_sample_code}

                @@ -34,7 +34,35 @@
                - + +
                + + +
                +
                +
                + +
                + + + + +
                +
                +
                +
                - + +
                @@ -33,9 +33,10 @@ diff --git a/modules/krzip/tpl/template.epostapi.html b/modules/krzip/tpl/template.epostapi.html index c001af6c3..f6835d723 100644 --- a/modules/krzip/tpl/template.epostapi.html +++ b/modules/krzip/tpl/template.epostapi.html @@ -13,7 +13,8 @@
                - + +
                @@ -29,7 +30,8 @@ diff --git a/modules/krzip/tpl/template.postcodify.html b/modules/krzip/tpl/template.postcodify.html index 71a5ac6a7..c8b1a2e2c 100644 --- a/modules/krzip/tpl/template.postcodify.html +++ b/modules/krzip/tpl/template.postcodify.html @@ -14,7 +14,8 @@
                - + +
                @@ -30,7 +31,8 @@ diff --git a/modules/layout/conf/info.xml b/modules/layout/conf/info.xml index a4386ef26..2b1a5aa89 100644 --- a/modules/layout/conf/info.xml +++ b/modules/layout/conf/info.xml @@ -18,8 +18,8 @@ Этот модуль служит для создания/управления лейаутами. 建立/管理版面的模組。 Bu modül yerleşim düzenleri(layout) oluşturmak ve yönetmek içindir. - 1.7 - 2013-11-27 + RX_VERSION + RX_CORE construction diff --git a/modules/layout/conf/module.xml b/modules/layout/conf/module.xml index 60b2ea8f3..b5278c856 100644 --- a/modules/layout/conf/module.xml +++ b/modules/layout/conf/module.xml @@ -5,7 +5,7 @@ - + @@ -14,11 +14,10 @@ - + - - + diff --git a/modules/layout/lang/en.php b/modules/layout/lang/en.php index f7507143f..ddc511f3e 100644 --- a/modules/layout/lang/en.php +++ b/modules/layout/lang/en.php @@ -19,7 +19,7 @@ $lang->about_downloaded_layouts = 'List of downloaded layouts'; $lang->about_title = 'Please enter the title that is easy to verify when connecting to the page'; $lang->about_not_apply_menu = 'Layouts of all pages connected via menu will be changed en bloc by checking this option.'; $lang->about_layout = 'Layout module helps you create the site\'s layout easily. By using layout setting and menu connection, website\'s completed shape will be displayed with various modules. Layouts which you cannot delete are the default layouts of blogs or other moduels, thus you have to delete them from their setting pages.'; -$lang->about_layout_code = 'It will be applied to the service when you save the layout code after editing it. Please first preview your code and then save it. You can refer grammar of XE\'s template from XE Template.'; +$lang->about_layout_code = 'It will be applied to the service when you save the layout code after editing it. Please first preview your code and then save it. You can refer the syntax of Rhymix\'s template from Rhymix Template Manual (Korean).'; $lang->layout_export = 'Export'; $lang->layout_btn_export = 'Download My Layout'; $lang->about_layout_export = 'Export currently edited layout.'; @@ -80,12 +80,17 @@ $lang->layout_manager['51'] = 'Reset'; $lang->layout_manager['52'] = 'Text'; $lang->layout_manager['53'] = 'Font'; $lang->layout_manager['54'] = 'Font Color'; +$lang->layout_editing_deprecated_p1 = 'Layout editing is no longer supported. It is allowed for a limited time only for layouts that have already been edited, but this allowance may be removed completely in the future.'; +$lang->layout_editing_deprecated_p2 = 'Layout editing is supported for a limited time only for layouts that have already been edited.
                Using this screen to edit soure code is not recommended.
                Please use FTP or a code editor to edit the HTML and CSS of the original layout source code.'; +$lang->layout_editing_deprecated_p3 = 'For your information, edited source code is currently stored in the following files.'; +$lang->layout_editing_deprecated_p4 = 'The original source code is stored in the following file, as well as any file included/referenced therein.'; +$lang->layout_editing_deprecated_p5 = 'Caution: Clicking Reset will roll back the layout source code to the original. You cannot return to this screen afterward.
                Please back up the edited source code below before clicking Reset.'; +$lang->layout_reset_confirmation = 'Do you want to reset the layout source code to the original state?'; $lang->layout_image_repository = 'Layout Repository'; $lang->about_layout_image_repository = 'You can upload image/flash files for the selected layout. They will be included in exports.'; -$lang->msg_layout_image_target = 'Only gif, png, jpg, swf, flv files are allowed'; +$lang->msg_layout_image_target = 'Only gif, jpg, png, svg, webp files are allowed.'; $lang->layout_migration = 'Layout Migration'; -$lang->about_layout_migration = 'You can export or import the editted layout as a tar file. -(So far only FaceOff supports exports/imports)'; +$lang->about_layout_migration = 'You can export or import the editted layout as a tar file. (So far only FaceOff supports exports/imports)'; $lang->about_faceoff['title'] = 'Rhymix FaceOff Layout Manager'; $lang->about_faceoff['description'] = 'FaceOff Layout Manager willl help you design layout on the web easily.
                Please design your own layout with components and functions as shown below.'; $lang->about_faceoff['layout'] = 'FaceOff has HTML structure as above.
                You can arrange/align with CSS, or use Style to design.
                You can add widget from Extension(e1, e2), Neck and Knee.
                Also Body, Layout, Header, Body, Footer can designed by Style, and Content will display content.'; @@ -109,3 +114,9 @@ $lang->faceoff_migration['5'] = 'Upload to ./layouts through FTP.'; $lang->faceoff_migration['6'] = 'Insert new layout from installed layout. Make sure to check if the path of layout is the same as the uploaded path. You need to set the layout including logo images.'; $lang->msg_empty_origin_layout = 'Original layout is not exist'; $lang->msg_empty_target_layout = 'Target layout is not assigned'; +$lang->msg_at_least_one_layout = 'You cannot delete this copy. At least one layout copy of this layout should be remained.'; +$lang->use_site_default_layout = 'Use the default layout of the website'; +$lang->use_responsive_pc_layout = 'Use the same responsive layout as the PC'; +$lang->msg_unabled_preview = 'You can\'t preview because there is no page with this menu type.'; +$lang->article_preview_title = 'Title of the document'; +$lang->article_preview_content = 'Welcome. Nice to meet you.'; diff --git a/modules/layout/lang/ko.php b/modules/layout/lang/ko.php index a59dd3b89..339c48ab6 100644 --- a/modules/layout/lang/ko.php +++ b/modules/layout/lang/ko.php @@ -19,7 +19,7 @@ $lang->about_downloaded_layouts = '다운로드되어 있는 레이아웃 목록 $lang->about_title = '페이지에 연결 시 쉽게 구분할 수 있는 제목을 입력해주세요.'; $lang->about_not_apply_menu = '지정한 메뉴에 연결된 모든 페이지의 레이아웃을 현재 레이아웃으로 지정합니다.'; $lang->about_layout = '사이트의 레이아웃을 쉽게 만들 수 있도록 도와줍니다. 레이아웃 설정과 메뉴의 연결을 통해서 완성된 사이트의 모습으로 보여줄 수 있도록 합니다. 삭제나 수정이 불가능한 레이아웃은 페이지에 포함된 레이아웃이므로 해당 페이지에서 설정해야 합니다.'; -$lang->about_layout_code = '아래 레이아웃의 코드를 직접 수정 후 저장하면 서비스에 반영이 됩니다. 꼭 미리보기를 한 후에 저장을 하세요. XE의 템플릿 문법은 XE 템플릿 을 참고하면 됩니다.'; +$lang->about_layout_code = '아래 레이아웃의 코드를 직접 수정 후 저장하면 서비스에 반영이 됩니다. 꼭 미리보기를 한 후에 저장을 하세요. Rhymix의 템플릿 문법은 테마 제작 매뉴얼을 참고하면 됩니다.'; $lang->layout_export = '내보내기'; $lang->layout_btn_export = '내 레이아웃 다운로드'; $lang->about_layout_export = '현재 수정된 레이아웃을 내보내기를 합니다.'; @@ -80,12 +80,17 @@ $lang->layout_manager['51'] = '초기화'; $lang->layout_manager['52'] = '글자'; $lang->layout_manager['53'] = '글자 폰트'; $lang->layout_manager['54'] = '글자 색'; +$lang->layout_editing_deprecated_p1 = '레이아웃 편집 기능은 더이상 제공하지 않습니다. 이미 편집된 레이아웃에 한하여 편집을 허용하나, 추후 완전히 제거될 수 있습니다.'; +$lang->layout_editing_deprecated_p2 = '레이아웃 편집 기능은 이미 편집된 레이아웃에 한하여 일시적으로 제공됩니다.
                이 화면에서 레이아웃 소스를 직접 수정하는 것은 권장하지 않습니다.
                FTP 또는 에디터를 사용하여 원본 레이아웃의 HTML, CSS 소스 코드를 직접 편집하시기 바랍니다.'; +$lang->layout_editing_deprecated_p3 = '편집된 소스 코드는 아래의 경로에 저장되어 있으니 참고하십시오.'; +$lang->layout_editing_deprecated_p4 = '원본 소스 코드는 아래의 경로에 있으며, 그곳에서 인클루드 또는 참조된 다른 파일이 더 있을 수 있습니다.'; +$lang->layout_editing_deprecated_p5 = '주의: 초기화를 클릭하면 편집된 소스 코드가 원본으로 대체되며, 이 화면으로 다시 돌아올 수 없습니다.
                초기화하기 전에 편집된 소스 코드를 백업해 두시기 바랍니다.'; +$lang->layout_reset_confirmation = '레이아웃 소스를 원본 상태로 초기화하시겠습니까?'; $lang->layout_image_repository = '레이아웃 파일 저장소'; $lang->about_layout_image_repository = '선택한 레이아웃에 사용될 이미지/플래시파일 등을 올릴 수 있습니다. 내보내기에 같이 포함 됩니다.'; -$lang->msg_layout_image_target = 'gif, png, jpg, swf, flv파일만 가능합니다.'; +$lang->msg_layout_image_target = 'gif, jpg, png, svg, webp 파일만 가능합니다.'; $lang->layout_migration = '레이아웃 내보내기/들이기'; -$lang->about_layout_migration = '수정된 레이아웃을 tar 파일로 내보내거나 tar 파일로 저장된 것을 불러올 수 있습니다 -(아직은 faceOff레이아웃만 내보내기/들이기가 됩니다.)'; +$lang->about_layout_migration = '수정된 레이아웃을 tar 파일로 내보내거나 tar 파일로 저장된 것을 불러올 수 있습니다. (아직은 faceOff레이아웃만 내보내기/들이기가 됩니다.)'; $lang->about_faceoff['title'] = 'Rhymix FaceOff Layout 관리자'; $lang->about_faceoff['description'] = 'FaceOff Layout관리자로 웹상에서 쉽게 레이아웃을 꾸밀 수 있습니다.
                아래 그림을 보고 구성요소와 기능을 이용하여 원하는 레이아웃을 만드세요.'; $lang->about_faceoff['layout'] = 'FaceOff는 위와 같은 HTML 구조로 되어 있습니다.
                이 구조에서 CSS를 이용하여 형태/배열/정렬을 할 수 있고 또 Style을 이용하여 꾸밀 수 있습니다.
                위젯 추가는 Extension(e1, e2)과 Neck, Knee에서 가능합니다.
                이 외 Body, Layout, Header, Body, Footer는 Style을 꾸밀 수 있고 Content는 내용이 출력됩니다.'; diff --git a/modules/layout/layout.admin.controller.php b/modules/layout/layout.admin.controller.php index 920fc2d12..6587cedb6 100644 --- a/modules/layout/layout.admin.controller.php +++ b/modules/layout/layout.admin.controller.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * admin controller class of the layout module */ -class layoutAdminController extends layout +class LayoutAdminController extends Layout { /** * Initialization @@ -18,7 +18,7 @@ class layoutAdminController extends layout /** * Create a new layout * Insert a title into "layouts" table in order to create a layout - * @deprecated + * @deprecated * @return void|Object (void : success, Object : fail) */ function procLayoutAdminInsert() @@ -65,7 +65,7 @@ class layoutAdminController extends layout * Initiate if it is faceoff layout * @param int $layout_srl * @param string $layout_name - * @return void + * @return void */ function initLayout($layout_srl, $layout_name) { @@ -92,12 +92,17 @@ class layoutAdminController extends layout // Consider the rest of items as extra vars, except module, act, layout_srl, layout, and title .. Some gurida .. $extra_vars = Context::getRequestVars(); unset($extra_vars->module); + unset($extra_vars->module); unset($extra_vars->act); unset($extra_vars->layout_srl); unset($extra_vars->layout); unset($extra_vars->title); unset($extra_vars->apply_layout); unset($extra_vars->apply_mobile_view); + unset($extra_vars->success_return_url); + unset($extra_vars->error_return_url); + unset($extra_vars->xe_validator_id); + unset($extra_vars->ruleset); $is_sitemap = $extra_vars->is_sitemap; unset($extra_vars->is_sitemap); @@ -111,10 +116,7 @@ class layoutAdminController extends layout if($layout_info->menu) { $menus = get_object_vars($layout_info->menu); - } - if(count($menus)) - { - foreach($menus as $menu_id => $val) + foreach($menus ?: [] as $menu_id => $val) { $menu_srl = Context::get($menu_id); if(!$menu_srl) continue; @@ -140,49 +142,6 @@ class layoutAdminController extends layout $menu_srl_list[] = $menu_srl; $menu_name_list[$menu_srl] = $output->title; - - $apply_layout = Context::get('apply_layout'); - $apply_mobile_view = Context::get('apply_mobile_view'); - - if($apply_layout=='Y' || $apply_mobile_view=='Y') - { - $menu_args = new stdClass(); - $menu_args->menu_srl = $menu_srl; - $menu_args->site_srl = $layout_info->site_srl; - $output = executeQueryArray('layout.getLayoutModules', $menu_args); - if($output->data) - { - $modules = array(); - for($i=0;$idata);$i++) - { - $modules[] = $output->data[$i]->module_srl; - } - - if(count($modules)) - { - $update_args = new stdClass(); - $update_args->module_srls = implode(',',$modules); - if($apply_layout == "Y") - { - $update_args->layout_srl = $args->layout_srl; - } - if($layout_info->layout_type == "M") - { - if(Context::get('apply_mobile_view') == "Y") - { - $update_args->use_mobile = "Y"; - } - $output = executeQuery('layout.updateModuleMLayout', $update_args); - } - else - { - $output = executeQuery('layout.updateModuleLayout', $update_args); - } - - Rhymix\Framework\Cache::clearGroup('site_and_module'); - } - } - } } } @@ -221,7 +180,8 @@ class layoutAdminController extends layout $oModuleModel = getModel('module'); $oModuleController = getController('module'); $layout_config = new stdClass(); - $layout_config->header_script = Context::get('header_script'); + $layout_config->header_script = Context::get('header_script') ?? ''; + $layout_config->header_script = Rhymix\Modules\Admin\Models\Utility::cleanHeaderAndFooterScripts($layout_config->header_script); $oModuleController->insertModulePartConfig('layout',$args->layout_srl,$layout_config); // Save a title of the menu $extra_vars->menu_name_list = $menu_name_list; @@ -324,12 +284,12 @@ class layoutAdminController extends layout $layout_file = $oLayoutModel->getUserLayoutHtml($layout_srl); FileHandler::removeFile($layout_file); - + // Delete Layout $args = new stdClass(); $args->layout_srl = $layout_srl; $output = executeQuery("layout.deleteLayout", $args); - + Rhymix\Framework\Cache::delete('layout:' . $args->layout_srl); if(!$output->toBool()) return $output; @@ -396,7 +356,11 @@ class layoutAdminController extends layout $this->initLayout($layout_srl, $info->layout); $this->setMessage('success_reset'); - $this->setRedirectUrl(Context::get('error_return_url')); + $this->setRedirectUrl(getNotEncodedUrl([ + 'module' => 'admin', + 'act' => 'dispLayoutAdminAllInstanceList', + 'type' => starts_with('./m.layouts/', $info->path) ? 'M' : '', + ])); } /** @@ -431,12 +395,13 @@ class layoutAdminController extends layout if(!is_dir($path)) FileHandler::makeDir($path); $filename = strtolower($source['name']); + $filename = Rhymix\Framework\Filters\FilenameFilter::clean($filename); if($filename != urlencode($filename)) { $ext = substr(strrchr($filename,'.'),1); $filename = sprintf('%s.%s', md5($filename), $ext); } - + if(file_exists($path .'/'. $filename)) @unlink($path . $filename); if(!move_uploaded_file($source['tmp_name'], $path . $filename )) return false; return true; @@ -448,9 +413,19 @@ class layoutAdminController extends layout */ function procLayoutAdminUserImageDelete() { - $filename = Context::get('filename'); $layout_srl = Context::get('layout_srl'); - $this->removeUserLayoutImage($layout_srl,$filename); + if (!$layout_srl) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest(); + } + + $filename = Context::get('filename'); + if (preg_match('!(\.\.|[/\\\\])!', $filename)) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest(); + } + + $this->removeUserLayoutImage($layout_srl, $filename); $this->setMessage('success_deleted'); $this->setRedirectUrl(Context::get('error_return_url')); } @@ -459,13 +434,19 @@ class layoutAdminController extends layout * delete image into user layout * @param int $layout_srl * @param string $filename - * @return void + * @return bool */ function removeUserLayoutImage($layout_srl,$filename) { $oLayoutModel = getModel('layout'); $path = $oLayoutModel->getUserLayoutImagePath($layout_srl); - @unlink($path . $filename); + $path = FileHandler::getRealPath($path . Rhymix\Framework\Filters\FilenameFilter::clean($filename)); + if (!Rhymix\Framework\Storage::exists($path)) + { + throw new Rhymix\Framework\Exceptions\TargetNotFound(); + } + + return Rhymix\Framework\Storage::delete($path); } // deprecated @@ -659,11 +640,8 @@ class layoutAdminController extends layout $stream = $tar->toTarStream(); $filename = 'faceoff_' . date('YmdHis') . '.tar'; - header("Cache-Control: "); - header("Pragma: "); + Context::setCacheControl(0); header("Content-Type: application/x-compressed"); - header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); - // header("Content-Length: " .strlen($stream)); ?? why?? header('Content-Disposition: attachment; filename="'. $filename .'"'); header("Content-Transfer-Encoding: binary\n"); echo $stream; @@ -738,12 +716,23 @@ class layoutAdminController extends layout $args = new stdClass(); $args->extra_vars = $output->extra_vars; $extra_vars = unserialize($args->extra_vars); - - if($layout->extra_var_count) { + $image_list = array(); + + if($layout->extra_var_count && $extra_vars) + { $reg = "/^.\/files\/attach\/images\/([0-9]+)\/(.*)/"; - if($extra_vars) foreach($extra_vars as $key => $val) { - if($layout->extra_var->{$key}->type == 'image') { - if(!preg_match($reg,$val,$matches)) continue; + foreach($extra_vars as $key => $val) + { + if($layout->extra_var->{$key}->type == 'image') + { + if(!preg_match($reg, $val, $matches)) + { + continue; + } + if(!isset($image_list[$key])) + { + $image_list[$key] = new stdClass; + } $image_list[$key]->filename = $matches[2]; $image_list[$key]->old_file = $val; } @@ -760,7 +749,7 @@ class layoutAdminController extends layout $args->layout_type = $layout->layout_type; if(!$args->layout_type) $args->layout_type = "P"; - $oDB = &DB::getInstance(); + $oDB = DB::getInstance(); $oDB->begin(); if(is_array($sourceArgs->title)) @@ -775,9 +764,11 @@ class layoutAdminController extends layout $args->layout_srl = getNextSequence(); $args->title = $value; - if(is_array($image_list)) { - foreach($image_list as $key=>$val) { - $new_file = sprintf("./files/attach/images/%d/%s", $args->layout_srl,$val->filename); + if($image_list) + { + foreach($image_list as $key => $val) + { + $new_file = sprintf("./files/attach/images/%d/%s", $args->layout_srl, $val->filename); FileHandler::copyFile($val->old_file, $new_file); $extra_vars->{$key} = $new_file; } @@ -929,7 +920,7 @@ class layoutAdminController extends layout return; } - if(!preg_match('/\.(jpg|jpeg|gif|png)$/i', $img['name'])) + if(!preg_match('/\.(gif|jpe?g|png|svg|webp)$/i', $img['name'])) { Context::set('msg', lang('msg_layout_image_target')); return; @@ -973,6 +964,7 @@ class layoutAdminController extends layout $oModel = getModel('layout'); $layoutInfo = $oModel->getLayout($layoutSrl); + $newLayoutInfo = new stdClass; if($layoutInfo->extra_var_count) { foreach($layoutInfo->extra_var as $varId => $val) diff --git a/modules/layout/layout.admin.model.php b/modules/layout/layout.admin.model.php index 004879a12..547f49e90 100644 --- a/modules/layout/layout.admin.model.php +++ b/modules/layout/layout.admin.model.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * admin view class of the layout module */ -class layoutAdminModel extends layout +class LayoutAdminModel extends Layout { /** * init @@ -26,7 +26,7 @@ class layoutAdminModel extends layout $script = ''; $oTemplate = TemplateHandler::getInstance(); $html = $oTemplate->compile($this->module_path.'tpl/', 'layout_info_view'); - + $csss = ''; preg_match_all('//', $html, $m); $pluginList = $m[1]; @@ -49,11 +49,6 @@ class layoutAdminModel extends layout } $this->add('html', $csss . $script . $html); - - if($isReturn) - { - return $this->get('html'); - } } public function setLayoutAdminSetInfoView() @@ -62,7 +57,7 @@ class layoutAdminModel extends layout // Get layout information $oLayoutModel = getModel('layout'); - $layout_info = $oLayoutModel->getLayout($layout_srl); + $layout_info = $oLayoutModel->getLayout($layout_srl, false); // Error appears if there is no layout information is registered if(!$layout_info) @@ -97,79 +92,10 @@ class layoutAdminModel extends layout Context::set('selected_layout', $layout_info); } - public function getLayoutAdminSetHTMLCSS() - { - // Set the layout with its information - $layout_srl = Context::get('layout_srl'); - // Get layout information - $oLayoutModel = getModel('layout'); - $layout_info = $oLayoutModel->getLayout($layout_srl); - // Error appears if there is no layout information is registered - if(!$layout_info) - { - return $this->dispLayoutAdminInstalledList(); - } - - // Get Layout Code - if($oLayoutModel->useDefaultLayout($layout_info->layout_srl)) - { - $layout_file = $oLayoutModel->getDefaultLayoutHtml($layout_info->layout); - $layout_css_file = $oLayoutModel->getDefaultLayoutCss($layout_info->layout); - } - else - { - $layout_file = $oLayoutModel->getUserLayoutHtml($layout_info->layout_srl); - $layout_css_file = $oLayoutModel->getUserLayoutCss($layout_info->layout_srl); - - if(!file_exists($layout_file)) $layout_file = $layout_info->path . 'layout.html'; - if(!file_exists($layout_css_file)) $layout_css_file = $layout_info->path . 'layout.css'; - } - - if(file_exists($layout_css_file)) - { - $layout_code_css = FileHandler::readFile($layout_css_file); - Context::set('layout_code_css', $layout_code_css); - } - - $layout_code = FileHandler::readFile($layout_file); - Context::set('layout_code', $layout_code); - - // set User Images - $layout_image_list = $oLayoutModel->getUserLayoutImageList($layout_info->layout_srl); - Context::set('layout_image_list', $layout_image_list); - - $layout_image_path = $oLayoutModel->getUserLayoutImagePath($layout_info->layout_srl); - Context::set('layout_image_path', $layout_image_path); - // Set widget list - $oWidgetModel = getModel('widget'); - $widget_list = $oWidgetModel->getDownloadedWidgetList(); - Context::set('widget_list', $widget_list); - - $security = new Security($layout_info); - $layout_info = $security->encodeHTML('.', '.author..'); - Context::set('selected_layout', $layout_info); - - //Security - $security = new Security(); - $security->encodeHTML('layout_list..'); - $security->encodeHTML('layout_list..author..'); - - $security = new Security(); - $security->encodeHTML('layout_code_css', 'layout_code', 'widget_list..title'); - - $script = ''; - $oTemplate = &TemplateHandler::getInstance(); - $html = $oTemplate->compile($this->module_path.'tpl/', 'layout_html_css_view'); - - $this->add('html', $script.$html); - } - public function getLayoutAdminSiteDefaultLayout() { - $siteSrl = Context::get('site_srl'); $type = Context::get('type'); - - $layoutSrl = $this->getSiteDefaultLayout($type, $siteSrl); + $layoutSrl = $this->getSiteDefaultLayout($type); $oLayoutModel = getModel('layout'); $layoutInfo = $oLayoutModel->getLayoutRawData($layoutSrl, array('title')); @@ -178,10 +104,10 @@ class layoutAdminModel extends layout $this->add('title', $layoutInfo->title); } - public function getSiteDefaultLayout($viewType = 'P', $siteSrl = 0) + public function getSiteDefaultLayout($viewType = 'P') { $target = ($viewType == 'M') ? 'mlayout_srl' : 'layout_srl'; - $designInfoFile = sprintf(_XE_PATH_ . 'files/site_design/design_%s.php', $siteSrl); + $designInfoFile = RX_BASEDIR . 'files/site_design/design_0.php'; if(FileHandler::exists($designInfoFile)) include($designInfoFile); if(!$designInfo || !$designInfo->{$target}) diff --git a/modules/layout/layout.admin.view.php b/modules/layout/layout.admin.view.php index 9dc793a9b..26f532086 100644 --- a/modules/layout/layout.admin.view.php +++ b/modules/layout/layout.admin.view.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * admin view class of the layout module */ -class layoutAdminView extends layout +class LayoutAdminView extends Layout { /** * Initialization @@ -97,7 +97,7 @@ class layoutAdminView extends layout $layout_info = $oLayoutModel->getLayoutInfo($item->layout, null, $type); if ($layout_info) { - $layout_list[$item->layout]['title'] = $layout_info->title; + $layout_list[$item->layout]['title'] = $layout_info->title; } } @@ -214,7 +214,7 @@ class layoutAdminView extends layout Context::set('is_sitemap', '0'); $script = ''; - $oTemplate = &TemplateHandler::getInstance(); + $oTemplate = TemplateHandler::getInstance(); $content = $oTemplate->compile($this->module_path.'tpl/', 'layout_info_view'); Context::set('content', $content); @@ -232,10 +232,16 @@ class layoutAdminView extends layout $layout_srl = Context::get('layout_srl'); // Get layout information $oLayoutModel = getModel('layout'); - $layout_info = $oLayoutModel->getLayout($layout_srl); + $layout_info = $oLayoutModel->getLayout($layout_srl, false); // Error appears if there is no layout information is registered if(!$layout_info) return $this->dispLayoutAdminInstalledList(); + // Prevent editing if the layout has not already been edited #2121 + if(!$layout_info->is_edited) + { + return new BaseObject(-1, 'layout.layout_editing_deprecated_p1'); + } + // Get Layout Code if($oLayoutModel->useDefaultLayout($layout_info->layout_srl)) { @@ -330,7 +336,7 @@ class layoutAdminView extends layout FileHandler::writeFile($edited_layout_file, $code); // Compile - $oTemplate = &TemplateHandler::getInstance(); + $oTemplate = TemplateHandler::getInstance(); $layout_path = $layout_info->path; $layout_file = 'layout'; @@ -338,7 +344,6 @@ class layoutAdminView extends layout $layout_tpl = $oTemplate->compile($layout_path, $layout_file, $edited_layout_file); Context::set('layout','none'); // Convert widgets and others - $oContext = &Context::getInstance(); Context::set('layout_tpl', $layout_tpl); // Delete Temporary Files FileHandler::removeFile($edited_layout_file); @@ -372,7 +377,7 @@ class layoutAdminView extends layout $faceoffcss = $oLayoutModel->_getUserLayoutFaceOffCss($current_module_info->layout_srl); $css = FileHandler::readFile($faceoffcss); - $match = null; + $style = []; preg_match_all('/([^\{]+)\{([^\}]*)\}/is',$css,$match); for($i=0,$c=count($match[1]);$i<$c;$i++) { @@ -389,7 +394,7 @@ class layoutAdminView extends layout Context::addHtmlHeader($script); } - $oTemplate = &TemplateHandler::getInstance(); + $oTemplate = TemplateHandler::getInstance(); Context::set('content', $oTemplate->compile($this->module_path.'tpl','about_faceoff')); // Change widget codes in Javascript mode $oWidgetController = getController('widget'); diff --git a/modules/layout/layout.class.php b/modules/layout/layout.class.php index 939a683bf..aaf0c2ab9 100644 --- a/modules/layout/layout.class.php +++ b/modules/layout/layout.class.php @@ -3,9 +3,9 @@ /** * @class layout * @author NAVER (developers@xpressengine.com) - * high class of the layout module + * high class of the layout module */ -class layout extends ModuleObject +class Layout extends ModuleObject { /** * Implement if additional tasks are necessary when installing @@ -13,8 +13,7 @@ class layout extends ModuleObject */ function moduleInstall() { - // Create a directory to be used in the layout - FileHandler::makeDir('./files/cache/layout'); + } /** @@ -23,32 +22,6 @@ class layout extends ModuleObject */ function checkUpdate() { - $oDB = &DB::getInstance(); - // 2009. 02. 11 Add site_srl to layout table - if(!$oDB->isColumnExists('layouts', 'site_srl')) return true; - // 2009. 02. 26 Move the previous layout for faceoff - $files = FileHandler::readDir('./files/cache/layout'); - for($i=0,$c=count($files);$i<$c;$i++) - { - $filename = $files[$i]; - if(preg_match('/([0-9]+)\.html/i',$filename)) return true; - } - - if(!$oDB->isColumnExists('layouts', 'layout_type')) return true; - - $args = new stdClass(); - $args->layout = '.'; - $output = executeQueryArray('layout.getLayoutDotList', $args); - if($output->data && count($output->data) > 0) - { - foreach($output->data as $layout) - { - $layout_path = explode('.', $layout->layout); - if(count($layout_path) != 2) continue; - if(is_dir(sprintf(_XE_PATH_ . 'themes/%s/layouts/%s', $layout_path[0], $layout_path[1]))) return true; - } - } - return false; } @@ -58,50 +31,7 @@ class layout extends ModuleObject */ function moduleUpdate() { - $oDB = &DB::getInstance(); - // 2009. 02. 11 Add site_srl to menu table - if(!$oDB->isColumnExists('layouts', 'site_srl')) - { - $oDB->addColumn('layouts','site_srl','number',11,0,true); - } - // 2009. 02. 26 Move the previous layout for faceoff - $oLayoutModel = getModel('layout'); - $files = FileHandler::readDir('./files/cache/layout'); - for($i=0,$c=count($files);$i<$c;$i++) - { - $filename = $files[$i]; - if(!preg_match('/([0-9]+)\.html/i',$filename,$match)) continue; - $layout_srl = $match[1]; - if(!$layout_srl) continue; - $path = $oLayoutModel->getUserLayoutPath($layout_srl); - if(!is_dir($path)) FileHandler::makeDir($path); - FileHandler::copyFile('./files/cache/layout/'.$filename, $path.'layout.html'); - @unlink('./files/cache/layout/'.$filename); - } - if(!$oDB->isColumnExists('layouts', 'layout_type')) - { - $oDB->addColumn('layouts','layout_type','char',1,'P',true); - } - - $args = new stdClass(); - $args->layout = '.'; - $output = executeQueryArray('layout.getLayoutDotList', $args); - if($output->data && count($output->data) > 0) - { - foreach($output->data as $layout) - { - $layout_path = explode('.', $layout->layout); - if(count($layout_path) != 2) continue; - if(is_dir(sprintf(_XE_PATH_ . 'themes/%s/layouts/%s', $layout_path[0], $layout_path[1]))) - { - $args->layout = implode('|@|', $layout_path); - $args->layout_srl = $layout->layout_srl; - $output = executeQuery('layout.updateLayout', $args); - Rhymix\Framework\Cache::delete('layout:' . $args->layout_srl); - } - } - } } /** @@ -110,12 +40,7 @@ class layout extends ModuleObject */ function recompileCache() { - $path = './files/cache/layout'; - if(!is_dir($path)) - { - FileHandler::makeDir($path); - return; - } + } } /* End of file layout.class.php */ diff --git a/modules/layout/layout.model.php b/modules/layout/layout.model.php index 0af1e37f1..402f8b348 100644 --- a/modules/layout/layout.model.php +++ b/modules/layout/layout.model.php @@ -6,7 +6,7 @@ * @version 0.1 * Model class of the layout module */ -class layoutModel extends layout +class LayoutModel extends Layout { /** * Check user layout temp @@ -14,48 +14,34 @@ class layoutModel extends layout */ var $useUserLayoutTemp = null; - /** - * Initialization - * @return void - */ - function init() - { - } - /** * Get a layout list created in the DB * If you found a new list, it means that the layout list is inserted to the DB - * @deprecated + * * @param int $site_srl * @param string $layout_type (P : PC, M : Mobile) * @param array $columnList * @return array layout lists in site */ - function getLayoutList($site_srl = 0, $layout_type="P", $columnList = array()) + public static function getLayoutList($site_srl = 0, $layout_type="P", $columnList = array()) { - if(!$site_srl) - { - $site_module_info = Context::get('site_module_info'); - $site_srl = (int)$site_module_info->site_srl; - } $args = new stdClass(); - $args->site_srl = $site_srl; $args->layout_type = $layout_type; $output = executeQueryArray('layout.getLayoutList', $args, $columnList); foreach($output->data as $no => &$val) { - if(!$this->isExistsLayoutFile($val->layout, $layout_type)) + if(!self::isExistsLayoutFile($val->layout, $layout_type)) { unset($output->data[$no]); } } - - $oLayoutAdminModel = getAdminModel('layout'); - $siteDefaultLayoutSrl = $oLayoutAdminModel->getSiteDefaultLayout($layout_type, $site_srl); + + $oLayoutAdminModel = LayoutAdminModel::getInstance(); + $siteDefaultLayoutSrl = $oLayoutAdminModel->getSiteDefaultLayout($layout_type); if($siteDefaultLayoutSrl) { - $siteDefaultLayoutInfo = $this->getlayout($siteDefaultLayoutSrl); + $siteDefaultLayoutInfo = self::getLayout($siteDefaultLayoutSrl); $siteDefaultLayoutInfo->layout_srl = -1; $siteDefaultLayoutInfo->layout = $siteDefaultLayoutInfo->title; $siteDefaultLayoutInfo->title = lang('use_site_default_layout'); @@ -70,7 +56,7 @@ class layoutModel extends layout $responsiveLayoutInfo->title = lang('use_responsive_pc_layout'); array_unshift($output->data, $responsiveLayoutInfo); } - + return $output->data; } @@ -81,15 +67,14 @@ class layoutModel extends layout */ public function getLayoutInstanceListForJSONP() { - $siteSrl = Context::get('site_srl'); $layoutType = Context::get('layout_type'); - $layoutList = $this->getLayoutInstanceList($siteSrl, $layoutType); + $layoutList = $this->getLayoutInstanceList(0, $layoutType); $thumbs = array(); foreach($layoutList as $key => $val) { - if($thumbs[$val->layouts]) + if(!empty($thumbs[$val->layouts ?? ''])) { $val->thumbnail = $thumbs[$val->layouts]; continue; @@ -100,6 +85,10 @@ class layoutModel extends layout { $thumbnailPath = sprintf('./themes/%s/layouts/%s/thumbnail.png' , $token[0], $token[1]); } + else if($layoutType == 'M') + { + $thumbnailPath = sprintf('./m.layouts/%s/thumbnail.png' , $val->layout); + } else { $thumbnailPath = sprintf('./layouts/%s/thumbnail.png' , $val->layout); @@ -125,19 +114,13 @@ class layoutModel extends layout * @param array $columnList * @return array layout lists in site */ - function getLayoutInstanceList($siteSrl = 0, $layoutType = 'P', $layout = null, $columnList = array()) + public static function getLayoutInstanceList($siteSrl = 0, $layoutType = 'P', $layout = null, $columnList = array()) { - if (!$siteSrl) - { - $siteModuleInfo = Context::get('site_module_info'); - $siteSrl = (int)$siteModuleInfo->site_srl; - } if ($columnList && !isset($columnList['layout_type'])) { $columnList[] = 'layout_type'; } $args = new stdClass(); - $args->site_srl = $siteSrl; $args->layout_type = $layoutType === 'P' ? 'P' : 'P,M'; $args->layout = $layout; $output = executeQueryArray('layout.getLayoutList', $args, $columnList); @@ -148,7 +131,17 @@ class layoutModel extends layout { foreach($output->data as $no => $iInfo) { - if($this->isExistsLayoutFile($iInfo->layout, $iInfo->layout_type) && $iInfo->layout_type === $layoutType) + if (file_exists(\RX_BASEDIR . 'files/faceOff/' . getNumberingPath($iInfo->layout_srl) . 'layout.html') || + file_exists(\RX_BASEDIR . 'files/faceOff/' . getNumberingPath($iInfo->layout_srl) . 'layout.css')) + { + $iInfo->is_edited = true; + } + else + { + $iInfo->is_edited = false; + } + + if(self::isExistsLayoutFile($iInfo->layout, $iInfo->layout_type) && $iInfo->layout_type === $layoutType) { $instanceList[] = $iInfo->layout; } @@ -162,7 +155,7 @@ class layoutModel extends layout // Create downloaded name list $downloadedList = array(); $titleList = array(); - $_downloadedList = $this->getDownloadedLayoutList($layoutType); + $_downloadedList = self::getDownloadedLayoutList($layoutType); if(is_array($_downloadedList)) { foreach($_downloadedList as $dLayoutInfo) @@ -171,19 +164,18 @@ class layoutModel extends layout $titleList[$dLayoutInfo->layout] = $dLayoutInfo->title; } } - + if($layout) { if(count($instanceList) < 1 && $downloadedList[$layout]) { $insertArgs = new stdClass(); - $insertArgs->site_srl = $siteSrl; $insertArgs->layout_srl = getNextSequence(); $insertArgs->layout = $layout; $insertArgs->title = $titleList[$layout]; $insertArgs->layout_type = $layoutType; - $oLayoutAdminController = getAdminController('layout'); + $oLayoutAdminController = LayoutAdminController::getInstance(); $oLayoutAdminController->insertLayout($insertArgs); $isCreateInstance = TRUE; } @@ -195,13 +187,12 @@ class layoutModel extends layout foreach($noInstanceList as $layoutName) { $insertArgs = new stdClass(); - $insertArgs->site_srl = $siteSrl; $insertArgs->layout_srl = getNextSequence(); $insertArgs->layout = $layoutName; $insertArgs->title = $titleList[$layoutName]; $insertArgs->layout_type = $layoutType; - $oLayoutAdminController = getAdminController('layout'); + $oLayoutAdminController = LayoutAdminController::getInstance(); $oLayoutAdminController->insertLayout($insertArgs); $isCreateInstance = TRUE; } @@ -216,7 +207,7 @@ class layoutModel extends layout { foreach($output->data as $no => $iInfo) { - if(!$this->isExistsLayoutFile($iInfo->layout, $layoutType)) + if(!self::isExistsLayoutFile($iInfo->layout, $layoutType)) { unset($output->data[$no]); } @@ -234,18 +225,18 @@ class layoutModel extends layout * @param string $layoutType P or M * @return bool */ - function isExistsLayoutFile($layout, $layoutType) + public static function isExistsLayoutFile($layout, $layoutType) { //TODO If remove a support themes, remove this codes also. if($layoutType == 'P') { - $pathPrefix = _XE_PATH_ . 'layouts/'; - $themePathFormat = _XE_PATH_ . 'themes/%s/layouts/%s'; + $pathPrefix = RX_BASEDIR . 'layouts/'; + $themePathFormat = RX_BASEDIR . 'themes/%s/layouts/%s'; } else { - $pathPrefix = _XE_PATH_ . 'm.layouts/'; - $themePathFormat = _XE_PATH_ . 'themes/%s/m.layouts/%s'; + $pathPrefix = RX_BASEDIR . 'm.layouts/'; + $themePathFormat = RX_BASEDIR . 'themes/%s/m.layouts/%s'; } if(strpos($layout, '|@|') !== FALSE) @@ -258,40 +249,70 @@ class layoutModel extends layout $path = $pathPrefix . $layout; } - return is_readable($path . '/layout.html'); + if (file_exists($path . '/layout.html') && is_readable($path . '/layout.html')) + { + return true; + } + elseif (file_exists($path . '/layout.blade.php') && is_readable($path . '/layout.blade.php')) + { + return true; + } + else + { + return false; + } } /** * Get one of layout information created in the DB * Return DB info + XML info of the generated layout * @param int $layout_srl + * @param bool $use_cache * @return object info of layout */ - function getLayout($layout_srl) + public static function getLayout($layout_srl, $use_cache = true) { // Get information from cache $layout_info = Rhymix\Framework\Cache::get("layout:$layout_srl"); - if ($layout_info !== null) + if ($use_cache && $layout_info !== null) { return $layout_info; } - + // Get information from the DB $args = new stdClass(); $args->layout_srl = $layout_srl; $output = executeQuery('layout.getLayout', $args); - if(!$output->data) return; - $layout = $output->data->layout; + if (!$output->data) + { + return; + } // Return xml file informaton after listing up the layout and extra_vars - $layout_info = $this->getLayoutInfo($layout, $output->data, $output->data->layout_type); + $layout = $output->data->layout; + $layout_info = self::getLayoutInfo($layout, $output->data, $output->data->layout_type); + if (!$layout_info) + { + return; + } + + // Check if layout has been edited + if (file_exists(\RX_BASEDIR . 'files/faceOff/' . getNumberingPath($layout_srl) . 'layout.html') || + file_exists(\RX_BASEDIR . 'files/faceOff/' . getNumberingPath($layout_srl) . 'layout.css')) + { + $layout_info->is_edited = true; + } + else + { + $layout_info->is_edited = false; + } // Store in cache Rhymix\Framework\Cache::set("layout:$layout_srl", $layout_info); return $layout_info; } - function getLayoutRawData($layout_srl, $columnList = array()) + public static function getLayoutRawData($layout_srl, $columnList = array()) { $args = new stdClass(); $args->layout_srl = $layout_srl; @@ -310,9 +331,9 @@ class layoutModel extends layout * @param string $layout_type (P : PC, M : Mobile) * @return string path of layout */ - function getLayoutPath($layout_name = "", $layout_type = "P") + public function getLayoutPath($layout_name = "", $layout_type = "P") { - $layout_parse = explode('|@|', $layout_name); + $layout_parse = explode('|@|', $layout_name ?? ''); if(count($layout_parse) > 1) { $class_path = './themes/'.$layout_parse[0].'/layouts/'.$layout_parse[1].'/'; @@ -340,12 +361,15 @@ class layoutModel extends layout * @param boolean $withAutoinstallInfo * @return array info of layout */ - function getDownloadedLayoutList($layout_type = "P", $withAutoinstallInfo = false) + public static function getDownloadedLayoutList($layout_type = "P", $withAutoinstallInfo = false) { - if ($withAutoinstallInfo) $oAutoinstallModel = getModel('autoinstall'); + if ($withAutoinstallInfo) + { + $oAutoinstallModel = AutoinstallModel::getInstance(); + } // Get a list of downloaded layout and installed layout - $searched_list = $this->_getInstalledLayoutDirectories($layout_type); + $searched_list = self::_getInstalledLayoutDirectories($layout_type); $searched_count = count($searched_list); if(!$searched_count) return; @@ -357,14 +381,14 @@ class layoutModel extends layout // Name of the layout $layout = $searched_list[$i]; // Get information of the layout - $layout_info = $this->getLayoutInfo($layout, null, $layout_type); + $layout_info = self::getLayoutInfo($layout, null, $layout_type); if(!$layout_info) { continue; } - if($withAutoinstallInfo) + if($withAutoinstallInfo && false) { // get easyinstall remove url $packageSrl = $oAutoinstallModel->getPackageSrlByPath($layout_info->path); @@ -383,14 +407,14 @@ class layoutModel extends layout $list[] = $layout_info; } - usort($list, array($this, 'sortLayoutByTitle')); + usort($list, array(self::class, 'sortLayoutByTitle')); return $list; } /** * Sort layout by title */ - function sortLayoutByTitle($a, $b) + public static function sortLayoutByTitle($a, $b) { if(!$a->title) { @@ -402,8 +426,8 @@ class layoutModel extends layout $b->title = $b->layout; } - $aTitle = strtolower($a->title); - $bTitle = strtolower($b->title); + $aTitle = strtolower($a->title ?? ''); + $bTitle = strtolower($b->title ?? ''); if($aTitle == $bTitle) { @@ -418,9 +442,9 @@ class layoutModel extends layout * @param string $layoutType (P : PC, M : Mobile) * @return int */ - function getInstalledLayoutCount($layoutType = 'P') + public static function getInstalledLayoutCount($layoutType = 'P') { - $searchedList = $this->_getInstalledLayoutDirectories($layoutType); + $searchedList = self::_getInstalledLayoutDirectories($layoutType); return count($searchedList); } @@ -429,7 +453,7 @@ class layoutModel extends layout * @param string $layoutType (P : PC, M : Mobile) * @return array */ - function _getInstalledLayoutDirectories($layoutType = 'P') + public static function _getInstalledLayoutDirectories($layoutType = 'P') { if($layoutType == 'M') { @@ -442,7 +466,10 @@ class layoutModel extends layout $globalValueKey = 'PC_LAYOUT_DIRECTORIES'; } - if($GLOBALS[$globalValueKey]) return $GLOBALS[$globalValueKey]; + if(!empty($GLOBALS[$globalValueKey])) + { + return $GLOBALS[$globalValueKey]; + } $searchedList = FileHandler::readDir($directory); if (!$searchedList) $searchedList = array(); @@ -459,7 +486,7 @@ class layoutModel extends layout * @param string $layoutType (P : PC, M : Mobile) * @return object info of layout */ - function getLayoutInfo($layout, $info = null, $layout_type = "P") + public static function getLayoutInfo($layout, $info = null, $layout_type = "P") { if($info) { @@ -467,7 +494,7 @@ class layoutModel extends layout $layout = $info->layout; $layout_srl = $info->layout_srl; $site_srl = $info->site_srl; - $vars = unserialize($info->extra_vars); + $vars = $info->extra_vars ? unserialize($info->extra_vars) : null; if($info->module_srl) { @@ -475,13 +502,28 @@ class layoutModel extends layout $xml_file = sprintf('%sskin.xml', $layout_path); } } + else + { + $layout_title = $layout_srl = null; + $site_srl = 0; + $vars = new stdClass; + } // Get a path of the requested module. Return if not exists. - if(!$layout_path) $layout_path = $this->getLayoutPath($layout, $layout_type); - if(!is_dir($layout_path)) return; + if(!isset($layout_path)) + { + $layout_path = self::getInstance()->getLayoutPath($layout, $layout_type); + } + if(!is_dir($layout_path)) + { + return; + } // Read the xml file for module skin information - if(!$xml_file) $xml_file = sprintf("%sconf/info.xml", $layout_path); + if(!isset($xml_file)) + { + $xml_file = sprintf("%sconf/info.xml", $layout_path); + } if(!file_exists($xml_file)) { $layout_info = new stdClass; @@ -489,7 +531,7 @@ class layoutModel extends layout $layout_info->layout = $layout; $layout_info->path = $layout_path; $layout_info->layout_title = $layout_title; - if(!$layout_info->layout_type) + if(empty($layout_info->layout_type)) { $layout_info->layout_type = $layout_type; } @@ -497,262 +539,74 @@ class layoutModel extends layout } // Include the cache file if it is valid and then return $layout_info variable - if(!$layout_srl) + if(empty($layout_srl)) { - $cache_file = $this->getLayoutCache($layout, Context::getLangType(), $layout_type); + $cache_file = self::getLayoutCache($layout, Context::getLangType(), $layout_type); } else { - $cache_file = $this->getUserLayoutCache($layout_srl, Context::getLangType()); + $cache_file = self::getUserLayoutCache($layout_srl, Context::getLangType()); } - - if(file_exists($cache_file)&&filemtime($cache_file)>filemtime($xml_file)) - { - include($cache_file); - if($layout_info->extra_var && $vars) + if(file_exists($cache_file) && filemtime($cache_file) > filemtime($xml_file)) + { + // Read cache file in a way that is compatible with the old format. + // The old format sets $layout_info directly, while the new format returns an object. + $layout_info = new stdClass; + if (is_object($output = include($cache_file))) + { + $layout_info = $output; + } + if (empty($layout_info->layout)) + { + return; + } + + if ($layout_info->extra_var && $vars) { foreach($vars as $key => $value) { - if(!$layout_info->extra_var->{$key} && !$layout_info->{$key}) + if(!isset($layout_info->extra_var->{$key}) && !isset($layout_info->{$key})) { $layout_info->{$key} = $value; } } } - - if(!$layout_info->title) - { - $layout_info->title = $layout; - } - return $layout_info; } + // If no cache file exists, parse the xml and then return the variable. - $oXmlParser = new XmlParser(); - $tmp_xml_obj = $oXmlParser->loadXmlFile($xml_file); - - if($tmp_xml_obj->layout) $xml_obj = $tmp_xml_obj->layout; - elseif($tmp_xml_obj->skin) $xml_obj = $tmp_xml_obj->skin; - - if(!$xml_obj) return; - - $buff = array(); - $buff[] = '$layout_info = new stdClass;'; - $buff[] = sprintf('$layout_info->site_srl = %s;', var_export($site_srl, true)); - - if($xml_obj->version && $xml_obj->attrs->version == '0.2') + $xml_info = Rhymix\Framework\Parsers\LayoutInfoParser::loadXML($xml_file, $layout, $layout_path); + if (!$xml_info) { - // Layout title, version and other information - sscanf($xml_obj->date->body, '%d-%d-%d', $date_obj->y, $date_obj->m, $date_obj->d); - $date = sprintf('%04d%02d%02d', $date_obj->y, $date_obj->m, $date_obj->d); - $buff[] = sprintf('$layout_info->layout = %s;', var_export($layout, true)); - $buff[] = sprintf('$layout_info->type = %s;', var_export($xml_obj->attrs->type, true)); - $buff[] = sprintf('$layout_info->path = %s;', var_export($layout_path, true)); - $buff[] = sprintf('$layout_info->title = %s;', var_export($xml_obj->title->body, true)); - $buff[] = sprintf('$layout_info->description = %s;', var_export($xml_obj->description->body, true)); - $buff[] = sprintf('$layout_info->version = %s;', var_export($xml_obj->version->body, true)); - $buff[] = sprintf('$layout_info->date = %s;', var_export($date, true)); - $buff[] = sprintf('$layout_info->homepage = %s;', var_export($xml_obj->link->body, true)); - $buff[] = sprintf('$layout_info->layout_srl = $layout_srl;'); - $buff[] = sprintf('$layout_info->layout_title = $layout_title;'); - $buff[] = sprintf('$layout_info->license = %s;', var_export($xml_obj->license->body, true)); - $buff[] = sprintf('$layout_info->license_link = %s;', var_export($xml_obj->license->attrs->link, true)); - $buff[] = sprintf('$layout_info->layout_type = %s;', var_export($layout_type, true)); + return; + } - // Author information - if(!is_array($xml_obj->author)) $author_list[] = $xml_obj->author; - else $author_list = $xml_obj->author; - - $buff[] = '$layout_info->author = array();'; - for($i=0, $c=count($author_list); $i<$c; $i++) + // Fill in user configuration + foreach ($xml_info->extra_var ?: [] as $key => $value) + { + if (isset($vars->{$key})) { - $buff[] = sprintf('$layout_info->author[%d] = new stdClass;', $i); - $buff[] = sprintf('$layout_info->author[%d]->name = %s;', $i, var_export($author_list[$i]->name->body, true)); - $buff[] = sprintf('$layout_info->author[%d]->email_address = %s;', $i, var_export($author_list[$i]->attrs->email_address, true)); - $buff[] = sprintf('$layout_info->author[%d]->homepage = %s;', $i, var_export($author_list[$i]->attrs->link, true)); - } - - // Extra vars (user defined variables to use in a template) - $extra_var_groups = $xml_obj->extra_vars->group; - if(!$extra_var_groups) $extra_var_groups = $xml_obj->extra_vars; - if(!is_array($extra_var_groups)) $extra_var_groups = array($extra_var_groups); - - $buff[] = '$layout_info->extra_var = new stdClass;'; - $extra_var_count = 0; - foreach($extra_var_groups as $group) - { - $extra_vars = $group->var; - if($extra_vars) - { - if(!is_array($extra_vars)) $extra_vars = array($extra_vars); - - $count = count($extra_vars); - $extra_var_count += $count; - - for($i=0;$i<$count;$i++) - { - unset($var, $options); - $var = $extra_vars[$i]; - $name = $var->attrs->name; - - $buff[] = sprintf('$layout_info->extra_var->%s = new stdClass;', $name); - $buff[] = sprintf('$layout_info->extra_var->%s->group = %s;', $name, var_export($group->title->body, true)); - $buff[] = sprintf('$layout_info->extra_var->%s->title = %s;', $name, var_export($var->title->body, true)); - $buff[] = sprintf('$layout_info->extra_var->%s->type = %s;', $name, var_export($var->attrs->type, true)); - $buff[] = sprintf('$layout_info->extra_var->%s->value = $vars->%s;', $name, $name); - $buff[] = sprintf('$layout_info->extra_var->%s->description = %s;', $name, var_export($var->description->body, true)); - - $options = $var->options; - if(!$options) continue; - if(!is_array($options)) $options = array($options); - - $buff[] = sprintf('$layout_info->extra_var->%s->options = array();', $var->attrs->name); - $options_count = count($options); - $thumbnail_exist = false; - for($j=0; $j < $options_count; $j++) - { - $buff[] = sprintf('$layout_info->extra_var->%s->options[%s] = new stdClass;', $var->attrs->name, var_export($options[$j]->attrs->value, true)); - $thumbnail = $options[$j]->attrs->src; - if($thumbnail) - { - $thumbnail = $layout_path.$thumbnail; - if(file_exists($thumbnail)) - { - $buff[] = sprintf('$layout_info->extra_var->%s->options[%s]->thumbnail = %s;', $var->attrs->name, var_export($options[$j]->attrs->value, true), var_export($thumbnail, true)); - if(!$thumbnail_exist) - { - $buff[] = sprintf('$layout_info->extra_var->%s->thumbnail_exist = true;', $var->attrs->name); - $thumbnail_exist = true; - } - } - } - $buff[] = sprintf('$layout_info->extra_var->%s->options[%s]->val = %s;', $var->attrs->name, var_export($options[$j]->attrs->value, true), var_export($options[$j]->title->body, true)); - } - } - } - } - $buff[] = sprintf('$layout_info->extra_var_count = "%s";', $extra_var_count); - // Menu - if($xml_obj->menus->menu) - { - $menus = $xml_obj->menus->menu; - if(!is_array($menus)) $menus = array($menus); - - $menu_count = count($menus); - $buff[] = sprintf('$layout_info->menu_count = "%s";', $menu_count); - $buff[] = '$layout_info->menu = new stdClass;'; - for($i=0;$i<$menu_count;$i++) - { - $name = $menus[$i]->attrs->name; - if($menus[$i]->attrs->default == "true") $buff[] = sprintf('$layout_info->default_menu = "%s";', $name); - $buff[] = sprintf('$layout_info->menu->%s = new stdClass;', $name); - $buff[] = sprintf('$layout_info->menu->%s->name = %s;', $name, var_export($menus[$i]->attrs->name, true)); - $buff[] = sprintf('$layout_info->menu->%s->title = %s;', $name, var_export($menus[$i]->title->body, true)); - $buff[] = sprintf('$layout_info->menu->%s->maxdepth = %s;', $name, var_export($menus[$i]->attrs->maxdepth, true)); - - $buff[] = sprintf('$layout_info->menu->%s->menu_srl = $vars->%s;', $name, $name); - $buff[] = sprintf('$layout_info->menu->%s->xml_file = "./files/cache/menu/".$vars->%s.".xml.php";',$name, $name); - $buff[] = sprintf('$layout_info->menu->%s->php_file = "./files/cache/menu/".$vars->%s.".php";',$name, $name); - } + $value->value = $vars->{$key}; } } - else + foreach ($xml_info->menu ?: [] as $key => $value) { - // Layout title, version and other information - sscanf($xml_obj->author->attrs->date, '%d. %d. %d', $date_obj->y, $date_obj->m, $date_obj->d); - $date = sprintf('%04d%02d%02d', $date_obj->y, $date_obj->m, $date_obj->d); - $buff[] = sprintf('$layout_info->layout = %s;', var_export($layout, true)); - $buff[] = sprintf('$layout_info->path = %s;', var_export($layout_path, true)); - $buff[] = sprintf('$layout_info->title = %s;', var_export($xml_obj->title->body, true)); - $buff[] = sprintf('$layout_info->description = %s;', var_export($xml_obj->author->description->body, true)); - $buff[] = sprintf('$layout_info->version = %s;', var_export($xml_obj->attrs->version, true)); - $buff[] = sprintf('$layout_info->date = %s;', var_export($date, true)); - $buff[] = sprintf('$layout_info->layout_srl = $layout_srl;'); - $buff[] = sprintf('$layout_info->layout_title = $layout_title;'); - // Author information - $buff[] = sprintf('$layout_info->author[0]->name = %s;', var_export($xml_obj->author->name->body, true)); - $buff[] = sprintf('$layout_info->author[0]->email_address = %s;', var_export($xml_obj->author->attrs->email_address, true)); - $buff[] = sprintf('$layout_info->author[0]->homepage = %s;', var_export($xml_obj->author->attrs->link, true)); - // Extra vars (user defined variables to use in a template) - $extra_var_groups = $xml_obj->extra_vars->group; - if(!$extra_var_groups) $extra_var_groups = $xml_obj->extra_vars; - if(!is_array($extra_var_groups)) $extra_var_groups = array($extra_var_groups); - foreach($extra_var_groups as $group) + if (isset($vars->{$key}) && $vars->{$key}) { - $extra_vars = $group->var; - if($extra_vars) - { - if(!is_array($extra_vars)) $extra_vars = array($extra_vars); - - $extra_var_count = count($extra_vars); - - $buff[] = sprintf('$layout_info->extra_var_count = "%s";', $extra_var_count); - for($i=0;$i<$extra_var_count;$i++) - { - unset($var, $options); - $var = $extra_vars[$i]; - $name = $var->attrs->name; - - $buff[] = sprintf('$layout_info->extra_var->%s->group = %s;', $name, var_export($group->title->body, true)); - $buff[] = sprintf('$layout_info->extra_var->%s->title = %s;', $name, var_export($var->title->body, true)); - $buff[] = sprintf('$layout_info->extra_var->%s->type = %s;', $name, var_export($var->attrs->type, true)); - $buff[] = sprintf('$layout_info->extra_var->%s->value = $vars->%s;', $name, $name); - $buff[] = sprintf('$layout_info->extra_var->%s->description = %s;', $name, var_export($var->description->body, true)); - - $options = $var->options; - if(!$options) continue; - - if(!is_array($options)) $options = array($options); - $options_count = count($options); - for($j=0;$j<$options_count;$j++) - { - $buff[] = sprintf('$layout_info->extra_var->%s->options[%s]->val = %s;', $var->attrs->name, var_export($options[$j]->value->body, true), var_export($options[$j]->title->body, true)); - } - } - } - } - // Menu - if($xml_obj->menus->menu) - { - $menus = $xml_obj->menus->menu; - if(!is_array($menus)) $menus = array($menus); - - $menu_count = count($menus); - $buff[] = sprintf('$layout_info->menu_count = "%s";', $menu_count); - for($i=0;$i<$menu_count;$i++) - { - $name = $menus[$i]->attrs->name; - if($menus[$i]->attrs->default == "true") $buff[] = sprintf('$layout_info->default_menu = "%s";', $name); - $buff[] = sprintf('$layout_info->menu->%s->name = "%s";',$name, $name); - $buff[] = sprintf('$layout_info->menu->%s->title = %s;',$name, var_export($menus[$i]->title->body, true)); - $buff[] = sprintf('$layout_info->menu->%s->maxdepth = "%s";',$name, $menus[$i]->maxdepth->body); - $buff[] = sprintf('$layout_info->menu->%s->menu_srl = $vars->%s;', $name, $name); - $buff[] = sprintf('$layout_info->menu->%s->xml_file = "./files/cache/menu/".$vars->%s.".xml.php";',$name, $name); - $buff[] = sprintf('$layout_info->menu->%s->php_file = "./files/cache/menu/".$vars->%s.".php";',$name, $name); - } + $value->menu_srl = $vars->{$key}; + $value->xml_file = sprintf('./files/cache/menu/%s.xml.php', $vars->{$key}); + $value->php_file = sprintf('./files/cache/menu/%s.php', $vars->{$key}); } } - // header_script - $oModuleModel = getModel('module'); - $layout_config = $oModuleModel->getModulePartConfig('layout', $layout_srl); - $header_script = trim($layout_config->header_script); + $layout_config = ModuleModel::getModulePartConfig('layout', $layout_srl); + $xml_info->header_script = trim($layout_config->header_script ?? ''); + $xml_info->layout_srl = $layout_srl; + $xml_info->layout_title = $layout_title; - if($header_script) - { - $buff[] = sprintf(' $layout_info->header_script = %s; ', var_export($header_script, true)); - } - - FileHandler::writeFile($cache_file, 'title) - { - $layout_info->title = $layout; - } - - return $layout_info; + Rhymix\Framework\Storage::writePHPData($cache_file, $xml_info, null, false); + return $xml_info; } /** @@ -760,9 +614,9 @@ class layoutModel extends layout * @param int $layout_srl * @return array image list in layout */ - function getUserLayoutImageList($layout_srl) + public static function getUserLayoutImageList($layout_srl) { - return FileHandler::readDir($this->getUserLayoutImagePath($layout_srl)); + return FileHandler::readDir(self::getUserLayoutImagePath($layout_srl)); } /** @@ -771,12 +625,12 @@ class layoutModel extends layout * @param string $layout_name * @return array */ - function getUserLayoutIniConfig($layout_srl, $layout_name=null) + public function getUserLayoutIniConfig($layout_srl, $layout_name=null) { - $file = $this->getUserLayoutIni($layout_srl); + $file = self::getUserLayoutIni($layout_srl); if($layout_name && FileHandler::exists($file) === FALSE) { - FileHandler::copyFile($this->getDefaultLayoutIni($layout_name),$this->getUserLayoutIni($layout_srl)); + FileHandler::copyFile($this->getDefaultLayoutIni($layout_name), $this->getUserLayoutIni($layout_srl)); } return FileHandler::readIniFile($file); @@ -787,7 +641,7 @@ class layoutModel extends layout * @param int $layout_srl * @return string */ - function getUserLayoutPath($layout_srl) + public static function getUserLayoutPath($layout_srl) { return sprintf("./files/faceOff/%s", getNumberingPath($layout_srl,3)); } @@ -797,9 +651,9 @@ class layoutModel extends layout * @param int $layout_srl * @return string */ - function getUserLayoutImagePath($layout_srl) + public static function getUserLayoutImagePath($layout_srl) { - return $this->getUserLayoutPath($layout_srl). 'images/'; + return self::getUserLayoutPath($layout_srl). 'images/'; } /** @@ -807,9 +661,9 @@ class layoutModel extends layout * @param int $layout_srl * @return string */ - function getUserLayoutCss($layout_srl) + public static function getUserLayoutCss($layout_srl) { - return $this->getUserLayoutPath($layout_srl). 'layout.css'; + return self::getUserLayoutPath($layout_srl). 'layout.css'; } /** @@ -817,7 +671,7 @@ class layoutModel extends layout * @param int $layout_srl * @return string */ - function getUserLayoutFaceOffCss($layout_srl) + public function getUserLayoutFaceOffCss($layout_srl) { if($this->useUserLayoutTemp == 'temp') return; return $this->_getUserLayoutFaceOffCss($layout_srl); @@ -828,9 +682,9 @@ class layoutModel extends layout * @param int $layout_srl * @return string */ - function _getUserLayoutFaceOffCss($layout_srl) + public function _getUserLayoutFaceOffCss($layout_srl) { - return $this->getUserLayoutPath($layout_srl). 'faceoff.css'; + return self::getUserLayoutPath($layout_srl). 'faceoff.css'; } /** @@ -838,9 +692,9 @@ class layoutModel extends layout * @param int $layout_srl * @return string */ - function getUserLayoutTempFaceOffCss($layout_srl) + public static function getUserLayoutTempFaceOffCss($layout_srl) { - return $this->getUserLayoutPath($layout_srl). 'tmp.faceoff.css'; + return self::getUserLayoutPath($layout_srl). 'tmp.faceoff.css'; } /** @@ -848,9 +702,9 @@ class layoutModel extends layout * @param int $layout_srl * @return string */ - function getUserLayoutHtml($layout_srl) + public function getUserLayoutHtml($layout_srl) { - $src = $this->getUserLayoutPath($layout_srl). 'layout.html'; + $src = self::getUserLayoutPath($layout_srl). 'layout.html'; if($this->useUserLayoutTemp == 'temp') { $temp = $this->getUserLayoutTempHtml($layout_srl); @@ -866,9 +720,9 @@ class layoutModel extends layout * @param int $layout_srl * @return string */ - function getUserLayoutTempHtml($layout_srl) + public static function getUserLayoutTempHtml($layout_srl) { - return $this->getUserLayoutPath($layout_srl). 'tmp.layout.html'; + return self::getUserLayoutPath($layout_srl). 'tmp.layout.html'; } /** @@ -876,12 +730,12 @@ class layoutModel extends layout * @param int $layout_srl * @return string */ - function getUserLayoutIni($layout_srl) + public function getUserLayoutIni($layout_srl) { - $src = $this->getUserLayoutPath($layout_srl). 'layout.ini'; + $src = self::getUserLayoutPath($layout_srl). 'layout.ini'; if($this->useUserLayoutTemp == 'temp') { - $temp = $this->getUserLayoutTempIni($layout_srl); + $temp = self::getUserLayoutTempIni($layout_srl); if(!file_exists(FileHandler::getRealPath($temp))) FileHandler::copyFile($src,$temp); return $temp; } @@ -894,9 +748,9 @@ class layoutModel extends layout * @param int $layout_srl * @return string */ - function getUserLayoutTempIni($layout_srl) + public static function getUserLayoutTempIni($layout_srl) { - return $this->getUserLayoutPath($layout_srl). 'tmp.layout.ini'; + return self::getUserLayoutPath($layout_srl). 'tmp.layout.ini'; } /** @@ -906,9 +760,9 @@ class layoutModel extends layout * @param string $lang_type * @return string */ - function getUserLayoutCache($layout_srl,$lang_type) + public static function getUserLayoutCache($layout_srl,$lang_type) { - return $this->getUserLayoutPath($layout_srl). "{$lang_type}.cache.php"; + return self::getUserLayoutPath($layout_srl). "{$lang_type}.cache.php"; } /** @@ -917,15 +771,15 @@ class layoutModel extends layout * @param string $lang_type * @return string */ - function getLayoutCache($layout_name,$lang_type,$layout_type='P') + public static function getLayoutCache($layout_name,$lang_type,$layout_type='P') { if($layout_type=='P') { - return sprintf("%sfiles/cache/layout/%s.%s.cache.php", _XE_PATH_, $layout_name,$lang_type); + return sprintf("%sfiles/cache/layout/%s.%s.cache.php", RX_BASEDIR, $layout_name,$lang_type); } else { - return sprintf("%sfiles/cache/layout/m.%s.%s.cache.php", _XE_PATH_, $layout_name,$lang_type); + return sprintf("%sfiles/cache/layout/m.%s.%s.cache.php", RX_BASEDIR, $layout_name,$lang_type); } } @@ -934,7 +788,7 @@ class layoutModel extends layout * @param string $layout_name * @return string */ - function getDefaultLayoutIni($layout_name) + public function getDefaultLayoutIni($layout_name) { return $this->getDefaultLayoutPath($layout_name). 'layout.ini'; } @@ -944,7 +798,7 @@ class layoutModel extends layout * @param string $layout_name * @return string */ - function getDefaultLayoutHtml($layout_name) + public function getDefaultLayoutHtml($layout_name) { return $this->getDefaultLayoutPath($layout_name). 'layout.html'; } @@ -954,7 +808,7 @@ class layoutModel extends layout * @param string $layout_name * @return string */ - function getDefaultLayoutCss($layout_name) + public function getDefaultLayoutCss($layout_name) { return $this->getDefaultLayoutPath($layout_name). 'css/layout.css'; } @@ -964,7 +818,7 @@ class layoutModel extends layout * @deprecated * @return string */ - function getDefaultLayoutPath() + public function getDefaultLayoutPath() { return "./modules/layout/faceoff/"; } @@ -974,7 +828,7 @@ class layoutModel extends layout * @param string $layout_name * @return boolean (true : faceoff, false : layout) */ - function useDefaultLayout($layout_name) + public function useDefaultLayout($layout_name) { $info = $this->getLayoutInfo($layout_name); return ($info->type == 'faceoff'); @@ -985,7 +839,7 @@ class layoutModel extends layout * @param string $flag (default 'temp') * @return void */ - function setUseUserLayoutTemp($flag='temp') + public function setUseUserLayoutTemp($flag='temp') { $this->useUserLayoutTemp = $flag; } @@ -995,7 +849,7 @@ class layoutModel extends layout * @param int $layout_srl * @return array temp files info */ - function getUserLayoutTempFileList($layout_srl) + public function getUserLayoutTempFileList($layout_srl) { return array( $this->getUserLayoutTempHtml($layout_srl), @@ -1009,7 +863,7 @@ class layoutModel extends layout * @param int $layout_srl * @return array files info */ - function getUserLayoutFileList($layout_srl) + public function getUserLayoutFileList($layout_srl) { $file_list = array( basename($this->getUserLayoutHtml($layout_srl)), @@ -1034,7 +888,7 @@ class layoutModel extends layout * @param object $layout_info * @return void */ - function doActivateFaceOff(&$layout_info) + public function doActivateFaceOff(&$layout_info) { $layout_info->faceoff_ini_config = $this->getUserLayoutIniConfig($layout_info->layout_srl, $layout_info->layout); // faceoff layout CSS @@ -1056,7 +910,7 @@ class layoutModel extends layout // Display menu when editing the faceOff page if(Context::get('act')=='dispLayoutAdminLayoutModify' && ($logged_info->is_admin == 'Y' || $logged_info->is_site_admin)) { - $oTemplate = &TemplateHandler::getInstance(); + $oTemplate = TemplateHandler::getInstance(); Context::addBodyHeader($oTemplate->compile($this->module_path.'/tpl', 'faceoff_layout_menu')); } } diff --git a/modules/layout/layout.view.php b/modules/layout/layout.view.php index b1f24d962..b83713a16 100644 --- a/modules/layout/layout.view.php +++ b/modules/layout/layout.view.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * admin view class of the layout module */ -class layoutView extends layout +class LayoutView extends Layout { /** * Initialization @@ -35,18 +35,17 @@ class layoutView extends layout /** * Preview a layout with module. - * + * * @return Object */ public function dispLayoutPreviewWithModule() { $content = ''; - $layoutSrl = Context::get('layout_srl'); - - $module = Context::get('module_name'); - $mid = Context::get('target_mid'); - $skin = Context::get('skin'); - $skinType = Context::get('skin_type'); + $layoutSrl = intval(Context::get('layout_srl')); + $module = preg_replace('/[^a-zA-Z0-9_]/', '', Context::get('module_name')); + $mid = preg_replace('/[^a-zA-Z0-9\/_-]/', '', Context::get('target_mid')); + $skin = preg_replace('/[^a-zA-Z0-9_-]/', '', Context::get('skin')); + $skinType = Context::get('skin_type') === 'M' ? 'M' : 'P'; try { @@ -87,12 +86,12 @@ class layoutView extends layout if ($skinType == 'M') { - $templatePath = _XE_PATH_ . 'modules/page/m.skins/' . $skin; + $templatePath = RX_BASEDIR . 'modules/page/m.skins/' . $skin; $templateFile = 'mobile'; } else { - $templatePath = _XE_PATH_ . 'modules/page/skins/' . $skin; + $templatePath = RX_BASEDIR . 'modules/page/skins/' . $skin; $templateFile = 'content'; } @@ -113,7 +112,7 @@ class layoutView extends layout if($layoutSrl == -1) { $site_srl = ($oModule) ? $oModule->module_info->site_srl : 0; - $designInfoFile = sprintf(_XE_PATH_ . 'files/site_design/design_%d.php', $site_srl); + $designInfoFile = sprintf(RX_BASEDIR . 'files/site_design/design_%d.php', $site_srl); include($designInfoFile); if($skinType == 'M') @@ -228,7 +227,6 @@ class layoutView extends layout Context::set('layout','none'); // Convert widgets and others - $oContext = Context::getInstance(); Context::set('layout_tpl', $layout_tpl); $this->setTemplatePath($this->module_path.'tpl'); $this->setTemplateFile('layout_preview'); @@ -261,7 +259,7 @@ class layoutView extends layout { throw new Rhymix\Framework\Exception(lang('msg_unabled_preview')); } - + $mid = current($output->data)->mid; } @@ -297,6 +295,12 @@ class layoutView extends layout $oModuleHandler->module_info->skin = $skin; } + // Remove unnecessary variables + Context::set('success_return_url', null); + Context::set('error_return_url', null); + Context::set('skin_type', null); + Context::set('skin_vars', null); + // Proc module $oModule = $oModuleHandler->procModule(); if(!$oModule->toBool()) @@ -305,7 +309,7 @@ class layoutView extends layout } // get module html - require_once(_XE_PATH_ . "classes/display/HTMLDisplayHandler.php"); + require_once(RX_BASEDIR . "classes/display/HTMLDisplayHandler.php"); $handler = new HTMLDisplayHandler(); return $handler->toDoc($oModule); } @@ -316,7 +320,7 @@ class layoutView extends layout */ function dispLayoutPreview() { - if(!checkCSRF()) + if(!Rhymix\Framework\Security::checkCSRF()) { throw new Rhymix\Framework\Exceptions\InvalidRequest; } @@ -362,11 +366,11 @@ class layoutView extends layout Context::set('layout_info', $layout_info); Context::set('content', lang('layout_preview_content')); // Temporary save the codes - $edited_layout_file = _XE_PATH_ . 'files/cache/layout/tmp.tpl'; + $edited_layout_file = RX_BASEDIR . 'files/cache/layout/tmp.tpl'; FileHandler::writeFile($edited_layout_file, $code); // Compile - $oTemplate = &TemplateHandler::getInstance(); + $oTemplate = TemplateHandler::getInstance(); $layout_path = $layout_info->path; $layout_file = 'layout'; @@ -374,7 +378,6 @@ class layoutView extends layout $layout_tpl = $oTemplate->compile($layout_path, $layout_file, $edited_layout_file); Context::set('layout','none'); // Convert widgets and others - $oContext = &Context::getInstance(); Context::set('layout_tpl', $layout_tpl); // Delete Temporary Files FileHandler::removeFile($edited_layout_file); @@ -392,7 +395,7 @@ class layoutView extends layout } else { - return ''; + return ''; } } @@ -408,7 +411,7 @@ class layoutView extends layout } else { - return ''; + return ''; } } diff --git a/modules/layout/queries/getLayoutList.xml b/modules/layout/queries/getLayoutList.xml index 7695c19af..e46491ef1 100644 --- a/modules/layout/queries/getLayoutList.xml +++ b/modules/layout/queries/getLayoutList.xml @@ -6,7 +6,7 @@ - + diff --git a/modules/layout/queries/insertLayout.xml b/modules/layout/queries/insertLayout.xml index 1bf9a01e8..4a5ffa8b9 100644 --- a/modules/layout/queries/insertLayout.xml +++ b/modules/layout/queries/insertLayout.xml @@ -4,7 +4,7 @@ - + diff --git a/modules/layout/schemas/layouts.xml b/modules/layout/schemas/layouts.xml index 1e6be0121..2a6d31bb3 100644 --- a/modules/layout/schemas/layouts.xml +++ b/modules/layout/schemas/layouts.xml @@ -1,11 +1,11 @@ - - + + - +
                diff --git a/modules/layout/tpl/installed_layout_list.html b/modules/layout/tpl/installed_layout_list.html index 8cfaea6e3..343e51ee7 100644 --- a/modules/layout/tpl/installed_layout_list.html +++ b/modules/layout/tpl/installed_layout_list.html @@ -14,7 +14,6 @@ {$lang->version} {$lang->author} {$lang->path} - {$lang->cmd_delete} @@ -27,7 +26,13 @@ {$lang->msg_avail_easy_update} {$lang->msg_do_you_like_update}

                - {$layout->version} + + + Rhymix Core + + {$layout->version} + + {$author->name} @@ -47,7 +52,6 @@ - - {$layout->path} - {$lang->cmd_delete} diff --git a/modules/layout/tpl/js/adminEdit.js b/modules/layout/tpl/js/adminEdit.js index 21f9aee5f..b8defafb7 100644 --- a/modules/layout/tpl/js/adminEdit.js +++ b/modules/layout/tpl/js/adminEdit.js @@ -10,3 +10,12 @@ function doPreviewLayoutCode() $form.removeAttr('target'); $act.val(og_act); } + +$(function() { + $('.reset_layout').on('click', function(e) { + var msg = $(this).data('confirmationMsg'); + if (!confirm(msg)) { + e.preventDefault(); + } + }); +}); diff --git a/modules/layout/tpl/layout_all_instance_list.html b/modules/layout/tpl/layout_all_instance_list.html index da1a1b10c..f4568d616 100644 --- a/modules/layout/tpl/layout_all_instance_list.html +++ b/modules/layout/tpl/layout_all_instance_list.html @@ -32,7 +32,13 @@ {$item->title} ({$layout_name}) {zdate($item->regdate, "Y-m-d")} {$lang->cmd_setup} - {$lang->cmd_edit} + + + {$lang->cmd_edit} + + 편집 + + {$lang->cmd_copy}
                diff --git a/modules/layout/tpl/layout_edit.html b/modules/layout/tpl/layout_edit.html index 86a0f5690..15a51f97a 100644 --- a/modules/layout/tpl/layout_edit.html +++ b/modules/layout/tpl/layout_edit.html @@ -1,6 +1,20 @@ -

                {nl2br($lang->about_layout_code)}

                + +
                +

                {$lang->layout_editing_deprecated_p2}

                +

                {$lang->layout_editing_deprecated_p3}

                +
                  +
                • files/faceOff/{getNumberingPath($layout_srl)}layout.html
                • +
                • files/faceOff/{getNumberingPath($layout_srl)}layout.css
                • +
                +

                {$lang->layout_editing_deprecated_p4}

                +
                  +
                • layouts/{$selected_layout->layout}/layout.html
                • +
                +

                {$lang->layout_editing_deprecated_p5}

                +
                +

                {$lang->layout_image_repository}

                {nl2br($lang->about_layout_image_repository)}

                @@ -69,7 +83,7 @@

                HTML - layout.html

                - +
                | {$widget->title} @@ -79,13 +93,13 @@

                CSS - layout.css

                - +
                - + diff --git a/modules/layout/tpl/layout_html_css_view.html b/modules/layout/tpl/layout_html_css_view.html index 7fb738c9f..6d09e664e 100644 --- a/modules/layout/tpl/layout_html_css_view.html +++ b/modules/layout/tpl/layout_html_css_view.html @@ -60,7 +60,7 @@

                HTML - layout.html

                - +
                | {$widget->title} @@ -84,7 +84,7 @@

                CSS - layout.css

                - +
                diff --git a/modules/layout/tpl/layout_info_view.html b/modules/layout/tpl/layout_info_view.html index da835a3d8..61d757cc3 100644 --- a/modules/layout/tpl/layout_info_view.html +++ b/modules/layout/tpl/layout_info_view.html @@ -45,7 +45,7 @@
                - + {$lang->about_header_script}
                @@ -53,10 +53,10 @@

                {$lang->extra_vars}

                {@$cnt = 1} -
                  - +
                    +
                  • {$var->group}
                  • - {@$group = $var->group} + {@$group = $var->group ?? null} {@$cnt ++}
                    @@ -64,7 +64,7 @@ {@$group = ''} {@$cnt = 1} - +
                    @@ -77,12 +77,12 @@
                    - ") !== false)-->{htmlspecialchars($var->value, ENT_COMPAT | ENT_HTML401, 'UTF-8', false)}{$var->value}" /> +
                    {@$use_multilang_textarea = true} - +
                    @@ -111,14 +111,14 @@ - +
                    -
                    +
                    {$val->val}
                    @@ -151,19 +151,13 @@
                    -
                    - -
                    - -
                    -
                - {$lang->cmd_list} - {$lang->cmd_list} + {$lang->cmd_list} + {$lang->cmd_list} diff --git a/modules/layout/tpl/layout_instance_list.html b/modules/layout/tpl/layout_instance_list.html index 702ea4fca..4382b6741 100644 --- a/modules/layout/tpl/layout_instance_list.html +++ b/modules/layout/tpl/layout_instance_list.html @@ -27,7 +27,13 @@ xe.lang.confirm_delete = '{$lang->confirm_delete}'; {$layout->title} {zdate($layout->regdate, "Y-m-d")} {$lang->cmd_setup} - {$lang->cmd_edit} + + + {$lang->cmd_edit} + + 편집 + + {$lang->cmd_copy} diff --git a/modules/member/conf/info.xml b/modules/member/conf/info.xml index aa0a672de..38c99474d 100644 --- a/modules/member/conf/info.xml +++ b/modules/member/conf/info.xml @@ -18,8 +18,8 @@ Этот модуль служит для управления и конфигурирования пользователей. 對會員進行管理與相關設置的模組。 Bu modül üyeleri yönetmek/yapılandırmak için kullanılır - 1.7 - 2013-11-27 + RX_VERSION + RX_CORE member diff --git a/modules/member/conf/module.xml b/modules/member/conf/module.xml index a09502bc5..a6aabadb5 100644 --- a/modules/member/conf/module.xml +++ b/modules/member/conf/module.xml @@ -2,39 +2,55 @@ - - + + + + + + + + + + + + + + + - + - - - - - + + + + + - + - + + - - + + - + - + - + + - + + @@ -56,8 +72,13 @@ - - + + + + + + + @@ -69,11 +90,11 @@ - + - + @@ -98,6 +119,11 @@ + + + + + Member List diff --git a/modules/member/controllers/Device.php b/modules/member/controllers/Device.php new file mode 100644 index 000000000..8efe7564b --- /dev/null +++ b/modules/member/controllers/Device.php @@ -0,0 +1,423 @@ +_getDeviceToken(); + if ($device_token) + { + $output = executeQuery('member.getMemberDevice', ['device_token' => $device_token]); + if (!$output->data || $output->data->member_srl != $member_srl) + { + $output = $this->procMemberRegisterDevice($member_srl, $device_token); + if ($output instanceof \BaseObject && !$output->toBool()) + { + return $output; + } + $this->_setDeviceKey(); + } + else + { + executeQuery('member.updateMemberDeviceLastActiveDate', ['device_token' => $device_token]); + } + } + return new \BaseObject; + } + + /** + * Automatically recognize device token from header or cookie and unregister it. + * + * @return \BaseObject + */ + public function autoUnregisterDevice(int $member_srl): \BaseObject + { + $device_token = $this->_getDeviceToken(); + if ($device_token) + { + $output = executeQuery('member.getMemberDevice', ['device_token' => $device_token]); + if ($output->data && $output->data->member_srl == $member_srl) + { + $args = new \stdClass; + $args->device_token = $output->data->device_token; + $output = executeQuery('member.deleteMemberDevice', $args); + return $output; + } + } + return new \BaseObject; + } + + /** + * Register device + */ + public function procMemberRegisterDevice($member_srl = null, $device_token = null) + { + // Set the response method to JSON, but only if this method was called directly. + // The response method will remain unchanged, for example, when this method was called by autoRegisterDevice() + if (\Context::get('act') === 'procMemberRegisterDevice') + { + \Context::setResponseMethod('JSON'); + } + + // Check user_id, password, device_token + $allow_guest_device = config('push.allow_guest_device'); + $user_id = \Context::get('user_id'); + $password = \Context::get('password'); + $device_token = $device_token ?? \Context::get('device_token'); + $device_model = escape(\Context::get('device_model')); + + // Return an error when id and password doesn't exist + if (!$member_srl && $this->user->member_srl) + { + $member_srl = $this->user->member_srl; + } + if (!$member_srl && !$user_id && !$allow_guest_device) + { + return new \BaseObject(-1, 'NULL_USER_ID'); + } + if (!$member_srl && !$password && !$allow_guest_device) + { + return new \BaseObject(-1, 'NULL_PASSWORD'); + } + if (!$device_token) + { + return new \BaseObject(-1, 'NULL_DEVICE_TOKEN'); + } + + // Get device information + $browserInfo = \Rhymix\Framework\UA::getBrowserInfo(); + $device_type = escape(strtolower($browserInfo->os)); + $device_version = escape(strval($browserInfo->os_version)); + if (!$device_model) + { + $device_model = escape($browserInfo->device); + } + + // Detect device token type + if (preg_match('/^[0-9a-z]{64}$/', $device_token)) + { + $device_token_type = 'apns'; + } + elseif (preg_match('/^[0-9a-zA-Z:_-]+$/', $device_token) && strlen($device_token) > 64) + { + $device_token_type = 'fcm'; + } + else + { + return new \BaseObject(-1, 'INVALID_DEVICE_TOKEN'); + } + + if ($member_srl) + { + $member_srl = intval($member_srl); + } + elseif ($user_id && $password) + { + $output = \memberController::getInstance()->procMemberLogin($user_id, $password); + if(!$output->toBool()) + { + return new \BaseObject(-1, 'LOGIN_FAILED'); + } + $logged_info = \Context::get('logged_info'); + $member_srl = intval($logged_info->member_srl); + } + else + { + $logged_info = null; + $member_srl = 0; + } + + // Generate keys + $random_key = \Rhymix\Framework\Security::getRandom(); + $device_key = hash_hmac('sha256', $random_key, $member_srl . ':' . config('crypto.authentication_key')); + + // Prepare query arguments + $args = new \stdClass; + $args->device_srl = getNextSequence(); + $args->member_srl = $member_srl; + $args->device_token = $device_token; + $args->device_token_type = $device_token_type; + $args->device_key = $device_key; + $args->device_type = $device_type; + $args->device_version = $device_version; + $args->device_model = $device_model; + + // Call trigger (before) + $trigger_output = \ModuleHandler::triggerCall('member.insertMemberDevice', 'before', $args); + if(!$trigger_output->toBool()) return $trigger_output; + + // Start transaction + $oDB = \DB::getInstance(); + $oDB->begin(); + + // Remove duplicated token key + executeQuery('member.deleteMemberDevice', ['device_token' => $device_token]); + + // Create member_device + $output = executeQuery('member.insertMemberDevice', $args); + if(!$output->toBool()) + { + $oDB->rollback(); + return $output; + } + + // Call trigger (after) + \ModuleHandler::triggerCall('member.insertMemberDevice', 'after', $args); + + $oDB->commit(); + + // Set parameters + $this->add('member_srl', $member_srl); + $this->add('user_id', $logged_info ? $logged_info->user_id : null); + $this->add('user_name', $logged_info ? $logged_info->user_name : null); + $this->add('nick_name', $logged_info ? $logged_info->nick_name : null); + $this->add('device_key', $random_key); + } + + /** + * Automatically log-in to registered device + */ + public function procMemberLoginWithDevice() + { + \Context::setResponseMethod('JSON'); + + // Check member_srl, device_token, device_key + $allow_guest_device = config('push.allow_guest_device'); + $member_srl = abs(\Context::get('member_srl')); + $device_token = strval(\Context::get('device_token')); + $random_key = strval(\Context::get('device_key')); + + // Return an error when id, password and device_key doesn't exist + if (!$member_srl && !$allow_guest_device) + { + return new \BaseObject(-1, 'NULL_MEMBER_SRL'); + } + if (!$device_token) + { + return new \BaseObject(-1, 'NULL_DEVICE_TOKEN'); + } + if (!$random_key) + { + return new \BaseObject(-1, 'NULL_DEVICE_KEY'); + } + + // Check the device token and key. + $args = new \stdClass; + $args->member_srl = $member_srl; + $args->device_token = $device_token; + $args->device_key = hash_hmac('sha256', $random_key, $member_srl . ':' . config('crypto.authentication_key')); + $output = executeQuery('member.getMemberDevice', $args); + if (!$output->toBool()) + { + return new \BaseObject(-1, 'DEVICE_RETRIEVE_FAILED'); + } + if (!$output->data || !is_object($output->data)) + { + return new \BaseObject(-1, 'UNREGISTERED_DEVICE'); + } + + // Log-in + if($member_srl) + { + $config = \MemberModel::getMemberConfig(); + $member_info = \MemberModel::getMemberInfoByMemberSrl($member_srl); + if (in_array('user_id', $config->identifiers)) + { + $user_id = $member_info->user_id; + } + elseif (in_array('email_address', $config->identifiers)) + { + $user_id = $member_info->email_address; + } + else + { + if(isset($member_info->phone_number)) + { + $user_id = $member_info->phone_number; + } + else + { + return new \BaseObject(-1, 'LOGIN_FAILED'); + } + } + $output = \memberController::getInstance()->doLogin($user_id); + if(!$output->toBool()) + { + return new \BaseObject(-1, 'LOGIN_FAILED'); + } + } + else + { + $member_info = null; + } + + // Update last active date + executeQuery('member.updateMemberDeviceLastActiveDate', ['device_token' => $device_token]); + + $this->add('member_srl', $member_srl); + $this->add('user_id', $member_info ? $member_info->user_id : null); + $this->add('user_name', $member_info ? $member_info->user_name : null); + $this->add('nick_name', $member_info ? $member_info->nick_name : null); + } + + /** + * Unregister a registered device. + * + * This action requires a device token and matching device key. + * It is intended to be called by mobile applications. + */ + public function procMemberUnregisterDevice() + { + \Context::setResponseMethod('JSON'); + + // Check member_srl, device_token, device_key + $allow_guest_device = config('push.allow_guest_device'); + $member_srl = abs(\Context::get('member_srl')); + $device_token = strval(\Context::get('device_token')); + $random_key = strval(\Context::get('device_key')); + + // Return an error when id, password and device_key doesn't exist + if (!$member_srl && !$allow_guest_device) + { + return new \BaseObject(-1, 'NULL_MEMBER_SRL'); + } + if (!$device_token) + { + return new \BaseObject(-1, 'NULL_DEVICE_TOKEN'); + } + if (!$random_key) + { + return new \BaseObject(-1, 'NULL_DEVICE_KEY'); + } + + // Check the device token and key. + $args = new \stdClass; + $args->member_srl = $member_srl; + $args->device_token = $device_token; + $args->device_key = hash_hmac('sha256', $random_key, $member_srl . ':' . config('crypto.authentication_key')); + $output = executeQuery('member.getMemberDevice', $args); + if (!$output->toBool()) + { + return new \BaseObject(-1, 'DEVICE_RETRIEVE_FAILED'); + } + if (!$output->data || !is_object($output->data)) + { + return new \BaseObject(-1, 'UNREGISTERED_DEVICE'); + } + + // Delete the device. + $args = new \stdClass; + $args->device_token = $device_token; + $output = executeQuery('member.deleteMemberDevice', $args); + if (!$output->toBool()) + { + return new \BaseObject(-1, 'DELETE_FAILED'); + } + } + + /** + * Delete a registered device. + * + * This action requires only the device_srl, but it must belong to the currently logged in member. + * It is intended to be called from the web frontend. + */ + public function procMemberDeleteDevice() + { + // Check the device_srl and member_srl of the currently logged in member. + $device_srl = intval(\Context::get('device_srl')); + if (!$device_srl) + { + throw new \Rhymix\Framework\Exceptions\InvalidRequest; + } + + $member_srl = $this->user->member_srl; + if (!$member_srl) + { + throw new \Rhymix\Framework\Exceptions\NotPermitted; + } + + // Check that the device_srl matches the member. + $args = new \stdClass; + $args->device_srl = $device_srl; + $args->member_srl = $member_srl; + $output = executeQuery('member.getMemberDevice', $args); + if (!$output->data || !is_object($output->data)) + { + throw new \Rhymix\Framework\Exceptions\TargetNotFound; + } + + // Delete the device. + $args = new \stdClass; + $args->device_token = $output->data->device_token; + $output = executeQuery('member.deleteMemberDevice', $args); + return $output; + } + + /** + * Get device token from POST parameter, HTTP header or cookie + * + * @return string|null + */ + protected function _getDeviceToken() + { + // POST parameter named device_token + $device_token = $_POST['device_token'] ?? null; + if ($device_token && is_string($device_token) && $device_token !== '') + { + return $device_token; + } + + // HTTP header named X-Device-Token + $device_token = $_SERVER['HTTP_X_DEVICE_TOKEN'] ?? null; + if ($device_token) + { + return $device_token; + } + + // Cookie named device_token + $device_token = $_COOKIE['device_token'] ?? null; + if ($device_token) + { + return $device_token; + } + } + + /** + * Set device key via header or cookie + * + * @return void + */ + protected function _setDeviceKey() + { + $member_srl = $this->get('member_srl'); + $device_key = $this->get('device_key'); + if (!$member_srl || !$device_key) + { + return; + } + + // Set header if header was given, or cookie otherwise + if (isset($_SERVER['HTTP_X_DEVICE_TOKEN'])) + { + header('X-Device-Key: ' . urlencode($member_srl . ':' . $device_key)); + } + else + { + Cookie::set('device_key', $member_srl . ':' . $device_key, [ + 'expires' => time() + 60, + 'httponly' => true, + ]); + } + } +} diff --git a/modules/member/lang/en.php b/modules/member/lang/en.php index 55159127f..874008eb8 100644 --- a/modules/member/lang/en.php +++ b/modules/member/lang/en.php @@ -10,9 +10,9 @@ $lang->default_group_1 = 'Associate Member'; $lang->default_group_2 = 'Regular Member'; $lang->default_group = 'Default Group'; $lang->admin_group = 'Managing Group'; -$lang->keep_signed = 'Keep me signed in.'; +$lang->keep_signed = 'Keep me logged in.'; $lang->remember_user_id = 'Remember ID.'; -$lang->already_logged = 'You are already signed in.'; +$lang->already_logged = 'You are already logged in.'; $lang->denied_user_id = 'You have entered a prohibited ID.'; $lang->denied_nick_name = 'You have entered a prohibited nick name.'; $lang->managed_email_host['allowed'] = 'Only %s e-mail accounts are allowed. (%s)'; @@ -20,10 +20,11 @@ $lang->managed_email_host['prohibited'] = 'E-mail accounts at %s are not allowed $lang->null_user_id = 'Please enter your ID.'; $lang->null_password = 'Please enter your password.'; $lang->invalid_authorization = 'The account is not activated.'; -$lang->invalid_email_address = 'You have entered an invalid email address. There is no member who has the email, entered.'; -$lang->invalid_user_id = 'You have entered an invalid ID.'; -$lang->invalid_password = 'You have entered an invalid password.'; -$lang->invalid_new_password = 'Please enter a password you haven\'t previously used.'; +$lang->invalid_email_address = 'Login failed. Please check your e-mail address or password.'; +$lang->invalid_user_id = 'Login failed. Please check your ID or password.'; +$lang->invalid_password = 'The password is not correct.'; +$lang->invalid_current_password = 'The current password is not correct.'; +$lang->invalid_new_password = 'Please enter a different password.'; $lang->allow_mailing = 'Join Mailing'; $lang->is_admin = 'Superadmin Permission'; $lang->member_group = 'Member Group'; @@ -31,6 +32,11 @@ $lang->group_title = 'Group Name'; $lang->group_srl = 'Group Number'; $lang->group_order = 'Gropu Priority'; $lang->group_order_change = 'Change Group Priority'; +$lang->phone_number_default_country = 'Default Country Code'; +$lang->phone_number_hide_country = 'Hide Country Code Selection'; +$lang->phone_number_allow_duplicate = 'Allow Multiple Members'; +$lang->phone_number_verify_by_sms = 'Require SMS Verification'; +$lang->msg_need_default_country = 'You must select a default country code if you want to hide the country code selection box.'; $lang->signature = 'Signature'; $lang->profile_image = 'Profile Image'; $lang->profile_image_max_width = 'Max Width'; @@ -45,19 +51,27 @@ $lang->group_image_mark = 'Group image mark'; $lang->group_image_mark_max_width = 'Max Width'; $lang->group_image_mark_max_height = 'Max Height'; $lang->signature_max_height = 'Max Signature Height'; +$lang->cmd_force_member_mid = 'Force'; +$lang->msg_exists_member_mid = 'The URL \'%s\' required by the member module is already used by another module. Please change the URL of the other module and try again.'; $lang->enable_join = 'Accept New Members'; +$lang->enable_join_only_with_url_key = 'Only with valid URL key'; +$lang->enable_join_url_key = 'URL Key'; $lang->enable_confirm = 'Email Activation'; $lang->enable_find_account_question = 'Account recovery using question/answer'; $lang->enable_ssl = 'Enable SSL'; $lang->msg_email_confirmation_required = 'A confirmation e-mail will be sent. Please check your email address carefully.'; -$lang->security_sign_in = 'Sign in using enhanced security'; +$lang->cmd_authmail_expires = 'Activation Email Expiry'; +$lang->about_authmail_expires = 'Activation emails and password reset emails can be set to expire after a certain time.'; +$lang->security_sign_in = 'Login using enhanced security'; $lang->member_limited = 'Limited'; $lang->limit_day = 'Temporary Limit Date'; $lang->limit_day_description = 'Description for Temporary Limit Date'; $lang->limit_date = 'Limit Date'; -$lang->after_login_url = 'URL to redirect after Sign in'; -$lang->after_logout_url = 'URL to redirect after Sign out'; -$lang->redirect_url = 'URL to redirect after Sign up'; +$lang->cmd_special_phone_number = 'Verification code exception'; +$lang->cmd_max_auth_sms_count = 'Verification rate limit'; +$lang->after_login_url = 'URL to redirect after login'; +$lang->after_logout_url = 'URL to redirect after sign out'; +$lang->redirect_url = 'URL to redirect after sign up'; $lang->agreement = 'Terms of Service'; $lang->accept_agreement = 'Agree'; $lang->member_info = 'User Info'; @@ -67,28 +81,42 @@ $lang->allow_message_type['Y'] = 'Allow All'; $lang->allow_message_type['F'] = 'Allow for Friends only'; $lang->allow_message_type['N'] = 'Reject All'; $lang->about_allow_message = 'You may allow or reject messages.'; +$lang->verify_by_sms = 'Verify'; +$lang->verify_by_sms_code = 'Verification code'; +$lang->verify_by_sms_confirm = 'Confirm'; +$lang->verify_by_sms_message = 'Your verification code is %s.'; +$lang->verify_by_sms_code_sent = 'A verification code has been sent to the number you entered.'; +$lang->verify_by_sms_code_incorrect = 'The code you entered is incorrect.'; +$lang->verify_by_sms_code_too_many_tries = 'Too many incorrect attempts. Please request a new code.'; +$lang->verify_by_sms_code_expired = 'The code has expired. Please request a new code.'; +$lang->verify_by_sms_code_confirmed = 'Your phone number has been confirmed.'; +$lang->verify_by_sms_incomplete = 'Your phone number has not been verified. Please go through the verification process first.'; +$lang->verify_by_sms_error = 'This website cannot send SMS.'; $lang->logged_users = 'Logged on Users'; $lang->webmaster_name = 'Webmaster Name'; $lang->webmaster_email = 'Webmaster Email'; $lang->column_id = 'The column id'; $lang->options = 'Options'; -$lang->about_keep_signed = 'You will be still signed in even when the browser is closed.\\n\\nIt is not recommended to use this if you are using a public computer, for your personal information could be violated.'; -$lang->about_keep_warning = 'You will be still signed in even when the browser is closed. It is not recommended to use this if you are using a public computer, for your personal information could be violated'; +$lang->about_keep_signed = 'You will be still logged in even when the browser is closed.\\n\\nIt is not recommended to use this if you are using a public computer, for your personal information could be violated.'; +$lang->about_keep_warning = 'You will be still logged in even when the browser is closed. It is not recommended to use this if you are using a public computer, for your personal information could be violated'; $lang->about_webmaster_email = 'This setting can be changed in the Notification Settings screen.'; $lang->retroactive_application = 'retroact'; $lang->signature_html_retroact = 'also remove HTML in past inseted HTML signature. Can not be reversed.'; $lang->search_target_list['email_address'] = 'Email Address'; -$lang->search_target_list['regdate'] = 'Sign up Date'; -$lang->search_target_list['regdate_more'] = 'Sign up Date (more)'; -$lang->search_target_list['regdate_less'] = 'Sign up Date (less)'; -$lang->search_target_list['last_login'] = 'Last Sign in Date'; -$lang->search_target_list['last_login_more'] = 'Last Sign in Date (more)'; -$lang->search_target_list['last_login_less'] = 'Last Sign in Date (less)'; +$lang->search_target_list['phone_number'] = 'Phone Number'; +$lang->search_target_list['regdate'] = 'Sign Up Date'; +$lang->search_target_list['regdate_more'] = 'Sign Up Date (more)'; +$lang->search_target_list['regdate_less'] = 'Sign Up Date (less)'; +$lang->search_target_list['ipaddress'] = 'Sign up IP address'; +$lang->search_target_list['last_login'] = 'Last Login Date'; +$lang->search_target_list['last_login_more'] = 'Last Login Date (more)'; +$lang->search_target_list['last_login_less'] = 'Last Login Date (less)'; +$lang->search_target_list['last_login_ipaddress'] = 'Last Login IP address'; $lang->search_target_list['birthday'] = 'Birthday'; $lang->search_target_list['extra_vars'] = 'User Defined'; $lang->cmd_modify_new_auth_email_address = 'New email address'; $lang->cmd_set_design_info = 'Desgin'; -$lang->cmd_login = 'Sign In'; +$lang->cmd_login = 'Login'; $lang->cmd_logout = 'Sign Out'; $lang->cmd_signup = 'Sign Up'; $lang->cmd_site_signup = 'Sign Up'; @@ -101,7 +129,7 @@ $lang->cmd_leave = 'Delete Account'; $lang->cmd_find_member_account = 'Find Account Info'; $lang->cmd_find_member_account_with_email = 'Find Account with Email address'; $lang->cmd_find_member_account_with_email_question = 'Find Account with Q&A'; -$lang->cmd_resend_auth_mail = 'Request for Activation Mail'; +$lang->cmd_resend_auth_mail = 'Resend Activation Email'; $lang->cmd_send_auth_new_emaill_address = 'Request for activation mail to new email'; $lang->cmd_member_list = 'Member List'; $lang->cmd_module_config = 'Default Setting'; @@ -113,15 +141,23 @@ $lang->cmd_manage_nick_name = 'Prohibited NickNames'; $lang->cmd_manage_form = 'Signup Form'; $lang->cmd_view_own_document = 'My Articles'; $lang->cmd_view_own_comment = 'My Comments'; -$lang->cmd_view_active_logins = 'Active Logins'; +$lang->cmd_view_active_logins = 'Active login'; $lang->cmd_manage_member_info = 'Manage Member Info'; $lang->cmd_trace_document = 'Trace Written Articles'; $lang->cmd_trace_comment = 'Trace Written Comments'; $lang->cmd_view_scrapped_document = 'Scraps'; $lang->cmd_view_saved_document = 'Saved Articles'; $lang->cmd_send_email = 'Send Mail'; +$lang->cmd_modify_nickname_allow = 'Allow Nickname Change'; +$lang->cmd_modify_nickname_log = 'Nickname Change Log'; +$lang->cmd_nickname_symbols = 'Allow Symbols in Nickname'; +$lang->cmd_nickname_symbols_list = 'Only Allow:'; +$lang->cmd_nickname_allow_spaces = 'Allow Spaces'; +$lang->cmd_member_profile_view = 'Show member profile picture'; $lang->cmd_allow_duplicate_nickname = 'Allow Duplicate Nicknames'; $lang->about_allow_duplicate_nickname = 'Allow more than one member to use the same nickname.'; +$lang->msg_special_code_incorrect_format = 'The verification code should be a 6-digit number.'; +$lang->msg_auth_sms_rate_limited = 'Too many verification attempts. Please try again later.'; $lang->msg_email_not_exists = 'You have entered an invalid email address.'; $lang->msg_alreay_scrapped = 'This article is already scrapped.'; $lang->msg_folder_alreay_exists = 'A folder with the same name already exists.'; @@ -131,12 +167,17 @@ $lang->msg_cart_is_null = 'Please select the target.'; $lang->msg_checked_file_is_deleted = '%d attached file(s) is(are) deleted.'; $lang->msg_find_account_title = 'Account Info'; $lang->msg_find_account_info = 'This is requested account info.'; -$lang->msg_find_account_comment = 'The password will be modified as the one above as you click the link below.
                Please modify the password after login.'; +$lang->msg_find_account_comment = 'Your password will be changed to the one above if you click the link below.
                Please change the password again as soon as possible after you are able to log in.
                Your password will remain unchanged until you click the link below.'; +$lang->msg_find_account_comment_v2 = 'Click the link below to set a new password for your account, even if you don\'t remember your current password.'; $lang->msg_confirm_account_title = 'Rhymix Account Activation'; +$lang->title_modify_email_address = 'This letter is sent for a confirmation of the changing e-mail address.'; $lang->msg_confirm_account_info = 'This is your account information:'; $lang->msg_confirm_account_comment = 'Click on the following link to complete your account activation.'; +$lang->msg_confirm_email_address_change = 'The email address will be modified to %s after clicking below.'; $lang->msg_auth_mail_sent = 'The activation mail has been sent to %s. Please check your mail.'; $lang->msg_confirm_mail_sent = 'We have just sent the activation email to %s. Please check your mail.'; +$lang->msg_change_mail_sent = 'The letter was sent to %s for the changing email address. Please, check your email.'; +$lang->msg_invalid_modify_email_auth_key = 'Invalid request for changing the email address.
                Please, request again or contact the website administrator.'; $lang->msg_invalid_auth_key = 'This is an invalid request of verification.
                Please retry finding account info or contact the administrator.'; $lang->msg_expired_auth_key = 'Your verification link has expired. Please request a new verification email.'; $lang->msg_success_authed = 'Please use the password you received in the email to log in, and change it to a password of your choice as soon as possible.'; @@ -150,21 +191,29 @@ $lang->msg_not_delete_default = 'Default items cannot be deleted'; $lang->msg_not_exists_member = 'Invalid member'; $lang->msg_cannot_delete_admin = 'Admin ID cannot be deleted. Please remove the ID from administration and try again.'; $lang->msg_column_id_not_available = 'The ID already exists or is not usable.'; -$lang->msg_exists_user_id = 'This ID already exists. Please try another one.'; -$lang->msg_exists_email_address = 'This email address already exists. Please try another one.'; +$lang->msg_exists_user_id = 'This username already exists. Please try another one.'; +$lang->msg_exists_email_address = 'This email address has already been registered.'; +$lang->msg_exists_phone_number = 'This phone number has already been registered.'; +$lang->msg_invalid_phone_country = 'The phone number belongs to an unsupported country.'; +$lang->msg_invalid_phone_number = 'The phone number you have entered is invalid.'; $lang->msg_exists_nick_name = 'This nickname already exists. Please try another one.'; +$lang->msg_nickname_not_changeable = 'The nickname cannot be changed.'; +$lang->msg_email_address_not_changeable = 'You cannot change your email address here. Please use the "Change Email Address" page to do so.'; $lang->msg_signup_disabled = 'You are not able to sign up'; $lang->msg_already_logged = 'You have already signed up.'; -$lang->msg_not_logged = 'Please sign in first.'; +$lang->msg_not_logged = 'Please log in.'; +$lang->msg_required_not_logged = 'This page is only available to users who are not logged in.'; +$lang->msg_required_specific_group = 'You need to belong to a certain group in order to access this page.'; +$lang->msg_required_minimum_level = 'YOu need to be level %d or higher in order to access this page.'; $lang->msg_insert_group_name = 'Please enter the name of group.'; $lang->msg_check_group = 'Please select the group.'; $lang->msg_not_uploaded_profile_image = 'Profile image could not be registered.'; $lang->msg_not_uploaded_image_name = 'Image name could not be registered.'; $lang->msg_not_uploaded_image_mark = 'Image mark could not be registered.'; $lang->msg_not_uploaded_group_image_mark = 'Group image mark could not be registered.'; -$lang->msg_accept_agreement = 'You must accept all required agreements in order to sign up.'; +$lang->msg_accept_agreement = 'You should accept all required agreements in order to sign up.'; $lang->msg_user_denied = 'You have entered a prohibited ID.'; -$lang->msg_user_not_confirmed = 'Your account is not activated yet. Please check your email.'; +$lang->msg_user_not_confirmed = 'Your account is not activated yet. If you just signed up, please check the confirmation email sent to %s. If you have not received the confirmation email, please visit the Resend Activation Email menu.'; $lang->msg_user_limited = 'You have entered an ID that cannot be used before %s'; $lang->msg_admin_ip_not_allowed = 'Your IP address is not allowed to log in as an administrator.'; $lang->about_rechecked_password = 'Confirm your password before editing account information.'; @@ -174,18 +223,24 @@ $lang->cmd_config_password_strength = 'password strength'; $lang->cmd_password_hashing_algorithm = 'Password Hashing Algorithm'; $lang->cmd_password_hashing_work_factor = 'Password Hashing Work Factor'; $lang->cmd_password_hashing_auto_upgrade = 'Auto-upgrade Hashing Algorithm'; -$lang->cmd_password_change_invalidate_other_sessions = 'Log out other devices on password change'; +$lang->cmd_password_change_invalidate_other_sessions = 'Sign out other devices on password change'; +$lang->cmd_password_reset_method = 'Password Reset'; +$lang->cmd_password_reset_method_v1 = 'Email random password'; +$lang->cmd_password_reset_method_v2 = 'Email link to password reset page (recommended)'; +$lang->cmd_login_invalidate_other_sessions = 'Sign out other devices'; $lang->password_strength_low = 'low'; $lang->password_strength_normal = 'normal'; $lang->password_strength_high = 'high'; -$lang->about_password_strength_config = 'When members register or change the password, the password must meet the specified password strength. However, the administrator is an exception.'; -$lang->about_password_hashing_algorithm = 'You can choose how to encrypt (hash) members\' passwords stored in the database.'; -$lang->about_password_hashing_work_factor = 'Higher work factors are more secure, but logins may take a long time. This only applies to bcrypt and pbkdf2.'; -$lang->about_password_hashing_auto_upgrade = 'Passwords encrypted using different algorithms will be automatically converted to the configured algorithm at next login.'; -$lang->about_password_change_invalidate_other_sessions = 'Log out all other devices (browsers) when a member changes the password.'; -$lang->about_password_strength['low'] = 'the password must be at least 4'; -$lang->about_password_strength['normal'] = 'the password must be at least 6, and must have at least one alpha character and numeric characters'; -$lang->about_password_strength['high'] = 'the password must be at least 8, and must have at least one alpha character, numeric character and special character '; +$lang->about_password_strength_config = 'When members register or change the password, the password should meet the specified password strength. However, the administrator is an exception.'; +$lang->about_password_hashing_algorithm = 'Choose how to encrypt (hash) passwords stored in the database.
                For better security, it is recommened that you choose one of bcrypt, pbkdf2, or argon2id.'; +$lang->about_password_hashing_work_factor = 'Higher work factors result in more security, but at the cost of delays at login and increased server load.
                With bcrypt, each step takes twice as long as the step below. Similar conversions are applied to pbkdf2 and argon2id.'; +$lang->about_password_hashing_auto_upgrade = 'Passwords not encrypted using the method configured above will be automatically converted to the configured method at next login.'; +$lang->about_password_change_invalidate_other_sessions = 'Sign out all other devices (browsers) when a member changes the password.'; +$lang->about_password_reset_method = 'Select how to assign a new password in the Find Member Account feature.'; +$lang->about_login_invalidate_other_sessions = 'Allow login from only one device. Previously used machines will be signed out automatically.'; +$lang->about_password_strength['low'] = 'the password should be longer, at least four characters'; +$lang->about_password_strength['normal'] = 'the password should be at least six characters, and should have at least one alpha character and numeric characters'; +$lang->about_password_strength['high'] = 'the password should be at least eight characters, and should have at least one alphabetic, numeric and special characters'; $lang->about_user_name = 'Name should be 2~20 letters long.'; $lang->about_nick_name = 'Nickname should be 2~20 characters long.'; $lang->about_email_address = 'Email address will be used to modify/find password after email verification.'; @@ -202,15 +257,17 @@ $lang->about_column_name = 'Please enter English name that can be used in the te $lang->about_column_title = 'This will be displayed on signup or modifying/viewing member info form.'; $lang->about_default_value = 'You can set the values to enter by default.'; $lang->about_active = 'You have to check on active items to show on signup form.'; -$lang->about_emailhost_check = 'Empty value will allow almost all email account providers.
                You can set new member\'s e-mail address providers. You can allow or prohibit some e-mail hosts(eg.: naver.com, gmail.com).'; +$lang->about_emailhost_check = 'You can limit new member\'s e-mail address providers. You can allow or prohibit some e-mail hosts (eg.: naver.com, gmail.com).
                If the list is empty, all e-mail hosts will be allowed.'; $lang->about_form_description = 'If you enter description in this form, it will be displayed on join form.'; $lang->about_required = 'Check this to make it mandatory item when signing up.'; -$lang->about_enable_join = 'Please check this if you want to allow new members to sign up your site.'; -$lang->about_enable_confirm = 'Please check if you want new members to activate their accounts via their emails.'; +$lang->about_enable_join = 'Please check if you want to accept new members. If you require a URL key, only those who visit with the corresponding string in the URL will be able to join.'; +$lang->about_enable_confirm = 'An activation email will be sent to new members. They must click a link in the email to complete the sign up process.'; $lang->about_enable_find_account_question = 'Check if you want to allow members to recover their accounts using a security question and answer.'; -$lang->about_enable_ssl = 'Personal information from Sign up/Modify Member Info/Sign in can be sent as SSL(https) mode if server provides SSL service.'; +$lang->about_enable_ssl = 'Personal information from Sign up/Modify Member Info/Login can be sent as SSL(https) mode if server provides SSL service.'; $lang->about_limit_day = 'You can limit activation date after sign up'; -$lang->about_limit_date = 'Users cannot sign in until the specified date'; +$lang->about_limit_date = 'Users cannot log in until the specified date'; +$lang->about_special_phone_number = 'You can set up a special phone number that can be verified with a preconfigured code instead of sending an actual SMS.
                The special phone number will also be allowed to sign up more than once even if this is normally disallowed.
                This can be useful for development, testing, and app store review process.'; +$lang->about_max_auth_sms_count = 'You can limit the number of time someone can try to verify the same phone number from the same IP address.'; $lang->about_after_login_url = 'You can set a URL after login. Blank means the current page.'; $lang->about_after_logout_url = 'You can set a URL after logout. Blank means the current page.'; $lang->about_redirect_url = 'Please select a page where users will go after sign up. When this is empty, it will be set as the previous page of the sign up page.'; @@ -224,11 +281,17 @@ $lang->about_accept_agreement = 'I have read the above and agree with it.'; $lang->about_member_default = 'It will be set as the default group on sign up'; $lang->about_find_member_account = 'Please input the email address you have entered during the registration and we will send your account info to this email address.'; $lang->about_ssl_port = 'Please enter if you are using non-default SSL port'; -$lang->about_resend_auth_mail = 'You can request for the activation email if you have not activated yet.'; +$lang->about_resend_auth_mail = 'You can request the activation email again if you have not received it.'; +$lang->about_reset_auth_mail_submit = 'If you use the address as an identifier, a new email address would be required for the login.'; +$lang->about_allow_nickname_change = 'Allow members to change their nicknames. This requires a skin that displays the nickname change input.'; +$lang->about_update_nickname_log = 'Record the nickname change history. If you use this option, you can see the nickname changing history.'; +$lang->about_nickname_symbols = 'Allow or prohibit the use of special characters in nicknames.'; +$lang->about_member_profile_view = 'Option to view the member\'s profile image on the admin member list page. Select No if you do not want to see your profile picture in the member panel.'; $lang->no_article = 'No articles'; $lang->find_account_question = 'Question for a temporary password.'; $lang->find_account_answer = 'Answer for a temporary password.'; $lang->about_find_account_question = 'You can get a temporary password by your ID, email address, and the answer for the question you have set.'; +$lang->find_account_question_items['0'] = ''; $lang->find_account_question_items['1'] = 'What is your alternate email address?'; $lang->find_account_question_items['2'] = 'What is your favorite thing?'; $lang->find_account_question_items['3'] = 'Which elementary school did you attend?'; @@ -241,16 +304,17 @@ $lang->find_account_question_items['9'] = 'What is your favorite food?'; $lang->temp_password = 'Temporary password'; $lang->cmd_get_temp_password = 'Get a temporary password'; $lang->about_get_temp_password = 'Change your password after you logged in.'; +$lang->msg_activation_not_needed = 'Your account is already activated.'; +$lang->msg_activation_key_not_found = 'There is no activation email to resend.'; +$lang->msg_feature_deprecated = 'This feature is deprecated.'; $lang->msg_question_not_allowed = 'The administrator has disabled this function.'; $lang->msg_question_not_exists = 'You haven`t set your question for a temporary password.'; $lang->msg_answer_not_matches = 'Your answer for the question is not correct.'; +$lang->msg_invalid_symbol_in_nickname = 'Your nickname contains a disallowed symbol, \'%s\'.'; $lang->change_password_date = 'Password renewal cycle'; $lang->about_change_password_date = 'If you set a value to this, you will be notified to change your password periodically. (If set to 0, disabled)'; $lang->msg_change_password_date = 'You have not changed the password during %s days. For personal information protection, you need to change the password.'; -$lang->login_trial_limit1 = 'Sign in trial limit'; -$lang->login_trial_limit2 = 'Sign in trial limit'; -$lang->about_login_trial_limit1 = 'Set the number of trial limit. Limit the number of trial to sign in from a IP address.'; -$lang->about_login_trial_limit2 = 'Set the time limit to try the written times of sign in. Limit the number of trial to sign in during the span of time, from a IP address.'; +$lang->about_login_trial_limit = 'Limit the number of login attempts in a short time from the same IP address.'; $lang->msg_kr_address = 'Search for the name of eup, myeon or dong of your address.'; $lang->msg_kr_address_etc = 'Enter the rest of your address.'; $lang->cmd_search_again = 'Search again'; @@ -269,11 +333,11 @@ $lang->cmd_optional = 'Optional'; $lang->cmd_disabled = 'Disabled'; $lang->cmd_image_max_width = 'Max Width'; $lang->cmd_image_max_height = 'Max Height'; -$lang->cmd_input_extend_form = 'User Defined Input'; +$lang->cmd_image_force_ratio = 'Fixed Aspect Ratio'; +$lang->cmd_input_extend_form = 'Add Signup Form Field'; $lang->about_multi_type = 'Enter the value of multi-or single-item selection.(separated by line breaks)'; $lang->msg_delete_extend_form = 'Delete the selected item.'; $lang->set_manage_id = 'Separated by line breaks.'; -$lang->count_manage_email_host = 'There are %s %s e-mail address providers below.'; $lang->count_manage_id = 'There are %s prohibited ID.'; $lang->count_manage_nick_name = 'There are %s prohibited nick name.'; $lang->user_list = 'Member List'; @@ -282,8 +346,10 @@ $lang->cmd_show_super_admin_member = 'Super Admin'; $lang->cmd_show_site_admin_member = 'Site Admin'; $lang->approval = 'Approval'; $lang->denied = 'Denied'; -$lang->refused_reason = 'Reason for account refusing'; -$lang->about_refused_reason = 'Describe the reason why you refuse this account. Something you write in this field would be displayed when this user signs in.'; +$lang->refused_reason = 'Reason for account suspension'; +$lang->about_refused_reason = 'This message will be displayed to the user if they try to log in.'; +$lang->limited_reason = 'Reason for limit'; +$lang->about_limited_reason = 'This message will be displayed to the user if they try to log in.'; $lang->use_group_image_mark = 'Use group image mark'; $lang->usable_group_image_mark_list = ' Usable list of group image mark'; $lang->add_group_image_mark = 'Add group image mark'; @@ -293,22 +359,28 @@ $lang->email = 'Email'; $lang->add_managed_emailhost = 'Add E-mail Host'; $lang->add_prohibited_id = 'Add prohibited id'; $lang->multi_line_input = 'To enter multiple entries, please change the line input.'; -$lang->add_extend_form = 'Add user defined item'; +$lang->add_extend_form = 'Add item'; $lang->msg_null_prohibited_id = 'Please enter an ID to prohibit.'; $lang->msg_null_prohibited_nick_name = 'Please enter a nick name to prohibit.'; $lang->msg_null_managed_emailhost = 'Please enter email address providers to manage. (eg.: gmail.com)'; -$lang->identifier = 'Login Account'; -$lang->about_identifier = 'Please select an account to use when logging in.'; +$lang->identifier = 'Login Identifier'; +$lang->about_identifier = 'Allow users to log in using one or more identifiers.
                Please beware of country codes if you allow login by phone number.'; +$lang->msg_need_identifier = 'You need to select at least one login identifier.'; +$lang->msg_need_enabled_identifier = 'You need to select identifiers that are enabled in the signup form.'; $lang->use_after_save = 'Use after saved'; $lang->cmd_add_group = 'Add group'; $lang->msg_groups_exist = 'groups exist.'; $lang->cmd_member_config = 'Member Configuration'; +$lang->cmd_member_sync = 'Sync member information'; +$lang->about_member_sync = 'Synchronize member information and post/comment information. This can take a long time if you have a lot of data. If there are many users, be sure to stop the service before proceeding.'; +$lang->msg_success_modify_email_address = 'Your email address has been successfully changed. You can log in with the changed email address.'; + $lang->group = 'Group'; $lang->retrieve_password = 'Retrieve password'; -$lang->excess_ip_access_count = 'There was too much sign in trial from your devices in a short time. You can not sign in for %s.'; -$lang->enable_login_fail_report = 'Sign in failure'; -$lang->login_fail_report = 'Sign in failure report.'; -$lang->login_fail_report_contents = '

                There is recorded sign in failures.

                %1$s

                * This notification is shown once.
                * This message contains sign in failure records, before a ID sign in success.
                Sending: %2$s

                '; +$lang->excess_ip_access_count = 'There were too much login attempts from your device in a short time. You can not log in for %s.'; +$lang->enable_login_fail_report = 'Login failure'; +$lang->login_fail_report = 'Login failure report.'; +$lang->login_fail_report_contents = '

                There is recorded login failures.

                %1$s

                * This notification is shown once.
                * This message contains login failure records, before a ID login success.
                Sending: %2$s

                '; $lang->all_group = 'Entire Group'; $lang->msg_insert_group_name_detail = 'If group title are empty, does not apply.'; $lang->msg_exist_selected_module = 'Address information does not exist.'; @@ -317,9 +389,19 @@ $lang->spammer_description = '

                Spam user management. This function denied user $lang->btn_spammer_delete_all = 'Delete all'; $lang->spammer_move_to_trash = 'Move to trash'; $lang->msg_spammer_complete = 'Completed.'; +$lang->nick_name_before_changing = 'Old nickname'; +$lang->nick_name_after_changing = 'New nickname'; $lang->cmd_login_browser_info = 'Browser Information'; +$lang->cmd_login_device_info = 'Device Information'; +$lang->cmd_initial_registration = 'Registered'; $lang->cmd_initial_login = 'First Login'; $lang->cmd_recent_visit = 'Recent Visit'; +$lang->cmd_recent_connection = 'Last Seen'; +$lang->cmd_view_registered_devices = 'Registered Devices'; $lang->scrap_folder_create = 'New Folder'; $lang->scrap_folder_rename = 'Rename'; $lang->scrap_folder_delete = 'Delete'; +$lang->member_unauthenticated = 'Unauthenticated'; +$lang->member_number = 'Member identification number'; +$lang->msg_change_after_click = 'Change after clicking link below'; +$lang->msg_password_changed = 'Your password has been changed.'; diff --git a/modules/member/lang/es.php b/modules/member/lang/es.php index b8901be12..c0980fd54 100644 --- a/modules/member/lang/es.php +++ b/modules/member/lang/es.php @@ -12,8 +12,10 @@ $lang->denied_user_id = 'Este ID está prohibido.'; $lang->null_user_id = 'IngresarID'; $lang->null_password = 'Ingresar la contraseña'; $lang->invalid_authorization = 'No está certificado'; -$lang->invalid_user_id = 'Este ID no existe'; -$lang->invalid_password = 'Contraseña incorrecta'; +$lang->invalid_email_address = 'Ingreso invalido.'; +$lang->invalid_user_id = 'Ingreso invalido.'; +$lang->invalid_password = 'Ingreso invalido.'; +$lang->invalid_new_password = 'Introduzca una contraseña diferente.'; $lang->allow_mailing = 'Registro del envío de mail'; $lang->is_admin = 'Atribución del administrador superior'; $lang->member_group = 'Grupo asignado'; diff --git a/modules/member/lang/fr.php b/modules/member/lang/fr.php index 3f0dba7b0..739133999 100644 --- a/modules/member/lang/fr.php +++ b/modules/member/lang/fr.php @@ -13,8 +13,10 @@ $lang->denied_user_id = 'C\'est un comte interdit.'; $lang->null_user_id = 'Entrez le compte, S.V.P.'; $lang->null_password = 'Entrez le mot de passe, S.V.P.'; $lang->invalid_authorization = 'Le compte n\'est pas encore certifié.'; -$lang->invalid_user_id = 'C\'est un compte qui n\'existe pas.'; -$lang->invalid_password = 'C\'est un mot de passe invalide'; +$lang->invalid_email_address = 'Identifiant invalide.'; +$lang->invalid_user_id = 'Identifiant invalide.'; +$lang->invalid_password = 'Identifiant invalide.'; +$lang->invalid_new_password = 'Veuillez entrer un mot de passe différent.'; $lang->allow_mailing = 'Inscrire au Mailing'; $lang->is_admin = 'Permission Superadministrative'; $lang->member_group = 'Groupe assigné'; diff --git a/modules/member/lang/ja.php b/modules/member/lang/ja.php index 619d864ff..1bf942e1d 100644 --- a/modules/member/lang/ja.php +++ b/modules/member/lang/ja.php @@ -17,10 +17,11 @@ $lang->managed_email_host['prohibited'] = 'E-mail accounts at %s are not allowed $lang->null_user_id = 'ユーザーIDをもう一度入力してください。'; $lang->null_password = 'パスワードを入力してください。'; $lang->invalid_authorization = '認証できませんでした。'; -$lang->invalid_email_address = 'Eメールアドレスと一致する会員がありません。'; -$lang->invalid_user_id = '存在しないユーザーIDです。'; -$lang->invalid_password = '無効なパスワードです。'; -$lang->invalid_new_password = '以前のパスワードと同じパスワードを使うことはできません。'; +$lang->invalid_email_address = 'メールアドレスまたはパスワードが一致しません。'; +$lang->invalid_user_id = 'ユーザーIDまたはパスワードが一致しません。'; +$lang->invalid_password = 'パスワードが一致しません。'; +$lang->invalid_current_password = '現在のパスワードが一致しません。'; +$lang->invalid_new_password = '以前のパスワードと異なるパスワードを入力してください。'; $lang->allow_mailing = 'メーリングリストに登録'; $lang->is_admin = '最高管理権限'; $lang->member_group = '所属グループ'; @@ -46,6 +47,7 @@ $lang->enable_join = '会員登録を許可する'; $lang->enable_confirm = 'メール認証機能を使用'; $lang->enable_find_account_question = '秘密質問/回答認証を使用'; $lang->enable_ssl = 'SSL使用'; +$lang->msg_email_confirmation_required = '認証メールをお送りするので正確に入力してください。'; $lang->security_sign_in = 'セキュア(SSL)'; $lang->limit_day = '一時制限期間(日)'; $lang->limit_day_description = '一時的な期限日の説明'; @@ -69,8 +71,8 @@ $lang->webmaster_email = 'ウェブマスターのメールアドレス'; $lang->column_id = '入力項目ID'; $lang->about_column_id = '入力項目の区切り文字で使用するIDです。英文で始め、英文+数字のみ使用できます。'; $lang->options = '選択オプション'; -$lang->about_keep_signed = 'ブラウザを閉じてもログイン状態が維持されます。\\n\\nログイン維持機能を利用すると、次回からログインする必要がありません。\\n\\nただし、インターネットカフェ、学校など公共場所で利用する場合、個人情報が流出する恐れがありますので、必ずログアウトしてください。'; -$lang->about_keep_warning = 'ブラウザを閉じてもログイン状態が維持されます。\\n\\nログイン維持機能を利用すると、次回からログインする必要がありません。 ただし、インターネットカフェ、学校など公共場所で利用する場合、個人情報が流出する恐れがありますので、必ずログアウトしてください。'; +$lang->about_keep_signed = 'ブラウザを閉じてもログイン状態が維持されます。\\n\\nログイン維持機能を利用すると、次回からログインする必要がありません。\\n\\nただし、インターネットカフェ、学校など公共の場所で利用する場合、個人情報が流出する恐れがありますので、必ずログアウトしてください。'; +$lang->about_keep_warning = 'ブラウザを閉じてもログイン状態が維持されます。ログイン維持機能を利用すると、次回からログインする必要がありません。 ただし、インターネットカフェ、学校など公共の場所で利用する場合、個人情報が流出する恐れがありますので、必ずログアウトしてください。'; $lang->search_target_list['email_address'] = 'メールアドレス'; $lang->search_target_list['regdate'] = '登録日'; $lang->search_target_list['regdate_more'] = '登録日(以上)'; @@ -92,7 +94,7 @@ $lang->cmd_modify_member_info = '会員情報変更'; $lang->cmd_modify_member_password = 'パスワード変更'; $lang->cmd_view_member_info = '会員情報確認'; $lang->cmd_leave = '退会'; -$lang->cmd_find_member_account = 'IDとパスワードのリマインダー'; +$lang->cmd_find_member_account = 'IDとパスワードを忘れたときは'; $lang->cmd_find_member_account_with_email = 'Eメールアドレスでアカウントを探す'; $lang->cmd_find_member_account_with_email_question = '質問・回答でアカウントを探す'; $lang->cmd_resend_auth_mail = '認証メール再申請'; @@ -121,7 +123,7 @@ $lang->msg_find_account_info = '登録された会員情報は下記の通りで $lang->msg_find_account_comment = '下のリンクをクリックすると上のパスワードに変更されます。
                ログインしてからパスワードを変更してください。'; $lang->msg_confirm_account_title = '確認メールです。'; $lang->title_modify_email_address = 'Eメールアドレス変更要求確認メールです。'; -$lang->msg_confirm_account_info = '作成した会員の情報'; +$lang->msg_confirm_account_info = '登録した会員の情報'; $lang->msg_confirm_account_comment = '下記のURLをクリックして会員登録手続きを完了してください。'; $lang->msg_confirm_email_address_change = '下のリンクをクリックするとEメールアドレスが %sに変更されます。'; $lang->msg_auth_mail_sent = '%s 宛に認証情報内容が送信されました。メールを確認してください。'; @@ -129,7 +131,7 @@ $lang->msg_confirm_mail_sent = '%s 宛に確認メールを送信しました。 $lang->msg_invalid_modify_email_auth_key = '正しくないEメール変更要求です。
                Eメール変更要求を再度行うかサイト管理者へお問い合わせください。'; $lang->msg_invalid_auth_key = '正しくないアカウントの認証要求です。
                IDとパスワードの検索を行うか、サイト管理者にアカウント情報をお問い合わせください。'; $lang->msg_success_authed = 'パスワードは、一時的なパスワードに変更されます。\\n必ず確認メールに記載されたパスワードを利用してお好みのパスワードに変更してください。'; -$lang->msg_success_confirmed = '会員登録、有難うございます。'; +$lang->msg_success_confirmed = '会員登録、ありがとうございます。'; $lang->msg_new_member = '会員追加'; $lang->msg_rechecked_password = 'パスワード再確認'; $lang->msg_update_member = '会員情報修正'; @@ -207,7 +209,7 @@ $lang->about_profile_image = 'ユーザーのプロフィールイメージが $lang->about_signature_max_height = '署名欄の高さのサイズを制限します。 (0 もしくは空の場合は制限なし。)'; $lang->about_accept_agreement = '登録規約をすべて読み、同意します。'; $lang->about_member_default = '会員登録時に基本グループとして設定されます。'; -$lang->about_find_member_account = 'ID/パスワードは登録時に登録されたメールにてお知らせします。登録時に登録したメールアドレスを入力して「IDとパスワードのリマインダー」ボタンをクリックしてください。
                '; +$lang->about_find_member_account = 'ID/パスワードは登録時に登録されたメールにてお知らせします。登録時に登録したメールアドレスを入力して「IDとパスワードを忘れたときは」ボタンをクリックしてください。
                '; $lang->about_temp_password = '仮パスワードが正常に発給されました。
                ログイン後、必ずパスワードを変更してください。
                '; $lang->about_ssl_port = '基本ポート以外のSSLポートを利用する場合、入力してください。'; $lang->about_reset_auth_mail = '現在登録されたEメールアドレスは %sです。Eメールアドレスを変更したい場合は、新しいEメールアドレスに会員情報の更新後認証メールを再送できます。'; diff --git a/modules/member/lang/ko.php b/modules/member/lang/ko.php index 9ce6d2bcb..fd29a4d81 100644 --- a/modules/member/lang/ko.php +++ b/modules/member/lang/ko.php @@ -20,9 +20,10 @@ $lang->managed_email_host['prohibited'] = '%s 사이트 이메일 계정은 사 $lang->null_user_id = '회원 아이디를 입력해주세요.'; $lang->null_password = '비밀번호를 입력해주세요.'; $lang->invalid_authorization = '인증이 필요한 계정입니다.'; -$lang->invalid_email_address = '이메일 주소와 일치하는 회원이 없습니다.'; -$lang->invalid_user_id = '존재하지 않는 회원 아이디입니다.'; -$lang->invalid_password = '잘못된 비밀번호입니다.'; +$lang->invalid_email_address = '이메일 주소 또는 비밀번호가 일치하지 않습니다.'; +$lang->invalid_user_id = '아이디 또는 비밀번호가 일치하지 않습니다.'; +$lang->invalid_password = '비밀번호가 일치하지 않습니다.'; +$lang->invalid_current_password = '현재 비밀번호가 일치하지 않습니다.'; $lang->invalid_new_password = '이전 비밀번호와 같습니다.'; $lang->allow_mailing = '메일링 가입'; $lang->is_admin = '최고 관리 권한'; @@ -31,6 +32,11 @@ $lang->group_title = '그룹 제목'; $lang->group_srl = '그룹 번호'; $lang->group_order = '그룹 우선순위'; $lang->group_order_change = '그룹 우선순위 변경'; +$lang->phone_number_default_country = '기본 선택 국가'; +$lang->phone_number_hide_country = '국가 목록 숨김'; +$lang->phone_number_allow_duplicate = '중복 가입 허용'; +$lang->phone_number_verify_by_sms = '문자 인증 사용'; +$lang->msg_need_default_country = '국가 목록을 숨기려면 기본 선택 국가가 있어야 합니다.'; $lang->signature = '서명'; $lang->profile_image = '프로필 사진'; $lang->profile_image_max_width = '가로 제한 길이'; @@ -45,16 +51,24 @@ $lang->group_image_mark = '그룹 이미지 마크'; $lang->group_image_mark_max_width = '가로 제한 길이'; $lang->group_image_mark_max_height = '세로 제한 길이'; $lang->signature_max_height = '서명 높이 제한'; +$lang->cmd_force_member_mid = '강제 적용'; +$lang->msg_exists_member_mid = '회원 모듈이 요구하는 \'%s\' URL은 다른 모듈이 이미 사용하고 있습니다. 회원 모듈과 충돌하는 URL을 가진 게시판이나 페이지의 주소를 변경해 주십시오.'; $lang->enable_join = '회원 가입 허가'; +$lang->enable_join_only_with_url_key = 'URL 키가 일치하는 경우에만 허가'; +$lang->enable_join_url_key = 'URL 키'; $lang->enable_confirm = '메일 인증 사용'; $lang->enable_find_account_question = '질문/답변 인증 사용'; $lang->enable_ssl = 'SSL 기능 사용'; $lang->msg_email_confirmation_required = '인증 메일이 발송되니 정확하게 입력해 주시기 바랍니다.'; +$lang->cmd_authmail_expires = '인증 메일 유효기간'; +$lang->about_authmail_expires = '가입 인증 메일, 아이디/비번 찾기 등의 유효기간을 제한할 수 있습니다.'; $lang->security_sign_in = '보안로그인 사용'; $lang->member_limited = '임시 제한'; $lang->limit_day = '임시 제한 일자'; $lang->limit_day_description = '임시 제한 일자 설명'; $lang->limit_date = '제한일'; +$lang->cmd_special_phone_number = '문자 인증 예외 전화번호'; +$lang->cmd_max_auth_sms_count = '문자 인증 횟수 제한'; $lang->after_login_url = '로그인 후 이동할 주소(URL)'; $lang->after_logout_url = '로그아웃 후 이동할 주소(URL)'; $lang->redirect_url = '회원 가입 후 이동할 페이지'; @@ -67,6 +81,17 @@ $lang->allow_message_type['Y'] = '모두 허용'; $lang->allow_message_type['F'] = '등록된 친구들만 허용'; $lang->allow_message_type['N'] = '모두 금지'; $lang->about_allow_message = '쪽지 허용 방법 및 대상을 지정할 수 있습니다.'; +$lang->verify_by_sms = '인증'; +$lang->verify_by_sms_code = '인증 코드'; +$lang->verify_by_sms_confirm = '인증번호 확인'; +$lang->verify_by_sms_message = '인증번호는 %s입니다.'; +$lang->verify_by_sms_code_sent = '인증번호가 SMS로 발송되었습니다.'; +$lang->verify_by_sms_code_incorrect = '인증번호가 올바르지 않습니다.'; +$lang->verify_by_sms_code_too_many_tries = '여러 번 틀려서 인증번호가 초기화됩니다. 다시 인증해 주세요.'; +$lang->verify_by_sms_code_expired = '인증번호의 유효기간이 만료되었습니다. 다시 인증해 주세요.'; +$lang->verify_by_sms_code_confirmed = '인증이 완료되었습니다.'; +$lang->verify_by_sms_incomplete = '전화번호가 인증되지 않았습니다. 인증 과정을 거쳐 주십시오.'; +$lang->verify_by_sms_error = 'SMS를 발송할 수 없습니다.'; $lang->logged_users = '현재 접속자'; $lang->msg_mail_authorization = '메일 인증을 사용하려면 웸마스터의 이름과 메일주소가 유효해야 합니다.'; $lang->webmaster_name = '웹마스터 이름'; @@ -80,12 +105,15 @@ $lang->about_webmaster_email = '이 설정은 '; +$lang->about_find_member_account = '회원정보에 등록된 메일 주소로 아이디/비밀번호를 알려드립니다. 메일 주소를 입력하고 "ID/PW 찾기" 버튼을 클릭해 주세요.
                '; $lang->about_temp_password = '임시 비밀번호가 정상적으로 발급되었습니다.
                로그인 후 반드시 비밀번호를 변경하세요.
                '; $lang->about_ssl_port = '기본 포트 이외의 보안접속(SSL) 포트를 사용하는 경우 포트번호를 입력해주세요.'; $lang->about_reset_auth_mail = '현재등록된 이메일 주소는 %s입니다. 이메일 주소를 변경하고자 하는 경우 새로운 이메일 주소로 회원정보 갱신 후 인증메일을 재발송할 수 있습니다.'; -$lang->about_resend_auth_mail = '인증 메일을 받지 못한 경우 다시 받을 수 있습니다.'; +$lang->about_resend_auth_mail = '가입 인증 메일을 받지 못한 경우 다시 받을 수 있습니다.'; $lang->about_reset_auth_mail_submit = '이메일을 로그인 계정으로 사용할 경우 신규 메일주소로 로그인해야 합니다.'; -$lang->about_update_nickname_log = '닉네임 로그를 기록합니다. 이 옵션을 사용하게 되면, 닉네임변경이력을 남기도록 할 수 있습니다.'; -$lang->about_member_profile_view = '관리자 회원목록 페이지에서 프로필 이미지를 볼 수 있는 옵션입니다. 회원목록을 보기 원치 않을 경우에는 아니요를 선택하세요.'; +$lang->about_allow_nickname_change = '닉네임 변경을 허용합니다. 사용하는 스킨에 따라 입력란이 표시되지 않을 수도 있습니다.'; +$lang->about_update_nickname_log = '닉네임 변경 이력을 저장합니다.'; +$lang->about_nickname_symbols = '닉네임에 특수문자를 사용할 수 있도록 허용하거나 금지합니다.'; +$lang->about_member_profile_view = '관리자 회원목록 페이지에서 프로필 이미지를 볼 수 있는 옵션입니다. 회원목록에서 프로필 사진을 보기 원치 않을 경우에는 아니요를 선택하세요.'; $lang->no_article = '글이 없습니다.'; $lang->find_account_question = '비밀번호 찾기 질문/답변'; $lang->find_account_answer = '비밀번호 찾기 답변'; @@ -255,16 +308,17 @@ $lang->find_account_question_items['9'] = '가장 좋아하는 음식은?'; $lang->temp_password = '임시 비밀번호'; $lang->cmd_get_temp_password = '임시 비밀번호 발급'; $lang->about_get_temp_password = '로그인 후 비밀번호 변경해 주세요.'; +$lang->msg_activation_not_needed = '이미 인증된 회원입니다.'; +$lang->msg_activation_key_not_found = '인증 메일 발송 내역이 없습니다.'; +$lang->msg_feature_deprecated = '지원되지 않는 기능입니다.'; $lang->msg_question_not_allowed = '질문/답변을 통한 비밀번호 찾기 기능은 이 사이트에서 사용할 수 없습니다.'; $lang->msg_question_not_exists = '등록한 비밀번호 찾기 질문/답변이 없습니다.'; $lang->msg_answer_not_matches = '비밀번호 찾기 질문/답변 또는 정보가 올바르지 않습니다.'; +$lang->msg_invalid_symbol_in_nickname = '닉네임에 사용할 수 없는 특수문자 \'%s\'가 포함되어 있습니다.'; $lang->change_password_date = '비밀번호 갱신주기'; -$lang->about_change_password_date = '일정기간이 지나면 비밀번호 변경을 하도록 유도하는 기능입니다. (사용하지 않음 : 0 입력) '; +$lang->about_change_password_date = '일정 기간이 지나면 비밀번호를 변경하도록 유도하는 기능입니다. 사용하지 않으려면 0을 입력하십시오.'; $lang->msg_change_password_date = '%s일 동안 비밀번호를 변경하지 않았습니다. 개인정보 보호를 위하여 비밀번호를 변경해야 합니다.'; -$lang->login_trial_limit1 = '로그인 시도 횟수 제한 횟수'; -$lang->login_trial_limit2 = '로그인 시도 횟수 제한 시간'; -$lang->about_login_trial_limit1 = '정해진 시간 안에 허용되는 로그인 횟수를 입력하십시오. 짧은 시간 동안 하나의 아이피(IP)에서 시도할 수 있는 로그인 횟수에 제한을 둡니다.'; -$lang->about_login_trial_limit2 = '지정된 횟수의 로그인을 허용하는 시간을 정하십시오. 짧은 시간 동안 하나의 아이피(IP)에서 시도할 수 있는 로그인 횟수에 제한을 둡니다. 시간은 가장 마지막 로그인 시도의 시각으로부터의 시간을 기준으로 측정합니다.'; +$lang->about_login_trial_limit = '짧은 시간 동안 하나의 아이피(IP)에서 시도할 수 있는 로그인 횟수에 제한을 둡니다.'; $lang->msg_kr_address = '읍, 면, 동 이름으로 검색하세요.'; $lang->msg_kr_address_etc = '나머지 주소(번지)를 입력하세요.'; $lang->cmd_search_again = '다시 검색'; @@ -283,11 +337,11 @@ $lang->cmd_optional = '선택'; $lang->cmd_disabled = '사용 안 함'; $lang->cmd_image_max_width = '너비 제한'; $lang->cmd_image_max_height = '높이 제한'; -$lang->cmd_input_extend_form = '회원 정의 입력'; +$lang->cmd_image_force_ratio = '가로세로 비율 고정'; +$lang->cmd_input_extend_form = '회원가입 추가 항목 생성'; $lang->about_multi_type = '다중 또는 단일 항목의 선택 값을 입력하세요.(줄 바꿈으로 구분)'; $lang->msg_delete_extend_form = '선택한 항목을 삭제합니다.'; $lang->set_manage_id = '줄 바꿈으로 구분'; -$lang->count_manage_email_host = '다음 %s개의 이메일 주소 제공자를 %s 합니다.'; $lang->count_manage_id = '%s개의 금지 아이디가 있습니다.'; $lang->count_manage_nick_name = '%s개의 금지 닉네임이 있습니다.'; $lang->user_list = '회원 목록'; @@ -297,7 +351,9 @@ $lang->cmd_show_site_admin_member = '사이트 관리자'; $lang->approval = '승인'; $lang->denied = '거부'; $lang->refused_reason = '계정 거부 사유'; -$lang->about_refused_reason = '계정이 정지된 이유를 적어주세요. 이 항목에 기록된 내용은 해당 회원에게 로그인 후 화면을 통해 안내됩니다.'; +$lang->about_refused_reason = '해당 회원이 로그인 시도시 이 내용이 표시됩니다.'; +$lang->limited_reason = '제한 사유'; +$lang->about_limited_reason = '해당 회원이 로그인 시도시 이 내용이 표시됩니다.'; $lang->use_group_image_mark = '그룹 이미지 마크 사용'; $lang->usable_group_image_mark_list = '사용가능한 그룹 이미지 마크 목록'; $lang->add_group_image_mark = '그룹 이미지 마크 추가'; @@ -307,13 +363,15 @@ $lang->email = '이메일'; $lang->add_managed_emailhost = '이메일 제공자 추가'; $lang->add_prohibited_id = '금지 아이디 추가'; $lang->multi_line_input = '여러 항목은 줄을 바꾸어 입력하세요.'; -$lang->add_extend_form = '사용자 정의 항목 추가'; +$lang->add_extend_form = '항목 추가'; $lang->msg_null_prohibited_id = '추가할 금지 아이디를 입력해주세요.'; $lang->msg_null_prohibited_nick_name = '추가할 금지 닉네임을 입력해주세요.'; $lang->msg_null_managed_emailhost = '추가할 금지 이메일 주소 제공자를 입력해주세요. (예: naver.com)'; -$lang->identifier = '로그인 계정'; -$lang->about_identifier = '로그인에 사용할 계정을 선택해주세요.'; +$lang->identifier = '로그인 방법'; +$lang->about_identifier = '회원들이 여러 가지 방법으로 로그인할 수 있습니다.
                전화번호 로그인을 허용하는 경우 국가코드에 유의하십시오.'; $lang->about_public_item = '본인 외에 다른 회원에게도 노출될 정보인지 선택합니다.'; +$lang->msg_need_identifier = '최소 한 가지의 로그인 방법을 선택해야 합니다.'; +$lang->msg_need_enabled_identifier = '가입 폼에서 사용하도록 설정된 로그인 방법을 선택해야 합니다.'; $lang->use_after_save = '저장 후 사용'; $lang->cmd_add_group = '그룹 추가'; $lang->msg_groups_exist = '개 그룹이 존재합니다.'; @@ -338,8 +396,16 @@ $lang->msg_spammer_complete = '완료되었습니다.'; $lang->nick_name_before_changing = '닉네임 변경 전'; $lang->nick_name_after_changing = '닉네임 변경 후'; $lang->cmd_login_browser_info = '브라우저 정보'; +$lang->cmd_login_device_info = '기기 정보'; +$lang->cmd_initial_registration = '최초 등록'; $lang->cmd_initial_login = '최초 로그인'; $lang->cmd_recent_visit = '최근 방문'; +$lang->cmd_recent_connection = '최근 접속'; +$lang->cmd_view_registered_devices = '등록 기기 목록'; $lang->scrap_folder_create = '폴더 추가'; $lang->scrap_folder_rename = '이름 변경'; $lang->scrap_folder_delete = '삭제'; +$lang->member_unauthenticated = '미인증'; +$lang->member_number = '회원 번호'; +$lang->msg_change_after_click = '아래 링크 클릭 후 변경 가능'; +$lang->msg_password_changed = '비밀번호가 변경되었습니다.'; diff --git a/modules/member/lang/vi.php b/modules/member/lang/vi.php index 5827054ce..f7a6e1378 100644 --- a/modules/member/lang/vi.php +++ b/modules/member/lang/vi.php @@ -13,7 +13,7 @@ $lang->denied_user_id = 'ID đăng nhập của bạn đã bị cấm sử dụn $lang->null_user_id = 'Xin vui lòng nhập ID đăng nhập'; $lang->null_password = 'Xin vui lòng nhập mật khẩu'; $lang->invalid_authorization = 'Tài khoản đăng nhập không đúng.'; -$lang->invalid_user_id = 'Bạn đã nhập sai tên sử dụng'; +$lang->invalid_user_id = 'Bạn đã nhập E-mail không đúng'; $lang->invalid_password = 'Mật khẩu không đúng'; $lang->allow_mailing = 'Đồng ý nhận Email'; $lang->is_admin = 'Toàn quyền Administrator'; @@ -23,16 +23,16 @@ $lang->group_srl = 'Nhóm số'; $lang->signature = 'Chữ kí'; $lang->profile_image = 'Hình đại diện'; $lang->profile_image_max_width = 'Chiểu rộng tối đa'; -$lang->profile_image_max_height = 'Chiều cao tối đa  '; +$lang->profile_image_max_height = 'Chiều dài tối đa  '; $lang->image_name = 'Hình thay thế tên hiển thị'; $lang->image_name_max_width = 'Chiểu rộng tối đa'; -$lang->image_name_max_height = 'Chiều cao tối đa  '; +$lang->image_name_max_height = 'Chiều dài tối đa  '; $lang->image_mark = 'Hình đánh dấu'; $lang->image_mark_max_width = 'Chiểu rộng tối đa'; -$lang->image_mark_max_height = 'Chiều cao tối đa  '; +$lang->image_mark_max_height = 'Chiều dài tối đa  '; $lang->group_image_mark = 'Hình đại diện nhóm'; $lang->group_image_mark_max_width = 'Chiểu rộng tối đa'; -$lang->group_image_mark_max_height = 'Chiều cao tối đa  '; +$lang->group_image_mark_max_height = 'Chiều dài tối đa  '; $lang->signature_max_height = 'Chiều cao lớn nhất của chữ kí'; $lang->enable_join = 'Đồng ý đăng kí thành viên'; $lang->enable_confirm = 'Xác nhận qua Email'; @@ -46,7 +46,7 @@ $lang->after_logout_url = 'URL sau khi thoát'; $lang->redirect_url = 'URL sau khi đăng kí'; $lang->agreement = 'Quy định sử dụng'; $lang->accept_agreement = 'Đồng ý'; -$lang->member_info = 'Thông tin cá nhân'; +$lang->member_info = 'Thông tin Thành viên'; $lang->current_password = 'Mật khẩu hiện tại'; $lang->allow_message = 'Nhận tin nhắn'; $lang->allow_message_type['Y'] = 'Tất cả'; @@ -74,7 +74,7 @@ $lang->cmd_logout = 'Thoát'; $lang->cmd_signup = 'Đăng kí'; $lang->cmd_site_signup = 'Đăng kí'; $lang->cmd_modify_member_password = 'Sửa đổi mật khẩu'; -$lang->cmd_view_member_info = 'Xem thông tin cá nhân'; +$lang->cmd_view_member_info = 'Thông tin thành viên'; $lang->cmd_leave = 'Từ bỏ'; $lang->cmd_find_member_account = 'Lấy lại thông tin'; $lang->cmd_resend_auth_mail = 'Xác nhận lại Email'; @@ -86,6 +86,8 @@ $lang->cmd_manage_id = 'Tên sử dụng cấm đăng kí'; $lang->cmd_manage_email_host = 'E-mail provider check'; $lang->cmd_manage_form = 'Quản lý mẫu đăng kí'; $lang->cmd_view_own_document = 'Bài gửi'; +$lang->cmd_view_own_comment = 'Bình luận'; +$lang->cmd_view_active_logins = 'Thông tin đăng nhập'; $lang->cmd_manage_member_info = 'Quản lý thông tin thành viên'; $lang->cmd_trace_document = 'Theo dõi chủ đề'; $lang->cmd_trace_comment = 'Theo dõi thảo luận'; @@ -99,7 +101,7 @@ $lang->msg_checked_file_is_deleted = '%d đính kèm đã được xóa.'; $lang->msg_find_account_info = 'Thông tin tài khoản.'; $lang->msg_find_account_comment = 'Mật khẩu sẽ thay đổi khi bạn bấm vào đường Link này.
                Xin vui lòng thay đổi mật khẩu sau khi đăng nhập!'; $lang->msg_confirm_account_comment = 'Bấm vào đường Link này để hoàn tất việc đăng kí.'; -$lang->msg_auth_mail_sent = 'Email xác nhận đã được gửi đến %s. Xin vui lòng kiểm tra Inbox hoặc Bulk mail của bạn!'; +$lang->msg_auth_mail_sent = 'Email xác nhận đã được gửi đến %s. Xin vui lòng kiểm tra E-mail của bạn!'; $lang->msg_confirm_mail_sent = 'Chúng tôi đã gửi Email xác nhận đến %s. Xin vui lòng bấm vào Link đi kèm trong Email để hoàn tất việc đăng kí!'; $lang->msg_invalid_auth_key = 'Xác nhận không hợp lệ.
                Xin vui lòng kiểm tra lại hoặc liên hệ với Webmaster để được hỗ trợ!'; $lang->msg_success_authed = 'Tài khoản của bạn đã được xác nhận.\\n Xin vui lòng thay đổi mật khẩu khác với mật khẩu trong Email và dễ nhớ hơn!'; @@ -166,3 +168,25 @@ $lang->about_member_default = 'Sẽ là nhóm mặc định khi thành viên đ $lang->about_find_member_account = 'Thông tin tài khoản của bạn sẽ được tìm thấy với Email bạn đã đăng kí. Xin vui lòng nhập Email mà bạn đã đăng kí và bấm "Lấy lại thông tin" để nhận được thông tin đăng nhập!.
                '; $lang->about_ssl_port = 'Xin hãy nhập cổng kết nối mặc định cho SSL.'; $lang->about_resend_auth_mail = 'Nhận lại mã kích hoạt nếu bạn đã không nhận được Email kích hoạt khi đăng kí.'; +$lang->cmd_view_own_comment = 'Bình luận của tôi'; +$lang->default_group_1 = 'Thành viên dự bị'; +$lang->default_group_2 = 'Thành viên chính thức'; +$lang->cmd_modify_member_email_address = 'Sửa đổi E-mail'; +$lang->about_modify_member_email_address = 'Bạn có thể sửa đổi Email.'; +$lang->cmd_modify_member_info = 'Sửa đổi thông tin'; +$lang->about_password_strength['normal'] = 'Phải có ít nhất 6 ký tự bao gồm chữ số '; +$lang->cmd_yes = 'Vâng'; +$lang->cmd_no = 'Không'; +$lang->cmd_find_member_account_with_email = 'Tìm thông tin tài khoản'; +$lang->msg_find_account_title = 'Thông tin tài khoản của bạn'; +$lang->msg_confirm_account_title = 'Xác nhận Email của bạn trên EỞHQ'; +$lang->msg_confirm_account_info = 'Sau đây là thông tin tài khoản của bạn.'; +$lang->cmd_send_auth_new_emaill_address = 'Gửi E-mail xác nhận đến địa chỉ E-mail mới'; +$lang->msg_confirm_email_address_change = 'Bấm vào đường link này để đổi E-mail mới'; +$lang->msg_email_confirmation_required ='E-mail xác nhận sẽ được gửi qua email trên, vui lòng kiểm tra kỹ E-mail của bạn.'; +$lang->msg_user_not_confirmed = 'Xác nhận đăng ký chưa hoàn thành. Thông tin xác nhận đã được gửi đến email %s mà bạn đã nhập, vui lòng kiểm tra và nhấn vào link xác nhận.'; +$lang->msg_success_confirmed = 'Đã xác nhận đăng ký thành công. Bây giờ bạn có thể đăng nhập.'; +$lang->temp_password = 'Mật khẩu tạm thời'; +$lang->invalid_email_address = 'E- mail không đúng.'; +$lang->login_fail_report = 'Thông báo hồ sơ đăng nhập thất bại.'; +$lang->login_fail_report_contents = '

                Chúng tôi xin thông báo hồ sơ đăng nhập thất bại.

                %1$s

                * Không có chuyện sai mật khẩu, nhưng nếu bạn nhìn thấy tin nhắn này, xin vui lòng lưu ý quản lý tài khoản.
                * Tin nhắn này được gửi nếu khi đăng nhập thành công nhưng trước đó lại có nhiều lần đăng nhập thất bại được ghi lại, thu thập những lần đăng ký thất bại trước khi đăng nhập thành công sau đó gửi.
                Thời gian gửi: %2$s

                '; diff --git a/modules/member/lang/zh-CN.php b/modules/member/lang/zh-CN.php index 3024cbd45..8ec0ac31a 100644 --- a/modules/member/lang/zh-CN.php +++ b/modules/member/lang/zh-CN.php @@ -15,9 +15,9 @@ $lang->denied_nick_name = '被禁止的昵称。'; $lang->null_user_id = '请输入用户名。'; $lang->null_password = '请输入密码。'; $lang->invalid_authorization = '还没有认证!'; -$lang->invalid_email_address = '找不到跟邮箱地址一致的会员!'; -$lang->invalid_user_id = '该用户名不存在,请检查您的输入是否有误!'; -$lang->invalid_password = '您的密码不正确!'; +$lang->invalid_email_address = '没有与您输入的信息一致的会员。'; +$lang->invalid_user_id = '没有与您输入的信息一致的会员。'; +$lang->invalid_password = '没有与您输入的信息一致的会员。'; $lang->invalid_new_password = '新密码不能跟旧密码相同'; $lang->allow_mailing = '接收邮件'; $lang->is_admin = '最高管理权限'; diff --git a/modules/member/lang/zh-TW.php b/modules/member/lang/zh-TW.php index 64a5210d7..e2ffea96f 100644 --- a/modules/member/lang/zh-TW.php +++ b/modules/member/lang/zh-TW.php @@ -13,8 +13,9 @@ $lang->denied_user_id = '被禁止的帳號。'; $lang->null_user_id = '請輸入帳號。'; $lang->null_password = '請輸入密碼。'; $lang->invalid_authorization = '還沒有認證!'; -$lang->invalid_user_id = '該帳號不存在,請檢查您的輸入是否有誤!'; -$lang->invalid_password = '您的密碼不正確!'; +$lang->invalid_email_address = '沒有與您輸入的信息一致的會員。'; +$lang->invalid_user_id = '沒有與您輸入的信息一致的會員。'; +$lang->invalid_password = '沒有與您輸入的信息一致的會員。'; $lang->invalid_new_password = '新密碼不能與舊密碼相同'; $lang->allow_mailing = '接收郵件'; $lang->is_admin = '最高管理權限'; diff --git a/modules/member/m.skins/default/active_logins.html b/modules/member/m.skins/default/active_logins.html index 15b85027e..68dd58030 100644 --- a/modules/member/m.skins/default/active_logins.html +++ b/modules/member/m.skins/default/active_logins.html @@ -1,4 +1,5 @@ +
                + +
                +

                {$lang->cmd_view_registered_devices}

                +
                  +
                • + {$device_info->device_type} {$device_info->device_version} + ({$device_info->device_model ?: 'no model'})
                  + {$lang->cmd_initial_registration}: {zdate($device_info->regdate, 'Y-m-d H:i')}
                  + {$lang->cmd_recent_connection}: {zdate($device_info->last_active_date, 'Y-m-d H:i')}
                  + +
                • +
                +
                + diff --git a/modules/member/m.skins/default/comment_list.html b/modules/member/m.skins/default/comment_list.html index 5d29c7be6..b0006258c 100644 --- a/modules/member/m.skins/default/comment_list.html +++ b/modules/member/m.skins/default/comment_list.html @@ -4,15 +4,21 @@ + + + + + +
                - + {$page} / {$page_navigation->last_page}
                - \ No newline at end of file + diff --git a/modules/member/m.skins/default/common_header.html b/modules/member/m.skins/default/common_header.html index f882e2d41..d46dd4165 100644 --- a/modules/member/m.skins/default/common_header.html +++ b/modules/member/m.skins/default/common_header.html @@ -3,7 +3,7 @@ \ No newline at end of file +
                diff --git a/modules/member/m.skins/default/css/member.css b/modules/member/m.skins/default/css/member.css index 8697d5792..a50417881 100644 --- a/modules/member/m.skins/default/css/member.css +++ b/modules/member/m.skins/default/css/member.css @@ -53,6 +53,11 @@ .lt .title em{font-size:12px;color:#333;color:#6352d2} .lt .auth{display:block;font-size:12px} .lt .auth .time{padding:0 5px;border-left:1px solid #bfbfbf} +/* Search */ +.search { clear: both; text-align: center; padding: 4px 0; } +.search input[type=search] { font-size: 12px; line-height: 16px; padding: 3px; width: 160px; height: 24px; box-sizing: border-box; } +.search select { font-size: 12px; line-height: 16px; padding: 3px; height: 24px; box-sizing: border-box; } +.search button { font-size: 12px; line-height: 16px; padding: 3px 8px; height: 24px; box-sizing: border-box; } /* Pagination */ .pn{font-size:12px;text-align:center;background:#f2f0ec;padding:15px 0;border-top:1px solid #fff} .pn a{color:#333;text-decoration:none} @@ -100,4 +105,4 @@ } .xm ul.mtab>li:first-child a span { border-left: 0 none; -} \ No newline at end of file +} diff --git a/modules/member/m.skins/default/css/mlogin.css b/modules/member/m.skins/default/css/mlogin.css index b9a3ed2b1..ed69be532 100644 --- a/modules/member/m.skins/default/css/mlogin.css +++ b/modules/member/m.skins/default/css/mlogin.css @@ -39,6 +39,7 @@ input[type=radio]{width:13px;height:13px;margin:0;padding:0} .hp li:first-child{border:0;} .hp a,p{color:#333;text-decoration:none} /* Button Area */ +.captcha{padding:0 10px;} .bna{text-align:center;padding:0 10px;margin:10px 0} .bna:after{content:"";display:block;clear:both} .bn{display:inline-block;line-height:26px !important;padding:0 10px;font-size:12px;font-weight:bold;border:1px solid;text-decoration:none;border-radius:5px;-moz-border-radius:5px;-webkit-border-radius:5px;cursor:pointer;vertical-align:middle} diff --git a/modules/member/m.skins/default/document_list.html b/modules/member/m.skins/default/document_list.html index a6f61db7a..a9a8294dd 100644 --- a/modules/member/m.skins/default/document_list.html +++ b/modules/member/m.skins/default/document_list.html @@ -9,10 +9,21 @@ +
                {$page} / {$page_navigation->last_page}
                - \ No newline at end of file + diff --git a/modules/member/m.skins/default/find_member_account.html b/modules/member/m.skins/default/find_member_account.html index 986ba58f2..ce2ff84e8 100644 --- a/modules/member/m.skins/default/find_member_account.html +++ b/modules/member/m.skins/default/find_member_account.html @@ -17,7 +17,10 @@ +
                + {$captcha} +
                - +
                diff --git a/modules/member/m.skins/default/login_form.html b/modules/member/m.skins/default/login_form.html index b4f5d5e3a..4996bec03 100644 --- a/modules/member/m.skins/default/login_form.html +++ b/modules/member/m.skins/default/login_form.html @@ -6,15 +6,18 @@

                {$XE_VALIDATOR_MESSAGE}

                -
                + - +
                  -
                • +
                +
                + {$captcha} +
                diff --git a/modules/member/m.skins/default/member_info.html b/modules/member/m.skins/default/member_info.html index 94f4b1823..e0f0d4ec4 100644 --- a/modules/member/m.skins/default/member_info.html +++ b/modules/member/m.skins/default/member_info.html @@ -16,12 +16,12 @@ {$lang->signup_date} - {zdate($memberInfo[regdate],"Y-m-d")} + {zdate($memberInfo['regdate'], "Y-m-d")} - + {$lang->last_login} - {zdate($memberInfo[last_login],"Y-m-d")} + {zdate($memberInfo['last_login'], "Y-m-d")} diff --git a/modules/member/m.skins/default/modify_info.html b/modules/member/m.skins/default/modify_info.html index ff229b40f..668404c85 100644 --- a/modules/member/m.skins/default/modify_info.html +++ b/modules/member/m.skins/default/modify_info.html @@ -26,7 +26,7 @@
                  -
                • +
                • @@ -78,4 +78,4 @@ return false;}); }); })(jQuery); - \ No newline at end of file + diff --git a/modules/member/m.skins/default/scrapped_list.html b/modules/member/m.skins/default/scrapped_list.html index 8b4011ec1..34b468628 100644 --- a/modules/member/m.skins/default/scrapped_list.html +++ b/modules/member/m.skins/default/scrapped_list.html @@ -9,10 +9,21 @@
                + + + + + + +
                {$page} / {$page_navigation->last_page}
                - \ No newline at end of file + diff --git a/modules/member/m.skins/default/signup_form.html b/modules/member/m.skins/default/signup_form.html index 85d5c7821..1c14ef8a3 100644 --- a/modules/member/m.skins/default/signup_form.html +++ b/modules/member/m.skins/default/signup_form.html @@ -9,7 +9,7 @@

                {$XE_VALIDATOR_MESSAGE}

                -
                + @@ -25,22 +25,22 @@
                  -
                • +
                • -
                • +
                • {$lang->about_password_strength[$member_config->password_strength]}

                • -
                • +
                • @@ -48,21 +48,24 @@
                  {$formTag->inputTag}
                  -
                • +
                • -
                • +
                -
                - -
                +
                + {$captcha} +
                +
                + +
                \ No newline at end of file + diff --git a/modules/member/m.skins/rx_prn/active_logins.html b/modules/member/m.skins/rx_prn/active_logins.html deleted file mode 100644 index c080e831a..000000000 --- a/modules/member/m.skins/rx_prn/active_logins.html +++ /dev/null @@ -1,47 +0,0 @@ - -
                -

                {$member_title = lang('member.cmd_view_active_logins')}

                -
                - {lang('common.total')}: {number_format($total_count)} -
                -
                -
                  -
                • - {@ $autologin_info->user_agent = @json_decode($autologin_info->user_agent) ?: new stdClass()} - - - {escape($autologin_info->user_agent->browser)} {escape($autologin_info->user_agent->version)} ({escape($autologin_info->user_agent->os)}) - - - {zdate($autologin_info->regdate, 'Y-m-d H:i')} ({$autologin_info->ipaddress}) - - - - - -
                • -
                -
                -
                - - - diff --git a/modules/member/m.skins/rx_prn/comment_list.html b/modules/member/m.skins/rx_prn/comment_list.html deleted file mode 100644 index c79de575e..000000000 --- a/modules/member/m.skins/rx_prn/comment_list.html +++ /dev/null @@ -1,39 +0,0 @@ - -
                -

                {$member_title = lang('member.cmd_view_own_comment')}

                -
                - {lang('common.total')}: {number_format($total_count)} -
                - -
                - - - diff --git a/modules/member/m.skins/rx_prn/common_footer.html b/modules/member/m.skins/rx_prn/common_footer.html deleted file mode 100644 index c63a94a2b..000000000 --- a/modules/member/m.skins/rx_prn/common_footer.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/modules/member/m.skins/rx_prn/common_header.html b/modules/member/m.skins/rx_prn/common_header.html deleted file mode 100644 index 280bbbedc..000000000 --- a/modules/member/m.skins/rx_prn/common_header.html +++ /dev/null @@ -1,76 +0,0 @@ - - - - {@ - if(!$layout_info->primary_color) - $layout_info->primary_color = 'red'; - if(!$layout_info->primary_color && $layout_info->customized_primary_color) - $layout_info->primary_color = 'customized'; - if(!$layout_info->customized_primary_color) - $layout_info->customized_primary_color = '#f44336'; - } - - - {@$layout_info->primary_color = 'red';} - -{@ - $material_colors = array( - 'red' => '#f44336', - 'crimson' => '#aa0000', - 'pink' => '#e91e63', - 'purple' => '#9c27b0', - 'deep-purple' => '#673ab7', - 'indigo' => '#3f51b5', - 'deep-blue' => '#00397f', - 'blue' => '#2196f3', - 'light-blue' => '#03a9f4', - 'cyan' => '#00bcd4', - 'teal' => '#009688', - 'green' => '#4caf50', - 'light-green' => '#8bc34a', - 'lime' => '#cddc39', - 'yellow' => '#ffeb3b', - 'amber' => '#ffc107', - 'orange' => '#ff9800', - 'deep-orange' => '#ff5722', - 'brown' => '#795548', - 'grey' => '#9e9e9e', - 'blue-grey' => '#607d8b', - 'black' => '#000000', - 'white' => '#ffffff', - 'customized' => $layout_info->customized_primary_color, - ); -} - -{@$colorset = $material_colors[$member_config->colorset];} -{@$skin_color = $material_colors[$layout_info->primary_color];} - - - {@$skin_color = trim($colorset)} - - {@$skin_color = trim($colorset)[1].trim($colorset)[1].trim($colorset)[2].trim($colorset)[2].trim($colorset)[3].trim($colorset)[3]} - - - {@$skin_color = '#f44336'} - - - - - {@$skin_color = $layout_info->primary_color} - - {@$skin_color = $layout_info->primary_color[1].$layout_info->primary_color[1].$layout_info->primary_color[2].$layout_info->primary_color[2].$layout_info->primary_color[3].$layout_info->primary_color[3]} - - - {@$skin_color = '#f44336'} - - -{@Context::set('prn_less_value', array('red' => hexdec(substr($skin_color, 1, 2)), 'green' => hexdec(substr($skin_color, 3, 2)), 'blue' => hexdec(substr($skin_color, 5, 2)) ))} - -
                -
                - -
                \ No newline at end of file diff --git a/modules/member/m.skins/rx_prn/confirm_member_account_mail.html b/modules/member/m.skins/rx_prn/confirm_member_account_mail.html deleted file mode 100644 index 392d46945..000000000 --- a/modules/member/m.skins/rx_prn/confirm_member_account_mail.html +++ /dev/null @@ -1,19 +0,0 @@ -
                -
                -

                {lang('member.msg_confirm_account_info')}

                -
                -
                {lang('member.site')}
                -
                - {getUrl()} -
                - -
                {lang($name)}
                -
                {$value}
                -
                -
                -

                - {lang('member.msg_confirm_account_comment')}
                - {$auth_url} -

                -
                -
                \ No newline at end of file diff --git a/modules/member/m.skins/rx_prn/confirm_member_new_email.html b/modules/member/m.skins/rx_prn/confirm_member_new_email.html deleted file mode 100644 index 0a0600b53..000000000 --- a/modules/member/m.skins/rx_prn/confirm_member_new_email.html +++ /dev/null @@ -1,19 +0,0 @@ -
                -
                -

                {lang('member.msg_confirm_account_info')}

                -
                -
                {lang('member.site')}
                -
                - {getUrl()} -
                - -
                {lang($name)}
                -
                {$value}
                -
                -
                -

                - {sprintf($lang->msg_confirm_email_address_change, $newEmail)}
                - {$auth_url} -

                -
                -
                \ No newline at end of file diff --git a/modules/member/m.skins/rx_prn/css/css.less b/modules/member/m.skins/rx_prn/css/css.less deleted file mode 100644 index b4e317eae..000000000 --- a/modules/member/m.skins/rx_prn/css/css.less +++ /dev/null @@ -1,506 +0,0 @@ -@charset "UTF-8"; -/* - @method .text-contrast() - @author misol - @brief Select a text color according to WCAG 2.0 contrast guideline. The calcualtion of contrast follows the formula on the guideline. -*/ -.text-contrast(@bg_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i >= 100) and ( ( (luma(@bg_color) + 0.05) / (luma(@dark_color) + 0.05) ) =< ( ( luma(@bright_color) + 0.05) / ( luma(@bg_color) + 0.05) )) { - color: @bright_color; -} -.text-contrast(@bg_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i >= 100) and ( ( (luma(@bg_color) + 0.05) / (luma(@dark_color) + 0.05) ) > ( ( luma(@bright_color) + 0.05) / ( luma(@bg_color) + 0.05) )) { - color: @dark_color; -} -.text-contrast(@bg_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i < 100) and ( ( (luma(@bg_color) + 0.05) / (luma(@dark_color) + 0.05) ) =< ( ( luma(@bright_color) + 0.05) / ( luma(@bg_color) + 0.05) )) and ( ( ( luma(@bright_color) + 0.05) / ( luma(@bg_color) + 0.05) ) > 4.5 * @multi ) { - color: @bright_color; -} -.text-contrast(@bg_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i < 100) and ( ( (luma(@bg_color) + 0.05) / (luma(@dark_color) + 0.05) ) =< ( ( luma(@bright_color) + 0.05) / ( luma(@bg_color) + 0.05) ) ) and ( ( ( luma(@bright_color) + 0.05) / ( luma(@bg_color) + 0.05) ) =< 4.5 * @multi ) { - .text-contrast(@bg_color; lighten(@bright_color, 5%); @dark_color; @multi; @i + 1); -} -.text-contrast(@bg_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i < 100) and ( ( (luma(@bg_color) + 0.05) / (luma(@dark_color) + 0.05) ) > ( ( luma(@bright_color) + 0.05) / ( luma(@bg_color) + 0.05) )) and ( ( (luma(@bg_color) + 0.05) / (luma(@dark_color) + 0.05) ) > 4.5 * @multi ) { - color: @dark_color; -} -.text-contrast(@bg_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i < 100) and (( (luma(@bg_color) + 0.05) / (luma(@dark_color) + 0.05) ) > ( ( luma(@bright_color) + 0.05) / ( luma(@bg_color) + 0.05) )) and ( ( (luma(@bg_color) + 0.05) / (luma(@dark_color) + 0.05) ) =< 4.5 * @multi ) { - .text-contrast(@bg_color; @bright_color; darken(@dark_color, 5%); @multi; @i + 1); -} - -/* - @method .bg-contrast() - @author misol - @brief Select a background color, which has less contrast background color than WCAG 2.0 contrast guideline. On the WCAG 2.0 guideline, bigger string can have less contrast as 3.0. -*/ -.bg-contrast(@text_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i >= 100) and ( ( (luma(@text_color) + 0.05) / (luma(@dark_color) + 0.05) ) =< ( ( luma(@bright_color) + 0.05) / ( luma(@text_color) + 0.05) )) { - background: @bright_color; -} -.bg-contrast(@text_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i >= 100) and ( ( (luma(@text_color) + 0.05) / (luma(@dark_color) + 0.05) ) > ( ( luma(@bright_color) + 0.05) / ( luma(@text_color) + 0.05) )) { - background: @dark_color; -} - -.bg-contrast(@text_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i < 100) and ( ( (luma(@text_color) + 0.05) / (luma(@dark_color) + 0.05) ) =< ( ( luma(@bright_color) + 0.05) / ( luma(@text_color) + 0.05) )) and ( ( ( luma(@bright_color) + 0.05) / ( luma(@text_color) + 0.05) ) > 3 * @multi ) { - background: @bright_color; -} -.bg-contrast(@text_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i < 100) and ( ( (luma(@text_color) + 0.05) / (luma(@dark_color) + 0.05) ) =< ( ( luma(@bright_color) + 0.05) / ( luma(@text_color) + 0.05) ) ) and ( ( ( luma(@bright_color) + 0.05) / ( luma(@text_color) + 0.05) ) =< 3 * @multi ) { - .bg-contrast(@text_color; lighten(@bright_color,3%); @dark_color; @multi; @i + 1); -} -.bg-contrast(@text_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i < 100) and ( ( (luma(@text_color) + 0.05) / (luma(@dark_color) + 0.05) ) > ( ( luma(@bright_color) + 0.05) / ( luma(@text_color) + 0.05) )) and ( ( (luma(@text_color) + 0.05) / (luma(@dark_color) + 0.05) ) > 3 * @multi ) { - background: @dark_color; -} -.bg-contrast(@text_color; @bright_color:#fff; @dark_color:#000; @multi:1; @i:0) when (@i < 100) and (( (luma(@text_color) + 0.05) / (luma(@dark_color) + 0.05) ) > ( ( luma(@bright_color) + 0.05) / ( luma(@text_color) + 0.05) )) and ( ( (luma(@text_color) + 0.05) / (luma(@dark_color) + 0.05) ) =< 3 * @multi ) { - .bg-contrast(@text_color; @bright_color; darken(@dark_color, 3%); @multi; @i + 1); -} - -/* As LESS library in Rhymix substitude variables as an strings, convert colors as the color objects of LESS. */ -@color: rgb(@red, @green, @blue); - - -/* As this file handle some wild-selectors to control display settings, hide inline script and style codes. */ -script, style -{ - display:none!important; -} - -/* Member skin container, default settings */ - -.rx_prn_member -{ - /* font-size */ - .font-xl() - { - /* font-size: 45; 1 */ - font-size: 1.25em; - } - .font-l() - { - /* font-size: 40; 1_2, 1_3, 2_1, 4, 5_1 */ - font-size: 1.11em; - } - .font-m() - { - /* font-size: 36; 1_4, 2_2, 4_1, 5_2 */ - font-size: 1em; - } - .font-s() - { - /* font-size: 30; 2_3, 3_1, 5_3 */ - font-size: 0.833em; - } - - - /* font-weight */ - .font-bold() - { - font-weight: 700; - } - - /* font-color */ - .font-point() - { - /* 4, 4_1 */ - color: lighten(@color, 5%) - } - - .font-dark() - { - color: #484848 - } - .font-gray() - { - color: #757575 - } - .font-light() - { - color: #bdbdbd - } - - - .background-lightgray() - { - background-color: #fafafa - } - .background-gray() - { - background-color: #bdbdbd - } - .background-lightpoint() - { - color: lighten(@color, 10%) - } - .background-point() - { - color: @color - } - - - font-family: "맑은 고딕", "Apple SD Gothic Neo","나눔고딕",NanumGothic,'Nanum Gothic',Arial,Helvetica,sans-serif; - font-size: 14px; - text-align: justify; - margin: 8px 0px; - padding: 0 5px; - .font-dark(); - - - a - { - .font-dark(); - text-decoration: none; - } - - .pos-right - { - position:absolute; - right:0 - } - - /* Tab over the main content. */ - div.rx_prn_tab{ - background: #ffffff; - margin: 5px 0 0; - box-sizing: border-box; - overflow: hidden; - width:100%; - ul.rx_prn_tab{ - margin: 0; - padding: 0; - list-style: outside none none; - display: block; - text-decoration: none; - overflow-x: auto; - -webkit-overflow-scrolling: touch; - &>li { - display:inline-block; - line-height: 1.5; - position: relative; - padding: 0 10px; - a { - display:inline-block; - letter-spacing: -1px; - line-height: 3; - border-bottom: solid 3px #fff; - padding: 0 5px; - .font-l(); - .font-bold(); - } - a:hover, a:focus, &.active a{ - border-bottom: solid 3px @color; - } - } - } - } - - - /* member forms */ - .prn-narrow - { - max-width:400px; - margin:0px auto; - box-sizing:border-box; - } - .prn-body - { - box-sizing:border-box; - border-radius: 25px; - .background-lightgray(); - margin: 0 0 17px; - padding: 1px 0; - &>* - { - padding: 20px; - margin:0; - border: 0; - } - h1 - { - padding: 15px 5px; - margin: 5px 15px; - border-bottom: 1px solid #d1d1d1; - .font-l(); - .font-bold(); - } - dl { - position: relative; - padding:0; - margin:20px; - &>div - { - clear:both; - } - dt { - width: 33%; - max-width: 300px; - .font-bold(); - float:left; - } - dt, dd { - list-style: outside none none; - box-sizing: border-box; - display: inline-block; - margin: 0; - padding: 7px 0; - border: none; - line-height: 1.6; - .font-m(); - } - } - .prn-anchor-buttons - { - margin: 5px 0px; - &>a, &>label, &>button, &>select, &>input[type="submit"] - { - display: block; - width: 100%; - -webkit-appearance: none; - box-sizing: border-box; - border-top: none; - border-right: none; - border-bottom: 1px solid #d1d1d1; - border-left: none; - border-radius: 0px; - background-color: #fff; - padding: 10px 15px; - text-decoration: none; - text-align: center; - .font-m(); - .font-point(); - &:hover, &:focus - { - .bg-contrast(#000; lighten(@color,5%); darken(@color,5%); 4.0); - } - &:first-child - { - border-top: 1px solid #d1d1d1; - } - } - } - } - form - { - overflow: hidden; - width:100%; - white-space: normal; - box-sizing: border-box; - div.control-group - { - &>* - { - display:block; - position:relative; - box-sizing: border-box; - width:100%; - height: auto; - margin:0px; - margin-top: 10px; - } - &>*:first-child - { - margin-top: 0; - } - &>input, &>select, button, #prn_profile_imagetag label.prn_button, &.agreement .text - { - border: 1px solid #d1d1d1; - height: auto; - border-radius: 25px; - margin-top: 0; - padding: 8px 15px; - -webkit-appearance: none; - line-height: 2; - .font-gray(); - .font-m(); - } - &>label, &>div.control-label - { - font-weight: bold; - border:0; - } - } - } - form input[type="submit"], form input.btn.dateRemover, form button, label.prn_button - { - padding: 10px; - vertical-align: bottom; - .bg-contrast(#000; lighten(@color,5%); darken(@color,5%); 3.0); - font-weight: bold; - text-shadow: none; - border:0; - color: #000; - &:hover, &:focus - { - .bg-contrast(#000; lighten(@color,5%); darken(@color,5%); 4.0); - } - } - form div.control-group>input[type="submit"], .prn-footer{ - margin: 20px 0 0; - } - div.prn-profile_image - { - text-align: center; - img - { - border-radius: 50%; - } - #prn_profile_imagetag { - position:relative; - button, label.prn_button - { - padding : 12px 15px; - line-height:1; - position:absolute; - bottom: 0; - right:0; - } - } - } - .prn-footer.prn-anchor-buttons { - text-align:right; - &>a { - display:inline-block; - } - } - - /* message; error, info, update */ - .rx_member-notice, .rx_member-notice.info - { - .bg-contrast(#000; lighten(@color, 5%); darken(@color, 5%); 5.0); - color: #000; - border-radius: 15px; - padding: 15px; - margin: 10px; - margin-top:0; - text-align: justify; - .font-gray(); - .font-m(); - } - .rx_member-notice.error - { - background: #fff3e0; - .text-contrast(#fff3e0); - } - .rx_member-notice.update - { - background: #e8f5e9; - .text-contrast(#e8f5e9); - } - .rx_member-notice>* - { - padding: 0; - margin:0; - } - - /* The list of document style */ - .rx_sw_list .cont_a { - color: #222; - display: block; - letter-spacing: -1px; - line-height: 18px; - margin: 0; - overflow: hidden; - padding: 0.667em 70px 0.733em 15px; - text-overflow: ellipsis; - white-space: nowrap; - text-decoration: none; - &.no_delete { - padding-right: 15px; - } - &:hover, &:focus { - .text-contrast(#fff; darken(@color,5%); lighten(@color,5%)); - } - } - .content_basic{ - position:relative; - display:inline-block; - max-width:100%; - vertical-align: middle; - overflow:hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - .content_subinfo - { - color: #9e9e9e; - font-size: 12px; - margin: 0 3px 0 7px; - overflow:hidden; - } - ul.rx_sw_list { - list-style: outside none none; - padding: 0; - margin: 0; - border-bottom: 1px solid #e0e0e0; - li { - position:relative; - border-top: 1px solid #e0e0e0; - overflow: hidden; - padding:0; - } - .content_delete{ - position:absolute; - top:0; - right:2px; - button, a{ - background: lighten(@color, 5%); - font-size: 14px; - border:0; - border-radius: 25px; - .text-contrast(lighten(@color, 5%)); - padding: 10px 15px; - vertical-align: bottom; - &:hover, &:focus - { - .bg-contrast(#000; lighten(@color,5%); darken(@color,5%); 4.0); - } - } - input[type="checkbox"] - { - width: 30px; - height: 30px; - } - } - } - .pagination ul - { - display:block; - list-style: outside none none; - text-align:center; - padding: 0; - margin: 0; - li{ - display:inline-block; - &>a - { - display: inline-block; - border-radius: 50%; - background-color: #fff; - padding: 10px 15px; - text-decoration: none; - .font-m(); - &:hover, &:focus - { - .bg-contrast(#000; lighten(@color,5%); darken(@color,5%); 4.0); - } - } - &.active>a - { - background-color: #d1d1d1; - font-weight:bold; - color:#000; - } - } - } - .prn-footer - { - &>a - { - display: block; - border-radius: 25px; - border: 1px solid #d1d1d1; - background-color: #fff; - padding: 10px 15px; - text-decoration: none; - text-align: center; - .font-m(); - &:hover, &:focus - { - .bg-contrast(#000; lighten(@color,5%); darken(@color,5%); 4.0); - } - } - } -} - diff --git a/modules/member/m.skins/rx_prn/document_list.html b/modules/member/m.skins/rx_prn/document_list.html deleted file mode 100644 index ea7c3c0d0..000000000 --- a/modules/member/m.skins/rx_prn/document_list.html +++ /dev/null @@ -1,45 +0,0 @@ - -
                -

                {$member_title = lang('member.cmd_view_own_document')}

                -
                - {lang('common.total')}: {number_format($total_count)} -
                - -
                - - - diff --git a/modules/member/m.skins/rx_prn/find_member_account.html b/modules/member/m.skins/rx_prn/find_member_account.html deleted file mode 100644 index eb3cf0e1d..000000000 --- a/modules/member/m.skins/rx_prn/find_member_account.html +++ /dev/null @@ -1,86 +0,0 @@ - -
                -

                {lang('member.cmd_find_member_account_with_email')}

                -
                -

                {$XE_VALIDATOR_MESSAGE}

                -
                -

                {lang('member.about_find_member_account')}

                -
                - - - - - -
                - - - {$captcha}
                - -
                -
                -
                -
                -

                {$lang->cmd_find_member_account_with_email_question}

                -
                -

                {$XE_VALIDATOR_MESSAGE}

                -
                -

                {lang('member.about_find_account_question')}

                -
                - - - - - - - -
                - - - - - - -
                -
                - -
                -
                - {$captcha}
                - -
                -
                -
                -
                -

                {lang('member.cmd_resend_auth_mail')}

                -
                -

                {$XE_VALIDATOR_MESSAGE}

                -
                -

                {lang('member.about_resend_auth_mail')}

                -
                - - - - -
                - -
                - {$captcha}
                - -
                -
                -
                - \ No newline at end of file diff --git a/modules/member/m.skins/rx_prn/find_member_account_mail.html b/modules/member/m.skins/rx_prn/find_member_account_mail.html deleted file mode 100644 index c1ed9dc2c..000000000 --- a/modules/member/m.skins/rx_prn/find_member_account_mail.html +++ /dev/null @@ -1,21 +0,0 @@ -
                -
                -

                {lang('member.msg_find_account_info')}

                -
                -
                {lang('member.site')}
                -
                - {getUrl()} -
                - -
                {lang($name)}
                -
                {$value}
                -
                -
                {lang('common.password')}
                -
                {$auth_args->new_password}
                -
                -

                - {lang('member.msg_find_account_comment')}
                - {$find_url} -

                -
                -
                \ No newline at end of file diff --git a/modules/member/m.skins/rx_prn/find_temp_password.html b/modules/member/m.skins/rx_prn/find_temp_password.html deleted file mode 100644 index ca699b53e..000000000 --- a/modules/member/m.skins/rx_prn/find_temp_password.html +++ /dev/null @@ -1,12 +0,0 @@ - -
                -

                {lang('member.cmd_find_member_account')}

                -
                {lang('member.about_temp_password')}
                -
                -
                {lang('common.user_id')}
                -
                {$user_id}
                -
                {lang('member.temp_password')}
                -
                {$temp_password}
                -
                -
                - diff --git a/modules/member/m.skins/rx_prn/images/member.png b/modules/member/m.skins/rx_prn/images/member.png deleted file mode 100644 index 120d3b48b..000000000 Binary files a/modules/member/m.skins/rx_prn/images/member.png and /dev/null differ diff --git a/modules/member/m.skins/rx_prn/images/member.svg b/modules/member/m.skins/rx_prn/images/member.svg deleted file mode 100644 index 46ebe44cc..000000000 --- a/modules/member/m.skins/rx_prn/images/member.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/modules/member/m.skins/rx_prn/js/member.js b/modules/member/m.skins/rx_prn/js/member.js deleted file mode 100644 index f346449f1..000000000 --- a/modules/member/m.skins/rx_prn/js/member.js +++ /dev/null @@ -1,185 +0,0 @@ -/* 사용자 추가 */ -function completeInsert(ret_obj, response_tags, args, fo_obj) { - var error = ret_obj['error']; - var message = ret_obj['message']; - var redirect_url = ret_obj['redirect_url']; - - alert(message); - - if(current_url.getQuery('popup')==1) { - if(typeof(opener)!='undefined') opener.location.reload(); - window.close(); - } else { - if(redirect_url) location.href = redirect_url; - else location.href = current_url.setQuery('act',''); - } -} - -/* 정보 수정 */ -function completeModify(ret_obj, response_tags, args, fo_obj) { - var error = ret_obj['error']; - var message = ret_obj['message']; - - alert(message); - - location.href = current_url.setQuery('act','dispMemberInfo'); -} - -/* 회원 탈퇴 */ -function completeLeave(ret_obj, response_tags, args, fo_obj) { - var error = ret_obj['error']; - var message = ret_obj['message']; - - alert(message); - - location.href = current_url.setQuery('act',''); -} - -/* 이미지 업로드 */ -function _doUploadImage(fo_obj, act) { - fo_obj.act.value = act; - fo_obj.submit(); -} - -/* 프로필 이미지/ 이미지 이름/마크 등록 */ -function doUploadProfileImage() { - var fo_obj = get_by_id("fo_insert_member"); - if(!fo_obj.profile_image.value) return; - _doUploadImage(fo_obj, 'procMemberInsertProfileImage'); -} -function doUploadImageName() { - var fo_obj = get_by_id("fo_insert_member"); - if(!fo_obj.image_name.value) return; - _doUploadImage(fo_obj, 'procMemberInsertImageName'); -} - -function doUploadImageMark() { - var fo_obj = get_by_id("fo_insert_member"); - if(!fo_obj.image_mark.value) return; - _doUploadImage(fo_obj, 'procMemberInsertImageMark'); -} - - -/* 로그인 후 */ -function completeLogin(ret_obj, response_tags, params, fo_obj) { - if(fo_obj.remember_user_id && fo_obj.remember_user_id.checked) { - var expire = new Date(); - expire.setTime(expire.getTime()+ (7000 * 24 * 3600000)); - setCookie('user_id', fo_obj.user_id.value, expire); - } - - var url = current_url.setQuery('act',''); - location.href = current_url.setQuery('act',''); -} - -/* 로그아웃 후 */ -function completeLogout(ret_obj) { - location.href = current_url.setQuery('act',''); -} - -/* 인증 메일 재발송 후 */ -function completeResendAuthMail(ret_obj, response_tags) { - var error = ret_obj['error']; - var message = ret_obj['message']; - - if(message) alert(message); - if(error != 0) alert(error); -} - -/* 프로필 이미지, 이미지 이름, 마크 삭제 */ -function doDeleteProfileImage(member_srl) { - if (!member_srl) return; - - if (!confirm(xe.lang.deleteProfileImage)) return false; - - exec_xml( - 'member', - 'procMemberDeleteProfileImage', - {member_srl:member_srl}, - function(){ - $('#profile_imagetag').remove(); - var html = ''; - $('#prn_profile_imagetag').html(html); - }, - ['error','message'] - ); -} - -function doDeleteImageName(member_srl) { - if (!member_srl) return; - - if (!confirm(xe.lang.deleteImageName)) return false; - exec_xml( - 'member', - 'procMemberDeleteImageName', - {member_srl:member_srl}, - function(){jQuery('#image_nametag').remove()}, - ['error','message'] - ); -} - -function doDeleteImageMark(member_srl) { - if (!member_srl) return; - - if (!confirm(xe.lang.deleteImageMark)) return false; - exec_xml( - 'member', - 'procMemberDeleteImageMark', - {member_srl:member_srl}, - function(){jQuery('#image_marktag').remove()}, - ['error','message'] - ); -} - -/* 스크랩 삭제 */ -function doDeleteScrap(document_srl) { - var params = new Array(); - params['document_srl'] = document_srl; - exec_xml('member', 'procMemberDeleteScrap', params, function() { location.reload(); }); -} - -/* 비밀번호 찾기 후 */ -function completeFindMemberAccount(ret_obj, response_tags) { - alert(ret_obj['message']); -} - -/* 임시 비밀번호 생성 */ -function completeFindMemberAccountByQuestion(ret_obj, response_tags) { - if(ret_obj['error'] != 0){ - alert(ret_obj['message']); - }else{ - location.href = current_url.setQuery('act','dispMemberGetTempPassword').setQuery('user_id',ret_obj['user_id']); - } -} - -/* 저장글 삭제 */ -function doDeleteSavedDocument(document_srl, confirm_message) { - if(!confirm(confirm_message)) return false; - - var params = new Array(); - params['document_srl'] = document_srl; - exec_xml('member', 'procMemberDeleteSavedDocument', params, function() { location.reload(); }); -} - -function insertSelectedModule(id, module_srl, mid, browser_title) { - location.href = current_url.setQuery('selected_module_srl',module_srl); -} - -function isRxPrnTouchable() { - var el = document.createElement('div'); - el.setAttribute('ontouchstart', 'return;'); // or try "ontouchstart" - return typeof el.ontouchstart === "function"; -} - -$(document).ready(function() { - if(isRxPrnTouchable()) { - $(".rx_prn_member div.rx_prn_tab ul.rx_prn_tab").css('white-space', 'nowrap'); - try - { - $(".rx_prn_member div.rx_prn_tab ul.rx_prn_tab").animate({ - scrollLeft: $(".rx_prn_member div.rx_prn_tab ul.rx_prn_tab li.active").offset().left - }, 300); - } catch (e) { - } - } -}); \ No newline at end of file diff --git a/modules/member/m.skins/rx_prn/js/signup_check.js b/modules/member/m.skins/rx_prn/js/signup_check.js deleted file mode 100644 index 3215c03d3..000000000 --- a/modules/member/m.skins/rx_prn/js/signup_check.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * @brief 회원 가입시나 정보 수정시 각 항목의 중복 검사하고 화면에 바로 나타냄 - * @author misol - * @author NAVER (developer@xpressengine.com) - **/ -// body 에서 불러오면 가능 -$('#rx_insert_member :input').filter('[name=user_id],[name=nick_name],[name=email_address]').blur(rxMemberCheckValue); - -// 실제 서버에 특정 필드의 value check를 요청하고 이상이 있으면 메세지를 뿌려주는 함수 -function rxMemberCheckValue(event) { - var field = event.target; - var _name = field.name; - var _value = field.value; - if(!_name || !_value) return; - - var params = {name:_name, value:_value}; - var response_tags = ['error','message','message_type']; - - exec_xml('member','procMemberCheckValue', params, dispMemberValueCheck, response_tags, field); -} - -// 서버에서 응답이 올 경우 이상이 있으면 메세지를 출력 -function dispMemberValueCheck(response, response_tags, field) { - var _id = 'rx_sw_dummy-'+field.name; - var dummy = $('#'+_id); - - if(response['message']=='success') { - dummy.html('').hide(); - return; - } - - if (!dummy.length) { - dummy = $('

                ').attr('id', _id) - $(field).after(dummy); - } - - dummy.html(response['message']).show(); -} - -$(document).ready(function(){ - // label for setup - $('.control-label[for]').each(function(){ - var $this = $(this); - if($this.attr('for') == ''){ - $this.attr('for', $this.next().children(':visible:first').attr('id')); - } - }); - // image input - var html = ''; - if(prn_profile_image.exists !== false) - { - html = ''; - } - else - { - html = ''; - } - $('#profile_image') - .after('

                ') - .css('display','none') - .change(function() { - if (this.files && this.files[0]) { - var reader = new FileReader(); - reader.onload = function (e) { - $('#prn_profile_imagetag img').attr('src', e.target.result); - } - reader.readAsDataURL(this.files[0]); - } else { - $('#prn_profile_imagetag img').attr('src', './modules/member/m.skin/rx_prn/images/member.svg').attr('width', prn_profile_image.width).attr('height', prn_profile_image.height); - } - }); - // label for profile images - $('#profile_imagetag').each(function() { - $(this).html(function(i, oldhtml) { - $(this).attr('id', ''); - return ''; - }); - }); -}); \ No newline at end of file diff --git a/modules/member/m.skins/rx_prn/lang/en.php b/modules/member/m.skins/rx_prn/lang/en.php deleted file mode 100644 index 00a84f466..000000000 --- a/modules/member/m.skins/rx_prn/lang/en.php +++ /dev/null @@ -1,2 +0,0 @@ -member_rx_prn_about_star = 'Items with an asterisk * are required.'; \ No newline at end of file diff --git a/modules/member/m.skins/rx_prn/lang/ja.php b/modules/member/m.skins/rx_prn/lang/ja.php deleted file mode 100644 index 6dd311d30..000000000 --- a/modules/member/m.skins/rx_prn/lang/ja.php +++ /dev/null @@ -1,2 +0,0 @@ -member_rx_prn_about_star = 'アスタリスク*のある項目は必須項目です。'; \ No newline at end of file diff --git a/modules/member/m.skins/rx_prn/lang/ko.php b/modules/member/m.skins/rx_prn/lang/ko.php deleted file mode 100644 index ce7964dae..000000000 --- a/modules/member/m.skins/rx_prn/lang/ko.php +++ /dev/null @@ -1,2 +0,0 @@ -member_rx_prn_about_star = '별표*가 있는 항목은 필수 항목입니다.'; \ No newline at end of file diff --git a/modules/member/m.skins/rx_prn/leave_form.html b/modules/member/m.skins/rx_prn/leave_form.html deleted file mode 100644 index 80b2de4e2..000000000 --- a/modules/member/m.skins/rx_prn/leave_form.html +++ /dev/null @@ -1,28 +0,0 @@ - -
                -
                -

                {$member_title = $lang->msg_leave_member}

                -
                -

                {$XE_VALIDATOR_MESSAGE}

                -
                -

                {$lang->about_rechecked_password}

                -
                - - - - -
                - - - - - -
                -
                -
                -
                - diff --git a/modules/member/m.skins/rx_prn/login_form.html b/modules/member/m.skins/rx_prn/login_form.html deleted file mode 100644 index 524c88a9c..000000000 --- a/modules/member/m.skins/rx_prn/login_form.html +++ /dev/null @@ -1,53 +0,0 @@ - -
                -
                -

                {lang('member.cmd_login')}

                -
                -

                {$XE_VALIDATOR_MESSAGE}

                -
                -
                - - - -
                - - - - - - -
                - {lang('member.about_keep_warning')} -
                - -
                -
                -
                - -
                - - diff --git a/modules/member/m.skins/rx_prn/logout.html b/modules/member/m.skins/rx_prn/logout.html deleted file mode 100644 index c9b16e147..000000000 --- a/modules/member/m.skins/rx_prn/logout.html +++ /dev/null @@ -1,16 +0,0 @@ - -
                -
                -

                {lang('member.cmd_logout')}

                -

                {lang('common.confirm_logout')}

                -
                - - - -
                - -
                -
                -
                -
                - diff --git a/modules/member/m.skins/rx_prn/member_info.html b/modules/member/m.skins/rx_prn/member_info.html deleted file mode 100644 index 862036b4c..000000000 --- a/modules/member/m.skins/rx_prn/member_info.html +++ /dev/null @@ -1,32 +0,0 @@ - -
                -
                -

                {$lang->member_info}

                -
                -

                {$XE_VALIDATOR_MESSAGE}

                -
                - -
                - {$item->value} -
                -
                -
                -
                -
                {$item->title}*
                {$item->value}
                -
                -
                {$lang->member_group}
                {implode(', ', $memberInfo['group_list'])}
                -
                {$lang->signup_date}
                {zdate($memberInfo[regdate],"Y-m-d")}
                -
                -
                {$lang->last_login}
                -
                {zdate($memberInfo[last_login],"Y-m-d")}
                -
                -
                - -
                -
                - \ No newline at end of file diff --git a/modules/member/m.skins/rx_prn/member_nick.html b/modules/member/m.skins/rx_prn/member_nick.html deleted file mode 100644 index fbd185fce..000000000 --- a/modules/member/m.skins/rx_prn/member_nick.html +++ /dev/null @@ -1,37 +0,0 @@ - -
                -

                {$member_title = $lang->cmd_modify_nickname_log}

                - - - - - - - - - - - - - - - - -
                {$lang->date}{$lang->nick_name_before_changing}{$lang->nick_name_after_changing}
                - {zdate($val->regdate,"Y-m-d H:i:s")} - - {$val->before_nick_name} - - {$val->after_nick_name} -
                -
                - - \ No newline at end of file diff --git a/modules/member/m.skins/rx_prn/modify_email_address.html b/modules/member/m.skins/rx_prn/modify_email_address.html deleted file mode 100644 index d5475b1d7..000000000 --- a/modules/member/m.skins/rx_prn/modify_email_address.html +++ /dev/null @@ -1,17 +0,0 @@ - -

                {$lang->cmd_modify_member_email_address}

                -

                {$lang->about_modify_member_email_address}

                -
                -

                {$XE_VALIDATOR_MESSAGE}

                -
                -
                - - - - - - - - -
                - diff --git a/modules/member/m.skins/rx_prn/modify_info.html b/modules/member/m.skins/rx_prn/modify_info.html deleted file mode 100644 index db5e44f51..000000000 --- a/modules/member/m.skins/rx_prn/modify_info.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - -
                -
                -

                {$lang->msg_update_member}

                -
                -

                {$XE_VALIDATOR_MESSAGE}

                -
                -
                -

                {lang('member_rx_prn_about_star')}

                -
                -
                - - - - - - -
                - -
                - {$item->inputTag} -
                -
                - - - - - {$formTag->inputTag} - {$editor|noescape} - - -
                - - -
                - -
                - -
                - -
                -
                -
                - -
                - diff --git a/modules/member/m.skins/rx_prn/modify_password.html b/modules/member/m.skins/rx_prn/modify_password.html deleted file mode 100644 index a3ab5512f..000000000 --- a/modules/member/m.skins/rx_prn/modify_password.html +++ /dev/null @@ -1,37 +0,0 @@ - - -
                -

                {$member_title = $lang->cmd_modify_member_password}

                -
                -

                {$XE_VALIDATOR_MESSAGE}

                -
                -
                - - - - - - - -
                - - - - - - {$lang->about_password_strength[$member_config->password_strength]} - - - -
                -
                -
                - diff --git a/modules/member/m.skins/rx_prn/rechecked_password.html b/modules/member/m.skins/rx_prn/rechecked_password.html deleted file mode 100644 index c1570c8c5..000000000 --- a/modules/member/m.skins/rx_prn/rechecked_password.html +++ /dev/null @@ -1,28 +0,0 @@ - -
                -
                -

                {$lang->msg_rechecked_password}

                -
                -

                {$XE_VALIDATOR_MESSAGE}

                -
                -

                {$lang->about_rechecked_password}

                -
                - - - - -
                - - - - - -
                -
                -
                -
                - diff --git a/modules/member/m.skins/rx_prn/resend_auth_mail.html b/modules/member/m.skins/rx_prn/resend_auth_mail.html deleted file mode 100644 index 25becead6..000000000 --- a/modules/member/m.skins/rx_prn/resend_auth_mail.html +++ /dev/null @@ -1,21 +0,0 @@ - -
                -

                {$lang->cmd_resend_auth_mail}

                -
                {lang('member.about_resend_auth_mail')}
                -
                -

                {$XE_VALIDATOR_MESSAGE}

                -
                -
                - - - -
                - - - -
                -
                -
                - diff --git a/modules/member/m.skins/rx_prn/reset_mail.html b/modules/member/m.skins/rx_prn/reset_mail.html deleted file mode 100644 index 3870e4579..000000000 --- a/modules/member/m.skins/rx_prn/reset_mail.html +++ /dev/null @@ -1,41 +0,0 @@ - -
                -

                {$lang->cmd_resend_auth_mail}

                -
                -

                {$XE_VALIDATOR_MESSAGE}

                -
                -
                {sprintf($lang->about_reset_auth_mail, $memberInfo->email_address)}
                -
                - - - - -
                - - - -
                -
                -
                -
                -

                {lang('member.cmd_send_auth_new_emaill_address')}

                -
                -

                {$XE_VALIDATOR_MESSAGE}

                -
                -
                {lang('member.about_reset_auth_mail_submit')}
                -
                - - - -
                - - - -
                -
                -
                - diff --git a/modules/member/m.skins/rx_prn/saved_list.html b/modules/member/m.skins/rx_prn/saved_list.html deleted file mode 100644 index def588105..000000000 --- a/modules/member/m.skins/rx_prn/saved_list.html +++ /dev/null @@ -1,36 +0,0 @@ - -
                -

                {$member_title = lang('member.cmd_view_saved_document')}

                -
                {lang('common.total')}: {number_format($total_count)}
                - -
                - - diff --git a/modules/member/m.skins/rx_prn/scrapped_list.html b/modules/member/m.skins/rx_prn/scrapped_list.html deleted file mode 100644 index 6589fd418..000000000 --- a/modules/member/m.skins/rx_prn/scrapped_list.html +++ /dev/null @@ -1,36 +0,0 @@ - -
                -

                {$member_title = lang('member.cmd_view_scrapped_document')}

                -
                {lang('common.total')}: {number_format($total_count)}
                - -
                - - diff --git a/modules/member/m.skins/rx_prn/signup_form.html b/modules/member/m.skins/rx_prn/signup_form.html deleted file mode 100644 index a2f6a8a42..000000000 --- a/modules/member/m.skins/rx_prn/signup_form.html +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - -
                -
                -

                {lang('member.cmd_signup')}

                -
                -

                {$XE_VALIDATOR_MESSAGE}

                -
                -
                -

                {lang('member_rx_prn_about_star')}

                -
                -
                - - - -
                -
                - {$agreement->title} - * - ({$lang->cmd_optional}) -
                -
                - {$agreement->content} -
                -
                - -
                -
                -
                - -
                - {$item->inputTag} -
                -
                - - -

                - {$lang->msg_email_confirmation_required} -

                - - -

                {lang('member.about_password_strength')[$member_config->password_strength]}

                - - - - - - {$formTag->inputTag} -

                - {$lang->msg_email_confirmation_required} -

                -
                - - - {$editor|noescape} - -
                -
                {$lang->allow_mailing}
                -
                - - -
                -
                {$lang->allow_message}
                -
                - -
                - -
                {$lang->captcha}
                -
                {$captcha}
                -
                - -
                -
                -
                -
                - - diff --git a/modules/member/m.skins/rx_prn/skin.xml b/modules/member/m.skins/rx_prn/skin.xml deleted file mode 100644 index 693643a74..000000000 --- a/modules/member/m.skins/rx_prn/skin.xml +++ /dev/null @@ -1,108 +0,0 @@ - - - 필요할 때 라이믹스 - Rhymix PRN - 필요할 때 꺼내보는 라이믹스. 캡슐 모양의 둥근 라이믹스 회원 모듈 스킨입니다. - You need Rhymix. A member skin with round, capsule-like shapes. - 0.1 - 2017-08-06 - - misol - misol - - - 마더캣 - Mothercat - - - - - 사이트 테마 색 - The site theme color - - - 붉은 색 - Red - - - 크림슨 - Crimson - - - 분홍 - Pink - - - 보라 - Purple - - - 진보라 - Deep Purple - - - 인디고 - Indigo - - - 짙은 파랑 - Deep Blue - - - 파랑 - Blue - - - 밝은 파랑 - Light Blue - - - 시안 - Cyan - - - - Teal - - - 초록 - Green - - - 연한 초록 - Light Green - - - 라임 - Lime - - - 노랑 - Yellow - - - 앰버 - Amber - - - 주황 - Orange - - - 진한 주황 - Deep Orange - - - 갈색 - Brown - - - 회색 - Grey - - - 푸른 회색 - Blue Grey - - - diff --git a/modules/member/member.admin.controller.php b/modules/member/member.admin.controller.php index b6f865e5b..1bc72fe41 100644 --- a/modules/member/member.admin.controller.php +++ b/modules/member/member.admin.controller.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * member module of the admin controller class */ -class memberAdminController extends member +class MemberAdminController extends Member { /** * Initialization @@ -24,20 +24,20 @@ class memberAdminController extends member // if(Context::getRequestMethod() == "GET") return new Object(-1, "msg_invalid_request"); // Extract the necessary information in advance $logged_info = Context::get('logged_info'); - if($logged_info->is_admin != 'Y' || !checkCSRF()) + if($logged_info->is_admin != 'Y' || !Rhymix\Framework\Security::checkCSRF()) { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - $args = Context::gets('member_srl','email_address','find_account_answer', 'allow_mailing','allow_message','denied','is_admin','description','group_srl_list','limit_date'); - $oMemberModel = &getModel ('member'); - $config = $oMemberModel->getMemberConfig (); + $args = Context::gets('member_srl','email_address','find_account_answer', 'allow_mailing','allow_message','is_admin','denied','status','description','group_srl_list','limit_date'); + $oMemberModel = getModel('member'); + $config = $oMemberModel->getMemberConfig(); $getVars = array(); if($config->signupForm) { foreach($config->signupForm as $formInfo) { - if($formInfo->isDefaultForm && ($formInfo->isUse || $formInfo->required || $formInfo->mustRequired)) + if($formInfo->isUse || $formInfo->required || $formInfo->mustRequired) { $getVars[] = $formInfo->name; } @@ -46,14 +46,16 @@ class memberAdminController extends member foreach($getVars as $val) { $args->{$val} = Context::get($val); + if ($val === 'phone_number') + { + $args->phone_country = preg_replace('/[^A-Z]/', '', Context::get('phone_country')); + } } $member_srl = Context::get('member_srl'); // Check if an original member exists having the member_srl $args->member_srl = $member_srl; if($args->member_srl) { - // Create a member model object - $oMemberModel = getModel('member'); // Get memebr profile $columnList = array('member_srl'); $member_info = $oMemberModel->getMemberInfoByMemberSrl($args->member_srl, 0, $columnList); @@ -73,31 +75,86 @@ class memberAdminController extends member } } - // Remove some unnecessary variables from all the vars + // Get existing extra vars + if($args->member_srl) + { + $output = executeQuery('member.getMemberInfoByMemberSrl', ['member_srl' => $args->member_srl], ['extra_vars']); + $extra_vars = ($output->data && $output->data->extra_vars) ? unserialize($output->data->extra_vars) : new stdClass; + if (!is_object($extra_vars)) + { + $extra_vars = new stdClass; + } + foreach(self::NOUSE_EXTRA_VARS as $key) + { + unset($extra_vars->$key); + } + } + else + { + $extra_vars = new stdClass; + } + + // Get list of extra vars $all_args = Context::getRequestVars(); - unset($all_args->module); - unset($all_args->act); - unset($all_args->mid); - unset($all_args->error_return_url); - unset($all_args->success_return_url); - unset($all_args->ruleset); - if(!isset($args->limit_date)) $args->limit_date = ""; - unset($all_args->password); - unset($all_args->password2); - unset($all_args->reset_password); - // Add extra vars after excluding necessary information from all the requested arguments - $extra_vars = delObjectVars($all_args, $args); + foreach($config->signupForm as $formInfo) + { + if (!$formInfo->isDefaultForm) + { + $extra_vars->{$formInfo->name} = $all_args->{$formInfo->name}; + } + } + foreach(self::ADMIN_EXTRA_VARS as $key) + { + $extra_vars->{$key} = escape(utf8_clean($all_args->{$key} ?? '')); + } $args->extra_vars = serialize($extra_vars); - // remove whitespace - $checkInfos = array('user_id', 'user_name', 'nick_name', 'email_address'); - foreach($checkInfos as $val) + // Normalize denied and status columns + if (!in_array($args->status ?? '', self::STATUS_LIST)) { - if(isset($args->{$val})) + $args->status = 'APPROVED'; + } + $args->denied = ($args->status === 'APPROVED') ? 'N' : 'Y'; + + // Delete invalid or past limit dates #1334 + if (!isset($args->limit_date)) + { + $args->limit_date = ''; + } + elseif ($args->limit_date < date('Ymd')) + { + $args->limit_date = ''; + } + + // remove whitespace + foreach (['user_id', 'email_address'] as $val) + { + if (isset($args->{$val})) { - $args->{$val} = preg_replace('/[\pZ\pC]+/u', '', html_entity_decode($args->{$val})); + $args->{$val} = preg_replace('/[\pZ\pC]+/u', '', utf8_clean(html_entity_decode($args->{$val}))); } } + if (isset($args->user_name)) + { + $args->user_name = utf8_normalize_spaces(utf8_clean(html_entity_decode($args->user_name))); + } + if (isset($args->nick_name)) + { + if (isset($config->nickname_spaces) && $config->nickname_spaces === 'Y') + { + $args->nick_name = utf8_normalize_spaces(utf8_clean(html_entity_decode($args->nick_name))); + } + else + { + $args->nick_name = preg_replace('/[\pZ\pC]+/u', '', utf8_clean(html_entity_decode($args->nick_name))); + } + } + + // 실제로 디비쿼리시 빈값이 없다면 해당 쿼리를 무시하고 업데이트 하기 때문에 메모의 내용이 삭제가 되지 않습니다. + if(!isset($args->description)) + { + $args->description = ''; + } $oMemberController = getController('member'); // Execute insert or update depending on the value of member_srl @@ -114,19 +171,29 @@ class memberAdminController extends member } if(!$output->toBool()) return $output; - + // Invalidate sessions if denied or limited if ($args->denied === 'Y' || $args->limited >= date('Ymd')) { $validity_info = Rhymix\Framework\Session::getValidityInfo($args->member_srl); $validity_info->invalid_before = time(); Rhymix\Framework\Session::setValidityInfo($args->member_srl, $validity_info); - executeQuery('member.deleteAutologin', (object)array('member_srl' => $args->member_srl)); + executeQuery('member.deleteAutologin', ['member_srl' => $args->member_srl]); } - + + // Invalidate auth mail if denied or limited + if ($args->denied === 'Y' || $args->limited >= date('Ymd')) + { + executeQuery('member.deleteAuthMail', ['member_srl' => $args->member_srl]); + } + // Save Signature $signature = Context::get('signature'); $oMemberController->putSignature($args->member_srl, $signature); + if($config->member_allow_fileupload === 'Y') + { + getController('file')->setFilesValid($args->member_srl, 'sig'); + } $profile_image = Context::get('profile_image'); if(is_uploaded_file($profile_image['tmp_name'])) @@ -150,8 +217,8 @@ class memberAdminController extends member } // Clear cache - $oMemberController->_clearMemberCache($args->member_srl); - + MemberController::clearMemberCache($args->member_srl); + // Return result $this->add('member_srl', $args->member_srl); $this->setMessage($msg_code); @@ -180,23 +247,96 @@ class memberAdminController extends member public function procMemberAdminInsertDefaultConfig() { $args = Context::gets( + 'member_mid', + 'force_mid', 'enable_join', + 'enable_join_key', 'enable_confirm', + 'authmail_expires', + 'authmail_expires_unit', 'password_strength', 'password_hashing_algorithm', 'password_hashing_work_factor', 'password_hashing_auto_upgrade', 'password_change_invalidate_other_sessions', + 'password_reset_method', + 'allow_nickname_change', 'update_nickname_log', + 'nickname_symbols', + 'nickname_symbols_allowed_list', + 'nickname_spaces', 'allow_duplicate_nickname', 'member_profile_view' ); - + + // Update member mid + $config = MemberModel::getMemberConfig(); + if ($args->member_mid !== ($config->mid ?? null)) + { + if (!preg_match('/^[a-z][a-z0-9_]+$/i', $args->member_mid)) + { + return new BaseObject(-1, 'msg_limit_mid'); + } + + if (!empty($config->mid) && $this->checkMid($config->mid) == 1) + { + $module_info = \ModuleModel::getModuleInfoByMid($config->mid); + } + else + { + $module_info = null; + } + + if ($module_info) + { + $module_info->mid = $args->member_mid; + $output = ModuleController::getInstance()->updateModule($module_info); + } + else + { + $output = $this->createMid($args->member_mid, $config->skin ?: 'default', $config->mskin ?: 'default'); + } + + if ($output->toBool()) + { + $args->mid = $args->member_mid; + unset($args->member_mid); + } + else + { + return $output; + } + } + + $args->force_mid = ($args->force_mid === 'Y'); + + // Update join key + if ($args->enable_join === 'KEY') + { + $args->enable_join = 'N'; + $args->enable_join_key = escape(trim(utf8_normalize_spaces(rawurldecode($args->enable_join_key)))); + } + else + { + $args->enable_join_key = null; + } + + $args->authmail_expires = max(0, intval($args->authmail_expires)); + if(!$args->authmail_expires) + { + $args->authmail_expires = 1; + } + $args->authmail_expires_unit = intval($args->authmail_expires_unit); + if(!in_array($args->authmail_expires_unit, [1, 60, 3600, 86400])) + { + $args->authmail_expires_unit = 86400; + } + if(!array_key_exists($args->password_hashing_algorithm, Rhymix\Framework\Password::getSupportedAlgorithms())) { $args->password_hashing_algorithm = 'md5'; } - + $args->password_hashing_work_factor = intval($args->password_hashing_work_factor, 10); if($args->password_hashing_work_factor < 4) { @@ -210,6 +350,13 @@ class memberAdminController extends member { $args->password_hashing_auto_upgrade = 'N'; } + $args->password_reset_method = intval($args->password_reset_method); + if(!in_array($args->nickname_symbols, ['Y', 'N', 'LIST'])) + { + $args->nickname_symbols = 'Y'; + } + $args->nickname_symbols_allowed_list = utf8_trim($args->nickname_symbols_allowed_list); + $args->nickname_spaces = (isset($args->nickname_spaces) && $args->nickname_spaces === 'Y') ? 'Y' : 'N'; $oModuleController = getController('module'); $output = $oModuleController->updateModuleConfig('member', $args); @@ -225,7 +372,7 @@ class memberAdminController extends member { $config = new stdClass; $config->features = array(); - + $args = Context::gets( 'scrapped_documents', 'saved_documents', @@ -238,7 +385,7 @@ class memberAdminController extends member { $config->features[$key] = toBool($value); } - + $oModuleController = getController('module'); $output = $oModuleController->updateModuleConfig('member', $config); @@ -253,7 +400,8 @@ class memberAdminController extends member { $config = new stdClass; $config->agreements = array(); - + $config->agreement = null; + $args = Context::getRequestVars(); for ($i = 1; $i < 20; $i++) { @@ -272,12 +420,23 @@ class memberAdminController extends member $config->agreements[$i] = $agreement; } } - - // for compatibility with older versions - $config->agreement = $config->agreements[1]->content; - + $oModuleController = getController('module'); $output = $oModuleController->updateModuleConfig('member', $config); + if (!$output->toBool()) + { + return $output; + } + + // Delete old agreement files. + foreach (Context::loadLangSupported() as $key => $val) + { + $agreement_file = RX_BASEDIR . 'files/member_extra_info/agreement_' . $key . '.txt'; + if (Rhymix\Framework\Storage::exists($agreement_file)) + { + Rhymix\Framework\Storage::delete($agreement_file); + } + } // default setting end $this->setMessage('success_updated'); @@ -285,21 +444,24 @@ class memberAdminController extends member $returnUrl = Context::get('success_return_url') ? Context::get('success_return_url') : getNotEncodedUrl('', 'module', 'admin', 'act', 'dispMemberAdminAgreementsConfig'); $this->setRedirectUrl($returnUrl); } - + public function procMemberAdminInsertSignupConfig() { $oMemberModel = getModel('member'); + $config = $oMemberModel->getMemberConfig(); + $oModuleController = getController('module'); $args = Context::gets( 'limit_day', 'limit_day_description', 'emailhost_check', - 'redirect_url', - 'profile_image', 'profile_image_max_width', 'profile_image_max_height', 'profile_image_max_filesize', - 'image_name', 'image_name_max_width', 'image_name_max_height', 'image_name_max_filesize', - 'image_mark', 'image_mark_max_width', 'image_mark_max_height', 'image_mark_max_filesize', - 'signature_editor_skin', 'sel_editor_colorset', 'signature_html', 'signature_html_retroact', 'member_allow_fileupload' + 'special_phone_number', 'special_phone_code', 'max_auth_sms_count', 'max_auth_sms_count_time', 'redirect_url', + 'phone_number_default_country', 'phone_number_hide_country', 'phone_number_allow_duplicate', 'phone_number_verify_by_sms', + 'profile_image_max_width', 'profile_image_max_height', 'profile_image_max_filesize', 'profile_image_force_ratio', + 'image_name_max_width', 'image_name_max_height', 'image_name_max_filesize', + 'image_mark_max_width', 'image_mark_max_height', 'image_mark_max_filesize', + 'signature_editor_skin', 'sel_editor_colorset', 'signature_max_height', 'signature_html', 'signature_html_retroact', 'member_allow_fileupload', 'member_max_filesize' ); $list_order = Context::get('list_order'); @@ -307,37 +469,41 @@ class memberAdminController extends member $all_args = Context::getRequestVars(); $args->limit_day = (int)$args->limit_day; + $args->limit_day_description = escape(trim(utf8_clean($args->limit_day_description))); if($args->emailhost_check != 'allowed' && $args->emailhost_check != 'prohibited') $args->emailhost_check == 'allowed'; - if($args->redirect_url) + $args->special_phone_number = preg_replace('/[^0-9]/', '', $args->special_phone_number); + $args->special_phone_code = preg_replace('/[^0-9]/', '', $args->special_phone_code); + if ($args->special_phone_code !== '' && strlen($args->special_phone_code) !== 6) { - $oModuleModel = getModel('module'); - $redirectModuleInfo = $oModuleModel->getModuleInfoByModuleSrl($args->redirect_url, array('mid')); - - if(!$redirectModuleInfo) - { - return new BaseObject('-1', 'msg_exist_selected_module'); - } - - $args->redirect_mid = $redirectModuleInfo->mid; - $args->redirect_url = getNotEncodedFullUrl('','mid',$redirectModuleInfo->mid); + return new BaseObject('-1', 'msg_special_code_incorrect_format'); } + $args->max_auth_sms_count = max(0, intval($args->max_auth_sms_count)); + $args->max_auth_sms_count_time = max(0, intval($args->max_auth_sms_count_time)); + $args->redirect_mid = ''; + $args->redirect_url = utf8_trim($args->redirect_url); - $args->profile_image = $args->profile_image ? 'Y' : 'N'; - $args->image_name = $args->image_name ? 'Y' : 'N'; - $args->image_mark = $args->image_mark ? 'Y' : 'N'; - $args->signature = $args->signature != 'Y' ? 'N' : 'Y'; - $args->identifier = $all_args->identifier; + $args->phone_number_default_country = preg_replace('/[^A-Z]/', '', $args->phone_number_default_country); + if (!array_key_exists($args->phone_number_default_country, Rhymix\Framework\i18n::listCountries())) + { + return new BaseObject('-1', 'msg_need_default_country'); + } + $args->phone_number_hide_country = $args->phone_number_hide_country == 'Y' ? 'Y' : 'N'; + $args->phone_number_allow_duplicate = $args->phone_number_allow_duplicate == 'Y' ? 'Y' : 'N'; + $args->phone_number_verify_by_sms = $args->phone_number_verify_by_sms == 'Y' ? 'Y' : 'N'; + if ($args->phone_number_hide_country === 'Y' && !$args->phone_number_default_country) + { + return new BaseObject('-1', 'msg_need_default_country'); + } // set default $all_args->is_nick_name_public = 'Y'; - $all_args->is_find_account_question_public = 'N'; // signupForm global $lang; $signupForm = array(); $items = array( - 'user_id', 'password', 'user_name', 'nick_name', 'email_address', 'homepage', 'blog', 'birthday', 'signature', + 'user_id', 'password', 'user_name', 'nick_name', 'email_address', 'phone_number', 'homepage', 'blog', 'birthday', 'signature', 'profile_image', 'profile_image_max_width', 'profile_image_max_height', 'profile_image_max_filesize', 'image_name', 'image_name_max_width', 'image_name_max_height', 'image_name_max_filesize', 'image_mark', 'image_mark_max_width', 'image_mark_max_height', 'image_mark_max_filesize', @@ -348,21 +514,39 @@ class memberAdminController extends member foreach($list_order as $key) { $signupItem = new stdClass(); - $signupItem->isIdentifier = ($key == $all_args->identifier); + $signupItem->isIdentifier = ($key == $config->identifier || in_array($key, $config->identifiers ?: [])); $signupItem->isDefaultForm = in_array($key, $items); $signupItem->name = $key; - $signupItem->title = (!in_array($key, $items)) ? $key : $lang->{$key}; + if (isset($all_args->{$key . '_title_edit'}) && $all_args->{$key . '_title_edit'} && $all_args->{$key . '_title_edit'} !== $lang->{$key}) + { + $signupItem->isCustomTitle = true; + $signupItem->title = $all_args->{$key . '_title_edit'}; + if (!preg_match('/^\\$user_lang->[a-z0-9_]+$/i', $signupItem->title)) + { + $signupItem->title = escape($signupItem->title); + } + } + else + { + $signupItem->isCustomTitle = false; + $signupItem->title = (!in_array($key, $items)) ? $key : $lang->{$key}; + } $signupItem->mustRequired = in_array($key, $mustRequireds); $signupItem->imageType = (strpos($key, 'image') !== false); - $signupItem->required = ($all_args->{$key} == 'required') || $signupItem->mustRequired || $signupItem->isIdentifier; + $signupItem->required = ($all_args->{$key} == 'required') || $signupItem->mustRequired; $signupItem->isUse = in_array($key, $usable_list) || $signupItem->required; $signupItem->isPublic = ($all_args->{'is_'.$key.'_public'} == 'Y' && $signupItem->isUse) ? 'Y' : 'N'; + if(in_array($key, ['signature', 'profile_image', 'image_name', 'image_mark'])) + { + $args->$key = $signupItem->isPublic; + } if($signupItem->imageType) { $signupItem->max_width = $all_args->{$key.'_max_width'}; $signupItem->max_height = $all_args->{$key.'_max_height'}; $signupItem->max_filesize = $all_args->{$key.'_max_filesize'}; + $signupItem->force_ratio = $all_args->{$key.'_force_ratio'} === 'N' ? 'N' : 'Y'; } // set extends form @@ -388,15 +572,10 @@ class memberAdminController extends member unset($extendItem); } - $signupForm[] = $signupItem; + $signupForm[$key] = $signupItem; } - $args->signupForm = $signupForm; - - // create Ruleset - $this->_createSignupRuleset($signupForm); - $this->_createLoginRuleset($args->identifier); - - $output = $oModuleController->updateModuleConfig('member', $args); + $args->signupForm = array_values($signupForm); + $oModuleController->updateModuleConfig('member', $args); // default setting end $this->setMessage('success_updated'); @@ -407,17 +586,55 @@ class memberAdminController extends member public function procMemberAdminInsertLoginConfig() { + $oMemberModel = getModel('member'); + $config = $oMemberModel->getMemberConfig(); $oModuleController = getController('module'); $args = Context::gets( + 'identifiers', 'change_password_date', 'enable_login_fail_report', 'max_error_count', 'max_error_count_time', + 'login_invalidate_other_sessions', 'after_login_url', 'after_logout_url' ); + if(!count($args->identifiers)) + { + return new BaseObject(-1, 'msg_need_identifier'); + } + $enabled_list = array(); + foreach($config->signupForm as $signupItem) + { + if($signupItem->isUse) + { + $enabled_list[] = $signupItem->name; + } + if(in_array($signupItem->name, $args->identifiers)) + { + $signupItem->isIdentifier = true; + } + else + { + $signupItem->isIdentifier = false; + } + } + if(!count(array_intersect($args->identifiers, $enabled_list))) + { + return new BaseObject(-1, 'msg_need_enabled_identifier'); + } + if (in_array('email_address', $args->identifiers) && $config->enable_confirm === 'Y') + { + $args->identifier = 'email_address'; + } + else + { + $args->identifier = array_first($args->identifiers) === 'email_address' ? 'email_address' : 'user_id'; + } + $args->signupForm = $config->signupForm; + if(!$args->change_password_date) { $args->change_password_date = 0; @@ -469,7 +686,33 @@ class memberAdminController extends member $args->mskin = 'default'; } + // Update member module config $output = $oModuleController->updateModuleConfig('member', $args); + if (!$output->toBool()) + { + return $output; + } + + // Sync member mid info with module config + $config = MemberModel::getMemberConfig(); + if ($config->mid) + { + $module_info = ModuleModel::getModuleInfoByMid($config->mid); + if ($module_info->module === 'member') + { + $module_info->layout_srl = $args->layout_srl ?? -1; + $module_info->mlayout_srl = $args->mlayout_srl ?? -1; + $module_info->skin = $args->skin; + $module_info->mskin = $args->mskin; + $module_info->is_skin_fix = str_starts_with($module_info->skin, '/') ? 'N' : 'Y'; + $module_info->is_mskin_fix = str_starts_with($module_info->mskin, '/') ? 'N' : 'Y'; + $output = ModuleController::getInstance()->updateModule($module_info); + if (!$output->toBool()) + { + return $output; + } + } + } // default setting end $this->setMessage('success_updated'); @@ -478,15 +721,23 @@ class memberAdminController extends member $this->setRedirectUrl($returnUrl); } - function createSignupForm($identifier) + public static function createSignupForm($config) { - global $lang; - $oMemberModel = getModel('member'); + // Maintain backward compatibility with inconsistent use of the first parameter of this method. + if (is_object($config)) + { + $identifier = $config->identifier ?? 'user_id'; + } + else + { + $identifier = strval($config) ?: 'user_id'; + $config = new \stdClass; + } // Get join form list which is additionally set - $extendItems = $oMemberModel->getJoinFormList(); + $extendItems = MemberModel::getJoinFormList(); - $items = array('user_id', 'password', 'user_name', 'nick_name', 'email_address', 'homepage', 'blog', 'birthday', 'signature', 'profile_image', 'image_name', 'image_mark'); + $items = array('user_id', 'email_address', 'phone_number', 'password', 'user_name', 'nick_name', 'homepage', 'blog', 'birthday', 'signature', 'profile_image', 'image_name', 'image_mark'); $mustRequireds = array('email_address', 'nick_name', 'password'); $orgRequireds = array('email_address', 'password', 'user_id', 'nick_name', 'user_name'); $orgUse = array('email_address', 'password', 'user_id', 'nick_name', 'user_name', 'homepage', 'blog', 'birthday'); @@ -494,7 +745,6 @@ class memberAdminController extends member foreach($items as $key) { - unset($signupItem); $signupItem = new stdClass; $signupItem->isDefaultForm = true; $signupItem->name = $key; @@ -504,7 +754,7 @@ class memberAdminController extends member $signupItem->required = in_array($key, $orgRequireds); $signupItem->isUse = ($config->{$key} == 'Y') || in_array($key, $orgUse); $signupItem->isPublic = ($signupItem->isUse) ? 'Y' : 'N'; - if(in_array($key, array('find_account_question', 'password', 'email_address'))) + if(in_array($key, array('find_account_question', 'password', 'email_address', 'phone_number'))) { $signupItem->isPublic = 'N'; } @@ -550,69 +800,9 @@ class memberAdminController extends member * @param object $signupForm (user define signup form) * @return void */ - function _createSignupRuleset($signupForm){ - $xml_file = './files/ruleset/insertMember.xml'; - $buff = '' . PHP_EOL. - '' . PHP_EOL. - '' . PHP_EOL. - '' . PHP_EOL. - '' . PHP_EOL . '%s' . PHP_EOL . '' . PHP_EOL. - ''; + function _createSignupRuleset($signupForm) + { - $fields = array(); - - foreach($signupForm as $formInfo) - { - if($formInfo->required || $formInfo->mustRequired) - { - if($formInfo->type == 'tel' || $formInfo->type == 'kr_zip') - { - $fields[] = sprintf('', $formInfo->name); - } - else if($formInfo->name == 'password') - { - $fields[] = ''; - $fields[] = ''; - } - else if($formInfo->name == 'find_account_question') - { - $fields[] = ''; - $fields[] = ''; - } - else if($formInfo->name == 'email_address') - { - $fields[] = sprintf('', $formInfo->name); - } - else if($formInfo->name == 'user_id') - { - $fields[] = sprintf('', $formInfo->name); - } - else if($formInfo->name == 'nick_name') - { - $fields[] = sprintf('', $formInfo->name); - } - else if(strpos($formInfo->name, 'image') !== false) - { - $fields[] = sprintf('', $formInfo->name, $formInfo->name); - } - else if($formInfo->name == 'signature') - { - $fields[] = ''; - } - else - { - $fields[] = sprintf('', $formInfo->name); - } - } - } - - $xml_buff = sprintf($buff, implode(PHP_EOL, $fields)); - FileHandler::writeFile($xml_file, $xml_buff); - unset($xml_buff); - - $validator = new Validator($xml_file); - $validator->setCacheDir('files/cache'); - $validator->getJsPath(); } /** @@ -622,25 +812,7 @@ class memberAdminController extends member */ function _createLoginRuleset($identifier) { - $xml_file = './files/ruleset/login.xml'; - $buff = ''. - ''. - ''. - ''. - '%s'. - ''; - $fields = array(); - $trans = array('email_address'=>'email', 'user_id'=> ''); - $fields[] = sprintf('', $trans[$identifier]); - $fields[] = ''; - - $xml_buff = sprintf($buff, implode('', $fields)); - Filehandler::writeFile($xml_file, $xml_buff); - - $validator = new Validator($xml_file); - $validator->setCacheDir('files/cache'); - $validator->getJsPath(); } /** @@ -650,7 +822,7 @@ class memberAdminController extends member */ function _createFindAccountByQuestion($identifier) { - + } /** @@ -663,7 +835,7 @@ class memberAdminController extends member $output = $this->insertGroup($args); if(!$output->toBool()) return $output; - $this->add('group_srl',''); + $this->add('group_srl', $output->get('group_srl')); $this->add('page',Context::get('page')); $this->setMessage('success_registed'); @@ -677,10 +849,7 @@ class memberAdminController extends member */ function procMemberAdminUpdateGroup() { - $group_srl = Context::get('group_srl'); - $args = Context::gets('group_srl','title','description','is_default','image_mark'); - $args->site_srl = 0; $output = $this->updateGroup($args); if(!$output->toBool()) return $output; @@ -723,21 +892,27 @@ class memberAdminController extends member $args->column_type = Context::get('column_type'); $args->column_name = strtolower(Context::get('column_id')); $args->column_title = Context::get('column_title'); - $args->default_value = explode("\n", str_replace("\r", '', Context::get('default_value'))); - $args->required = Context::get('required'); - $args->is_active = (isset($args->required)); - if(!in_array(strtoupper($args->required), array('Y','N')))$args->required = 'N'; - $args->description = Context::get('description') ? Context::get('description') : ''; - // Default values - if(in_array($args->column_type, array('checkbox','select','radio')) && count($args->default_value)) + $args->default_value = trim(utf8_clean(Context::get('default_value'))); + $args->options = trim(utf8_clean(Context::get('options'))); + if ($args->options !== '') { - $args->default_value = serialize($args->default_value); + $args->options = array_map('trim', explode("\n", $args->options)); + $args->options = json_encode($args->options, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES); } else { - $args->default_value = ''; + $args->options = null; } + $args->required = Context::get('required'); + if (!in_array(strtoupper($args->required), array('Y','N'))) + { + $args->required = 'N'; + } + + $args->is_active = (isset($args->required)); + $args->description = Context::get('description') ? Context::get('description') : ''; + // Check ID duplicated if (Context::isReservedWord($args->column_name)) { @@ -754,7 +929,7 @@ class memberAdminController extends member } } // Fix if member_join_form_srl exists. Add if not exists. - $isInsert; + $isInsert = false; if(!$args->member_join_form_srl) { $isInsert = true; @@ -781,7 +956,6 @@ class memberAdminController extends member $oMemberModel = getModel('member'); $config = $oMemberModel->getMemberConfig(); - unset($config->agreement); if($isInsert) { @@ -817,7 +991,6 @@ class memberAdminController extends member $oMemberModel = getModel('member'); $config = $oMemberModel->getMemberConfig(); - unset($config->agreement); foreach($config->signupForm as $key=>$val) { @@ -870,10 +1043,10 @@ class memberAdminController extends member function procMemberAdminSelectedMemberManage() { $var = Context::getRequestVars(); - $groups = $var->groups; - $members = $var->member_srls; + $groups = $var->groups ?? []; + $members = $var->member_srls ?? []; - $oDB = &DB::getInstance(); + $oDB = DB::getInstance(); $oDB->begin(); $oMemberController = getController('member'); @@ -887,7 +1060,6 @@ class memberAdminController extends member { if(count($groups) > 0) { - $args->site_srl = 0; // One of its members to delete all the group $output = executeQuery('member.deleteMemberGroupMember', $args); if(!$output->toBool()) @@ -909,6 +1081,7 @@ class memberAdminController extends member if($var->denied) { $args->denied = $var->denied; + $args->status = $var->denied === 'Y' ? 'DENIED' : 'APPROVED'; $output = executeQuery('member.updateMemberDeniedInfo', $args); if(!$output->toBool()) { @@ -918,7 +1091,7 @@ class memberAdminController extends member } $this->setMessage('success_updated'); break; - } + } case 'delete': { $oMemberController->memberInfo = null; @@ -931,7 +1104,7 @@ class memberAdminController extends member $this->setMessage('success_deleted'); } } - $oMemberController->_clearMemberCache($args->member_srl); + MemberController::clearMemberCache($args->member_srl); } $message = $var->message; @@ -946,10 +1119,12 @@ class memberAdminController extends member foreach($members as $member_srl) { - $oCommunicationController->sendMessage($sender_member_srl, $member_srl, $title, $message, false); + $oCommunicationController->sendMessage($sender_member_srl, $member_srl, $title, $message, true, null, false); } } + $oDB->commit(); + $returnUrl = Context::get('success_return_url') ? Context::get('success_return_url') : getNotEncodedUrl('', 'module', 'admin', 'act', 'dispMemberAdminList'); $this->setRedirectUrl($returnUrl); } @@ -992,8 +1167,9 @@ class memberAdminController extends member if(!is_array($group_srl)) $group_srls = explode('|@|', $group_srl); else $group_srls = $group_srl; - $oDB = &DB::getInstance(); + $oDB = DB::getInstance(); $oDB->begin(); + // Delete a group of selected members $args = new stdClass; $args->member_srl = $member_srl; @@ -1202,7 +1378,7 @@ class memberAdminController extends member $args->target_group_srl = $target_group_srl; $output = executeQuery('member.changeGroup', $args); - $this->_deleteMemberGroupCache($site_srl); + $this->_deleteMemberGroupCache(); return $output; } @@ -1214,8 +1390,6 @@ class memberAdminController extends member */ function insertGroup($args) { - if(!$args->site_srl) $args->site_srl = 0; - // Call trigger (before) $trigger_output = ModuleHandler::triggerCall('member.insertGroup', 'before', $args); if(!$trigger_output->toBool()) @@ -1223,30 +1397,37 @@ class memberAdminController extends member return $trigger_output; } + $oDB = DB::getInstance(); + $oDB->begin(); + // Check the value of is_default. - if($args->is_default != 'Y') - { - $args->is_default = 'N'; - } - else + $args->is_default = $args->is_default ?? 'N'; + if($args->is_default === 'Y') { $output = executeQuery('member.updateGroupDefaultClear', $args); if(!$output->toBool()) return $output; } + $args->group_srl = !empty($args->group_srl) ? $args->group_srl : getNextSequence(); + $args->list_order = $args->list_order ?? $args->group_srl; + $args->title = escape($args->title, false, true); + $args->description = escape($args->description); - if(!isset($args->list_order) || $args->list_order=='') + $output = executeQuery('member.insertGroup', $args); + if ($output->toBool()) { - $args->list_order = $args->group_srl; + $oDB->commit(); + } + else + { + $oDB->rollback(); } - if(!$args->group_srl) $args->group_srl = getNextSequence(); - $args->list_order = $args->group_srl; - $output = executeQuery('member.insertGroup', $args); - $this->_deleteMemberGroupCache($args->site_srl); + $this->_deleteMemberGroupCache(); // Call trigger (after) ModuleHandler::triggerCall('member.insertGroup', 'after', $args); - + + $output->add('group_srl', $args->group_srl); return $output; } @@ -1257,9 +1438,11 @@ class memberAdminController extends member */ function updateGroup($args) { - if(!$args->site_srl) $args->site_srl = 0; - if(!$args->group_srl) throw new Rhymix\Framework\Exceptions\TargetNotFound; - + if(!$args->group_srl) + { + throw new Rhymix\Framework\Exceptions\TargetNotFound; + } + // Call trigger (before) $trigger_output = ModuleHandler::triggerCall('member.updateGroup', 'before', $args); if(!$trigger_output->toBool()) @@ -1267,33 +1450,43 @@ class memberAdminController extends member return $trigger_output; } + $oDB = DB::getInstance(); + $oDB->begin(); + // Check the value of is_default. - if($args->is_default!='Y') - { - $args->is_default = 'N'; - } - else + $args->is_default = $args->is_default ?? 'N'; + if($args->is_default === 'Y') { $output = executeQuery('member.updateGroupDefaultClear', $args); if(!$output->toBool()) return $output; } + $args->title = isset($args->title) ? escape($args->title, false, true) : null; + $args->description = isset($args->description) ? escape($args->description) : null; $output = executeQuery('member.updateGroup', $args); - $this->_deleteMemberGroupCache($args->site_srl); - + if ($output->toBool()) + { + $oDB->commit(); + } + else + { + $oDB->rollback(); + } + + $this->_deleteMemberGroupCache(); + // Call trigger (after) ModuleHandler::triggerCall('member.updateGroup', 'after', $args); - + return $output; } /** * Delete a Group * @param int $group_srl - * @param int $site_srl * @return Object */ - function deleteGroup($group_srl, $site_srl = 0) + function deleteGroup($group_srl) { // Create a member model object $oMemberModel = getModel('member'); @@ -1304,7 +1497,7 @@ class memberAdminController extends member if(!$group_info) throw new Rhymix\Framework\Exceptions\TargetNotFound; if($group_info->is_default == 'Y') throw new Rhymix\Framework\Exception('msg_not_delete_default'); - + // Call trigger (before) $trigger_output = ModuleHandler::triggerCall('member.deleteGroup', 'before', $group_info); if(!$trigger_output->toBool()) @@ -1313,8 +1506,7 @@ class memberAdminController extends member } // Get groups where is_default == 'Y' - $columnList = array('site_srl', 'group_srl'); - $default_group = $oMemberModel->getDefaultGroup($site_srl, $columnList); + $default_group = $oMemberModel->getDefaultGroup(); $default_group_srl = $default_group->group_srl; // Change to default_group_srl @@ -1323,7 +1515,7 @@ class memberAdminController extends member $args = new stdClass; $args->group_srl = $group_srl; $output = executeQuery('member.deleteGroup', $args); - $this->_deleteMemberGroupCache($site_srl); + $this->_deleteMemberGroupCache(); if (!$output->toBool()) { return $output; @@ -1349,7 +1541,6 @@ class memberAdminController extends member // group image mark option $config = $oMemberModel->getMemberConfig(); $config->group_image_mark = $vars->group_image_mark; - unset($config->agreement); $output = $oModuleController->updateModuleConfig('member', $config); $defaultGroup = $oMemberModel->getDefaultGroup(0); @@ -1409,7 +1600,7 @@ class memberAdminController extends member executeQuery('member.updateMemberGroupListOrder', $args); } - $this->_deleteMemberGroupCache($vars->site_srl); + $this->_deleteMemberGroupCache(); $this->setRedirectUrl(getNotEncodedUrl('', 'module', 'admin', 'act', 'dispMemberAdminGroupList')); } @@ -1418,7 +1609,7 @@ class memberAdminController extends member * Delete cached group data * @return void */ - function _deleteMemberGroupCache($site_srl = 0) + function _deleteMemberGroupCache() { //remove from cache Rhymix\Framework\Cache::clearGroup('member'); diff --git a/modules/member/member.admin.model.php b/modules/member/member.admin.model.php index 670ac23f0..18f434a03 100644 --- a/modules/member/member.admin.model.php +++ b/modules/member/member.admin.model.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * admin model class of member module */ -class memberAdminModel extends member +class MemberAdminModel extends Member { /** * info of member @@ -35,28 +35,35 @@ class memberAdminModel extends member /** * Get a member list - * + * * @return object|array (object : when member count is 1, array : when member count is more than 1) */ function getMemberList() { // Search option $args = new stdClass(); - $args->is_admin = Context::get('is_admin')=='Y'?'Y':''; - $args->is_denied = Context::get('is_denied')=='Y'?'Y':''; + $args->is_admin = Context::get('is_admin') === 'Y' ? 'Y' : null; + $args->status = Context::get('is_denied') === 'Y' ? 'DENIED' : null; $args->selected_group_srl = Context::get('selected_group_srl'); $filter = Context::get('filter_type'); switch($filter) { - case 'super_admin' : $args->is_admin = 'Y';break; - case 'site_admin' : $args->member_srls = $this->getSiteAdminMemberSrls();break; - case 'enable' : $args->is_denied = 'N';break; - case 'disable' : $args->is_denied = 'Y';break; + case 'admin': + case 'super_admin': + $args->is_admin = 'Y'; break; + case 'approved': + case 'enable': + $args->status = 'APPROVED'; break; + case 'denied': + case 'disable': + $args->status = 'DENIED'; break; + case 'unauthed': + $args->status = 'UNAUTHED'; break; } - $search_target = trim(Context::get('search_target')); - $search_keyword = trim(Context::get('search_keyword')); + $search_target = trim(Context::get('search_target') ?? ''); + $search_keyword = trim(Context::get('search_keyword') ?? ''); if($search_target && $search_keyword) { @@ -79,6 +86,10 @@ class memberAdminModel extends member if($search_keyword) $search_keyword = str_replace(' ','%',$search_keyword); $args->s_email_address = $search_keyword; break; + case 'phone_number' : + if($search_keyword) $search_keyword = preg_replace('/[^0-9]/', '', $search_keyword); + $args->s_phone_number = $search_keyword; + break; case 'regdate' : $args->s_regdate = preg_replace("/[^0-9]/","",$search_keyword); break; @@ -88,6 +99,9 @@ class memberAdminModel extends member case 'regdate_less' : $args->s_regdate_less = substr(preg_replace("/[^0-9]/","",$search_keyword) . '00000000000000',0,14); break; + case 'ipaddress' : + $args->s_ipaddress = preg_replace('/[^0-9a-z.:]/', '', $search_keyword) . '%'; + break; case 'last_login' : $args->s_last_login = preg_replace("/[^0-9]/","",$search_keyword); //$args->s_last_login = $search_keyword; @@ -98,6 +112,9 @@ class memberAdminModel extends member case 'last_login_less' : $args->s_last_login_less = substr(preg_replace("/[^0-9]/","",$search_keyword) . '00000000000000',0,14); break; + case 'last_login_ipaddress' : + $args->s_last_login_ipaddress = preg_replace('/[^0-9a-z.:]/', '', $search_keyword) . '%'; + break; case 'birthday' : $args->s_birthday = preg_replace("/[^0-9]/","",$search_keyword); break; @@ -110,7 +127,7 @@ class memberAdminModel extends member // Change the query id if selected_group_srl exists (for table join) $sort_order = Context::get('sort_order'); $sort_index = Context::get('sort_index'); - if(!$sort_index) + if(!$sort_index || !in_array($sort_index, ['user_id', 'email_address', 'phone_number', 'user_name', 'nick_name', 'regdate', 'last_login'])) { $sort_index = "list_order"; } @@ -133,7 +150,7 @@ class memberAdminModel extends member else { $query_id = 'member.getMemberList'; - $args->sort_index = $sort_index; + $args->sort_index = $sort_index; } $args->sort_order = $sort_order; @@ -149,16 +166,15 @@ class memberAdminModel extends member /** * Get a memebr list for each site - * + * * @param int $site_srl * @param int $page * * @return array */ - function getSiteMemberList($site_srl, $page = 1) + function getSiteMemberList($site_srl = 0, $page = 1) { $args = new stdClass(); - $args->site_srl = $site_srl; $args->page = $page; $args->list_count = 40; $args->page_count = 10; @@ -169,8 +185,8 @@ class memberAdminModel extends member /** * Get member_srls lists about site admins - * - * @return array + * + * @return array */ function getSiteAdminMemberSrls() { @@ -188,8 +204,8 @@ class memberAdminModel extends member /** * Return colorset list of a skin in the member module - * - * @return void + * + * @return void */ function getMemberAdminColorset() { @@ -206,7 +222,7 @@ class memberAdminModel extends member if(!$config->colorset) $config->colorset = "white"; Context::set('config', $config); - $oTemplate = &TemplateHandler::getInstance(); + $oTemplate = TemplateHandler::getInstance(); $tpl = $oTemplate->compile($this->module_path.'tpl', 'new_colorset_list'); } @@ -216,7 +232,7 @@ class memberAdminModel extends member /** * Return member count with date - * + * * @param string $date * * @return int @@ -272,18 +288,29 @@ class memberAdminModel extends member if($output->toBool() && $output->data) { $formInfo = $output->data; - $default_value = $formInfo->default_value; - if($default_value) + $default_value = ''; + $options = ''; + if (isset($formInfo->options) && $formInfo->options !== '') { - $default_value = unserialize($default_value); - Context::set('default_value', $default_value); + $default_value = $formInfo->default_value; + $options = json_decode($formInfo->options, true); + } + elseif (preg_match('/^a:\d+:\{i:/', $formInfo->default_value)) + { + $options = unserialize($formInfo->default_value); + } + else + { + $default_value = $formInfo->default_value; } Context::set('formInfo', $output->data); + Context::set('default_value', $default_value); + Context::set('options', $options); } $oMemberModel = getModel('member'); $config = $oMemberModel->getMemberConfig(); - foreach($config->signupForm as $item) + foreach($config->signupForm as $item) { $list[] = $item->name; } @@ -291,14 +318,18 @@ class memberAdminModel extends member $id_list = implode(',',$list); Context::set('id_list',$id_list); - $oTemplate = &TemplateHandler::getInstance(); + $extravar_types = lang('common.column_type_list'); + unset($extravar_types['file']); + Context::set('extravar_types', $extravar_types); + + $oTemplate = TemplateHandler::getInstance(); $tpl = $oTemplate->compile($this->module_path.'tpl', 'insert_join_form'); - $this->add('tpl', str_replace("\n"," ",$tpl)); + $this->add('tpl', $tpl); } /** - * check allowed target ip address when login for admin. + * check allowed target ip address when login for admin. * * @return boolean (true : allowed, false : refuse) */ @@ -308,12 +339,12 @@ class memberAdminModel extends member { return Rhymix\Framework\Filters\IpFilter::inRanges(RX_CLIENT_IP, $allow_list); } - + if ($deny_list = ($deny_list === null) ? config('admin.deny') : $deny_list) { return !Rhymix\Framework\Filters\IpFilter::inRanges(RX_CLIENT_IP, $deny_list); } - + return true; } } diff --git a/modules/member/member.admin.view.php b/modules/member/member.admin.view.php index 360691fa2..0b46709f2 100644 --- a/modules/member/member.admin.view.php +++ b/modules/member/member.admin.view.php @@ -1,11 +1,11 @@ - */ /** * @class memberAdminView * @author NAVER (developers@xpressengine.com) * member module's admin view class */ -class memberAdminView extends member +class MemberAdminView extends Member { /** * Group list @@ -58,6 +58,13 @@ class memberAdminView extends member // retrieve group list $this->group_list = $oMemberModel->getGroups(); + if ($this->act !== 'dispMemberAdminGroupList') + { + foreach ($this->group_list as $group) + { + $group->title = Context::replaceUserLang($group->title, true); + } + } Context::set('group_list', $this->group_list); $security = new Security(); @@ -77,9 +84,9 @@ class memberAdminView extends member $oMemberModel = getModel('member'); $output = $oMemberAdminModel->getMemberList(); - $filter = Context::get('filter_type'); + $filter_type = Context::get('filter_type'); global $lang; - switch($filter) + switch($filter_type) { case 'super_admin' : Context::set('filter_type_title', $lang->cmd_show_super_admin_member);break; case 'site_admin' : Context::set('filter_type_title', $lang->cmd_show_site_admin_member);break; @@ -95,8 +102,14 @@ class memberAdminView extends member } } $config = $this->memberConfig; - $memberIdentifiers = array('user_id'=>'user_id', 'user_name'=>'user_name', 'nick_name'=>'nick_name'); - $usedIdentifiers = array(); + $memberIdentifiers = array( + 'user_id' => 'user_id', + 'email_address' => 'email_address', + 'phone_number' => 'phone_number', + 'user_name' => 'user_name', + 'nick_name' => 'nick_name' + ); + $usedIdentifiers = array(); if(is_array($config->signupForm)) { @@ -110,9 +123,14 @@ class memberAdminView extends member } } } + Context::set('total_count', $output->total_count); Context::set('total_page', $output->total_page); Context::set('page', $output->page); + Context::set('filter_type', $filter_type); + Context::set('selected_group_srl', Context::get('selected_group_srl')); + Context::set('sort_index', Context::get('sort_index')); + Context::set('member_config', $oMemberModel->getMemberConfig()); Context::set('member_list', $output->data); Context::set('usedIdentifiers', $usedIdentifiers); Context::set('page_navigation', $output->page_navigation); @@ -132,8 +150,25 @@ class memberAdminView extends member */ public function dispMemberAdminConfig() { - Context::set('password_hashing_algos', Rhymix\Framework\Password::getSupportedAlgorithms()); - + // Get supported password algorithms. + $oDB = DB::getInstance(); + $column_info = $oDB->getColumnInfo('member', 'password'); + $password_maxlength = intval($column_info->size); + $password_algos = Rhymix\Framework\Password::getSupportedAlgorithms(); + if ($password_maxlength < 128 && isset($password_algos['argon2id'])) + { + $password_algos['argon2id'] = false; + } + if ($password_maxlength < 128 && isset($password_algos['sha512'])) + { + $password_algos['sha512'] = false; + } + if ($password_maxlength < 64 && isset($password_algos['sha256'])) + { + $password_algos['sha256'] = false; + } + Context::set('password_hashing_algos', $password_algos); + $this->setTemplateFile('default_config'); } @@ -161,26 +196,6 @@ class memberAdminView extends member { $config = $this->memberConfig; - if($config->redirect_url) - { - if(!$config->redirect_mid) - { - $mid = str_ireplace(Context::getDefaultUrl(), '', $config->redirect_url); - } - else - { - $mid = $config->redirect_mid; - } - - $siteModuleInfo = Context::get('site_module_info'); - - $oModuleModel = getModel('module'); - $moduleInfo = $oModuleModel->getModuleInfoByMid($mid, (int)$siteModuleInfo->site_srl); - - $config->redirect_url = $moduleInfo->module_srl; - Context::set('config', $config); - } - $oMemberModel = getModel('member'); // retrieve skins of editor $oEditorModel = getModel('editor'); @@ -198,7 +213,8 @@ class memberAdminView extends member $option->height = 300; $option->editor_toolbar_hide = 'Y'; Context::set('editor', $oEditorModel->getEditor(0, $option)); - + + $userIdInfo = null; $signupForm = $config->signupForm; foreach($signupForm as $val) { @@ -210,7 +226,7 @@ class memberAdminView extends member } $oSecurity = new Security(); - if($userIdInfo->isUse) + if($userIdInfo && $userIdInfo->isUse) { // get denied ID list Context::set('useUserID', 1); @@ -229,6 +245,14 @@ class memberAdminView extends member Context::set('managedEmailHost', $managedEmailHost); $oSecurity->encodeHTML('managedEmailHost..email_host'); + // Get country calling code list + $country_list = Rhymix\Framework\i18n::listCountries(Context::get('lang_type') === 'ko' ? Rhymix\Framework\i18n::SORT_NAME_KOREAN : Rhymix\Framework\i18n::SORT_NAME_ENGLISH); + Context::set('country_list', $country_list); + if(!$config->phone_number_default_country && Context::get('lang_type') === 'ko') + { + $config->phone_number_default_country = 'KOR'; + } + $this->setTemplateFile('signup_config'); } @@ -348,7 +372,13 @@ class memberAdminView extends member Context::set('member_config', $member_config); $extendForm = $oMemberModel->getCombineJoinForm($this->memberInfo); Context::set('extend_form_list', $extendForm); - $memberInfo = get_object_vars(Context::get('member_info')); + + $memberInfo = Context::get('member_info'); + if(!is_object($memberInfo) || !$memberInfo->member_srl) + { + throw new Rhymix\Framework\Exceptions\TargetNotFound(); + } + $memberInfo = get_object_vars($memberInfo); if (!is_array($memberInfo['group_list'])) $memberInfo['group_list'] = array(); Context::set('memberInfo', $memberInfo); @@ -375,7 +405,7 @@ class memberAdminView extends member { $oMemberModel = getModel('member'); $member_config = $this->memberConfig; - + if($member_info = Context::get('member_info')) { $member_info->signature = $oMemberModel->getSignature($this->memberInfo->member_srl); @@ -384,12 +414,12 @@ class memberAdminView extends member { $member_info = new stdClass; } - + Context::set('member_info', $member_info); - + $formTags = $this->_getMemberInputTag($member_info, true); Context::set('formTags', $formTags); - + // Editor of the module set for signing by calling getEditor foreach($formTags as $formTag) { @@ -410,17 +440,37 @@ class memberAdminView extends member $option->editor_toolbar_hide = 'Y'; $option->editor_skin = $member_config->signature_editor_skin; $option->sel_editor_colorset = $member_config->sel_editor_colorset; - + if (!$option->allow_html) + { + $option->editor_skin = 'textarea'; + } + if ($option->allow_fileupload) + { + $option->module_srl = MemberView::getInstance()->getMemberModuleSrl(); + $option->upload_target_type = 'sig'; + } + if ($member_config->member_max_filesize) + { + $option->allowed_filesize = $member_config->member_max_filesize * 1024; + } + Context::set('editor', getModel('editor')->getEditor($member_info->member_srl, $option)); } } - - $identifierForm = new stdClass; - $identifierForm->title = lang($member_config->identifier); - $identifierForm->name = $member_config->identifier; - $identifierForm->value = $member_info->{$member_config->identifier}; - Context::set('identifierForm', $identifierForm); - + + if ($member_info->limit_date < date('Ymd')) + { + $member_info->limit_date = ''; + } + + if (Context::get('member_srl')) + { + Context::setBrowserTitle(lang('member.msg_update_member')); + } + else + { + Context::setBrowserTitle(lang('member.msg_new_member')); + } $this->setTemplateFile('insert_member'); } @@ -434,43 +484,49 @@ class memberAdminView extends member */ function _getMemberInputTag($memberInfo = null, $isAdmin = false) { - $logged_info = Context::get('logged_info'); - $oMemberModel = getModel('member'); - $extend_form_list = $oMemberModel->getCombineJoinForm($memberInfo); + $extend_form_list = MemberModel::getCombineJoinForm($memberInfo); $security = new Security($extend_form_list); - $security->encodeHTML('..column_title', '..description', '..default_value.'); - + $security->encodeHTML('..column_title', '..description', '..default_value', '..options.'); + if ($memberInfo) { $memberInfo = get_object_vars($memberInfo); + $isSignup = false; } else { $memberInfo = array(); + $isSignup = true; } $member_config = $this->memberConfig; if(!$this->memberConfig) { - $member_config = $this->memberConfig = $oMemberModel->getMemberConfig(); + $member_config = $this->memberConfig = MemberModel::getMemberConfig(); } - + $identifiers = $member_config->identifiers ?? [$member_config->identifier]; + $identifiers = array_intersect($identifiers, ['user_id', 'email_address']); + global $lang; $formTags = array(); - - foreach($member_config->signupForm as $no=>$formInfo) + + foreach($member_config->signupForm as $formInfo) { - if(!$formInfo->isUse || $formInfo->name == $member_config->identifier || $formInfo->name == 'password') + if(!$formInfo->isUse || ($formInfo->name == 'password' && !$isAdmin)) { continue; } - + if((in_array($formInfo->name, $identifiers) && $formInfo->name === array_first($identifiers)) && !$isAdmin) + { + continue; + } + $formTag = new stdClass(); $inputTag = ''; - $formTag->title = ($formInfo->isDefaultForm) ? $lang->{$formInfo->name} : $formInfo->title; + $formTag->title = $formInfo->title; if($isAdmin) { - if($formInfo->mustRequired) $formTag->title = '* '.$formTag->title; + if($formInfo->mustRequired || $formInfo->required) $formTag->title = '* '.$formTag->title; } else { @@ -478,6 +534,7 @@ class memberAdminView extends member } $formTag->name = $formInfo->name; + // Default input fields if($formInfo->isDefaultForm) { if($formInfo->imageType) @@ -499,7 +556,7 @@ class memberAdminView extends member $functionName = 'doDeleteImageMark'; } - if($target->src) + if(!empty($target->src)) { $inputTag = sprintf('%s ', $formInfo->name, @@ -514,9 +571,19 @@ class memberAdminView extends member { $inputTag = sprintf('', $formInfo->name); } - $inputTag .= sprintf('

                %s: %dpx, %s: %dpx

                ', + + $max_filesize = min(FileHandler::returnBytes(ini_get('upload_max_filesize')), FileHandler::returnBytes(ini_get('post_max_size'))); + if (isset($member_config->{$formInfo->name.'_max_filesize'})) + { + $max_filesize = min($max_filesize, $member_config->{$formInfo->name.'_max_filesize'} * 1024); + } + $inputTag .= sprintf('

                %s: %s, %s: %dpx, %s: %dpx

                ', $formInfo->name, $formInfo->name, + $max_filesize, + escape(lang('file.allowed_filesize_exceeded')), + lang('file.allowed_filesize'), + FileHandler::filesize($max_filesize), $lang->{$formInfo->name.'_max_width'}, $member_config->{$formInfo->name.'_max_width'}, $lang->{$formInfo->name.'_max_height'}, @@ -525,9 +592,13 @@ class memberAdminView extends member else if($formInfo->name == 'birthday') { $formTag->type = 'date'; - $inputTag = sprintf(' ', + $inputTag = sprintf('' . + ' ' . + '', $memberInfo['birthday'], - zdate($memberInfo['birthday'], 'Y-m-d', false), + $memberInfo['birthday'] ? sprintf('%s-%s-%s', substr($memberInfo['birthday'], 0, 4), substr($memberInfo['birthday'], 4, 2), substr($memberInfo['birthday'], 6, 2)) : '', $lang->cmd_delete); } else if($formInfo->name == 'find_account_question') @@ -536,136 +607,140 @@ class memberAdminView extends member } else if($formInfo->name == 'email_address') { + if(isset($member_config->enable_confirm) && $member_config->enable_confirm === 'Y' && !$isAdmin && !$isSignup) + { + $readonly = 'readonly="readonly" '; + } + else + { + $readonly = ''; + } $formTag->type = 'email'; - $inputTag = ''; + $inputTag = ''; } - else if($formInfo->name == 'homepage') + else if($formInfo->name == 'phone_number') + { + $formTag->type = 'phone'; + $match_country = $memberInfo['phone_country']; + if(!$match_country && $member_config->phone_number_default_country) + { + $match_country = $member_config->phone_number_default_country; + } + if($match_country && !preg_match('/^[A-Z]{3}$/', $match_country)) + { + $match_country = Rhymix\Framework\i18n::getCountryCodeByCallingCode($match_country); + } + if(!$match_country && Context::get('lang_type') === 'ko') + { + $match_country = 'KOR'; + } + if($member_config->phone_number_hide_country !== 'Y') + { + $inputTag = '' . "\n"; + } + if($memberInfo['phone_number']) + { + if($match_country === 'KOR') + { + $phone_number = Rhymix\Framework\Korea::formatPhoneNumber($memberInfo['phone_number']); + } + else + { + $phone_number = $memberInfo['phone_number']; + } + } + else + { + $phone_number = ''; + } + $inputTag .= ''; + if($member_config->phone_number_verify_by_sms === 'Y') + { + $inputTag .= "\n" . ''; + $inputTag .= "\n" . ''; + } + } + else if($formInfo->name == 'homepage' || $formInfo->name === 'blog') { $formTag->type = 'url'; - $inputTag = ''; + $input = new Rhymix\Modules\Extravar\Models\Value(0, 1, '', 'url'); + $input->parent_type = 'member'; + $input->input_name = $formInfo->name; + $input->input_id = $formInfo->name; + $input->value = $memberInfo[$formInfo->name] ?? ''; + $inputTag = $input->getFormHTML(); } - else if($formInfo->name == 'blog') + else if($formInfo->name == 'password') { - $formTag->type = 'url'; - $inputTag = ''; + $formTag->type = 'password'; + $input = new Rhymix\Modules\Extravar\Models\Value(0, 1, '', 'password'); + $input->parent_type = 'member'; + $input->input_name = $formInfo->name; + $input->input_id = $formInfo->name; + $input->value = ''; + $inputTag = $input->getFormHTML(); } else { + if($formInfo->name === 'nick_name' && ($member_config->allow_nickname_change ?? 'Y') === 'N' && !$isAdmin && !$isSignup) + { + $readonly = 'Y'; + } + else + { + $readonly = 'N'; + } $formTag->type = 'text'; - $inputTag = sprintf('', - $formInfo->name, - $formInfo->name, - $memberInfo[$formInfo->name]); + $input = new Rhymix\Modules\Extravar\Models\Value(0, 1, '', 'text'); + $input->parent_type = 'member'; + $input->input_name = $formInfo->name; + $input->input_id = $formInfo->name; + $input->value = $memberInfo[$formInfo->name] ?? ''; + $input->is_readonly = $readonly; + $inputTag = $input->getFormHTML(); } - }//end isDefaultForm + } + + // User-defined input fields else { $extendForm = $extend_form_list[$formInfo->member_join_form_srl]; - $replace = array('column_name' => $extendForm->column_name, 'value' => $extendForm->value); - $extentionReplace = array(); - $formTag->type = $extendForm->column_type; - if($extendForm->column_type == 'text') + $input = new Rhymix\Modules\Extravar\Models\Value(0, 1, '', $extendForm->column_type); + $input->parent_type = 'member'; + $input->input_name = $extendForm->column_name; + $input->input_id = $extendForm->column_name; + $input->value = $extendForm->value ?? ''; + $input->default = $extendForm->default_value ?? null; + $input->options = $extendForm->options ?? null; + if ($extendForm->column_type === 'tel' || $extendForm->column_type === 'tel_intl') { - $template = ''; - } - else if($extendForm->column_type == 'homepage') - { - $template = ''; - } - else if($extendForm->column_type == 'email_address') - { - $template = ''; - } - else if($extendForm->column_type == 'tel') - { - $extentionReplace = array('tel_0' => $extendForm->value[0], - 'tel_1' => $extendForm->value[1], - 'tel_2' => $extendForm->value[2]); - $template = ' - - '; - } - else if($extendForm->column_type == 'textarea') - { - $template = ''; - } - else if($extendForm->column_type == 'password') - { - $template = ''; - } - else if($extendForm->column_type == 'checkbox') - { - $template = ''; - if($extendForm->default_value) - { - $template = '
                %s
                '; - $__i = 0; - $optionTag = array(); - foreach($extendForm->default_value as $v) - { - $checked = ''; - if(is_array($extendForm->value) && in_array($v, $extendForm->value))$checked = 'checked="checked"'; - $optionTag[] = ''; - $__i++; - } - $template = sprintf($template, implode('', $optionTag)); - } - } - else if($extendForm->column_type == 'radio') - { - $template = ''; - if($extendForm->default_value) - { - $template = '
                %s
                '; - $optionTag = array(); - foreach($extendForm->default_value as $v) - { - if($extendForm->value == $v)$checked = 'checked="checked"'; - else $checked = ''; - $optionTag[] = ''; - } - $template = sprintf($template, implode('', $optionTag)); - } - } - else if($extendForm->column_type == 'select') - { - $template = ''; - $optionTag = array(); - $optionTag[] = sprintf('', $lang->cmd_select); - if($extendForm->default_value) - { - foreach($extendForm->default_value as $v) - { - if($v == $extendForm->value) $selected = 'selected="selected"'; - else $selected = ''; - $optionTag[] = sprintf('', $v, $selected, $v); - } - } - $template = sprintf($template, implode('', $optionTag)); - } - else if($extendForm->column_type == 'kr_zip') - { - $krzipModel = getModel('krzip'); - if($krzipModel && method_exists($krzipModel , 'getKrzipCodeSearchHtml' )) - { - $template = $krzipModel->getKrzipCodeSearchHtml($extendForm->column_name, $extendForm->value); - } - } - else if($extendForm->column_type == 'jp_zip') - { - $template = ''; - } - else if($extendForm->column_type == 'date') - { - $extentionReplace = array('date' => zdate($extendForm->value, 'Y-m-d'), 'cmd_delete' => $lang->cmd_delete); - $template = ' '; + $input->style = 'width:33.3px'; } + $inputTag = $input->getFormHTML(); - $replace = array_merge($extentionReplace, $replace); - $inputTag = preg_replace_callback('@%(\w+)%@', function($n) use($replace) { return $replace[$n[1]]; }, $template); - - if($extendForm->description) - $inputTag .= '

                '.$extendForm->description.'

                '; + if (!empty($extendForm->description)) + { + $inputTag = vsprintf('%s

                %s

                ', [ + $inputTag, + $extendForm->description, + ]); + } } + $formTag->inputTag = $inputTag; $formTags[] = $formTag; } diff --git a/modules/member/member.api.php b/modules/member/member.api.php index 8c70bb140..ba3fb0aac 100644 --- a/modules/member/member.api.php +++ b/modules/member/member.api.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * API Processing of View Action in the member module */ -class memberAPI extends member +class MemberAPI extends Member { /** * Content List diff --git a/modules/member/member.class.php b/modules/member/member.class.php index 425d09cc2..4f932bb53 100644 --- a/modules/member/member.class.php +++ b/modules/member/member.class.php @@ -5,33 +5,14 @@ * @author NAVER (developers@xpressengine.com) * high class of the member module */ -class member extends ModuleObject { +class Member extends ModuleObject +{ /** - * Use sha1 encryption - * - * @var boolean + * Constants */ - var $useSha1 = false; - - /** - * constructor - * - * @return void - */ - function __construct() - { - if(!Context::isInstalled()) return; - - $oModuleModel = getModel('module'); - $member_config = $oModuleModel->getModuleConfig('member'); - - // Set to use SSL upon actions related member join/information/password and so on. 2013.02.15 - if(!Context::isExistsSSLAction('dispMemberModifyPassword') && Context::getSslStatus() == 'optional') - { - $ssl_actions = array('dispMemberModifyPassword', 'dispMemberSignUpForm', 'dispMemberModifyInfo', 'dispMemberModifyEmailAddress', 'dispMemberResendAuthMail', 'dispMemberLoginForm', 'dispMemberFindAccount', 'dispMemberLeave', 'procMemberLogin', 'procMemberModifyPassword', 'procMemberInsert', 'procMemberModifyInfo', 'procMemberFindAccount', 'procMemberModifyEmailAddress', 'procMemberResendAuthMail', 'procMemberLeave'/*, 'getMemberMenu'*/, 'procMemberFindAccountByQuestion'); - Context::addSSLActions($ssl_actions); - } - } + public const ADMIN_EXTRA_VARS = ['refused_reason', 'limited_reason']; + public const NOUSE_EXTRA_VARS = ['error_return_url', 'success_return_url', '_rx_ajax_compat', '_rx_ajax_form', '_rx_csrf_token', 'ruleset', 'captchaType', 'use_editor', 'use_html']; + public const STATUS_LIST = ['APPROVED', 'DENIED', 'UNAUTHED', 'SUSPENDED', 'DELETED']; /** * Implement if additional tasks are necessary when installing @@ -40,69 +21,23 @@ class member extends ModuleObject { */ function moduleInstall() { - // Register action forward (to use in administrator mode) $oModuleController = getController('module'); + $config = ModuleModel::getModuleConfig('member'); - $oDB = &DB::getInstance(); - $oDB->addIndex("member_group","idx_site_title", array("site_srl","title"),true); - - $oModuleModel = getModel('module'); - $config = $oModuleModel->getModuleConfig('member'); - - if(empty($config)) + // Set default config + if(!$config) { - $isNotInstall = true; - $config = new stdClass; + $config = MemberModel::getMemberConfig(); + $config->mid = 'member'; + $config->force_mid = true; + $config->password_reset_method = 2; + $this->createMid($config->mid); + $oModuleController->insertModuleConfig('member', $config); } - // Set the basic information - $config->enable_join = 'Y'; - $config->enable_openid = 'N'; - if(!$config->enable_auth_mail) $config->enable_auth_mail = 'N'; - if(!$config->image_name) $config->image_name = 'Y'; - if(!$config->image_mark) $config->image_mark = 'Y'; - if(!$config->profile_image) $config->profile_image = 'Y'; - if(!$config->image_name_max_width) $config->image_name_max_width = '90'; - if(!$config->image_name_max_height) $config->image_name_max_height = '20'; - if(!$config->image_mark_max_width) $config->image_mark_max_width = '20'; - if(!$config->image_mark_max_height) $config->image_mark_max_height = '20'; - if(!$config->profile_image_max_width) $config->profile_image_max_width = '90'; - if(!$config->profile_image_max_height) $config->profile_image_max_height = '90'; - if($config->group_image_mark!='Y') $config->group_image_mark = 'N'; - if(!$config->password_strength) $config->password_strength = 'normal'; - - if(!$config->password_hashing_algorithm) - { - $config->password_hashing_algorithm = Rhymix\Framework\Password::getBestSupportedAlgorithm(); - } - if(!$config->password_hashing_work_factor) - { - $config->password_hashing_work_factor = 10; - } - if(!$config->password_hashing_auto_upgrade) - { - $config->password_hashing_auto_upgrade = 'Y'; - } - - global $lang; $oMemberModel = getModel('member'); - // Create a member controller object $oMemberController = getController('member'); $oMemberAdminController = getAdminController('member'); - - if(!$config->signupForm || !is_array($config->signupForm)) - { - $identifier = 'user_id'; - $config->signupForm = $oMemberAdminController->createSignupForm($identifier); - $config->identifier = $identifier; - - // Create Ruleset File - FileHandler::makeDir('./files/ruleset'); - $oMemberAdminController->_createSignupRuleset($config->signupForm); - $oMemberAdminController->_createLoginRuleset($config->identifier); - } - $oModuleController->insertModuleConfig('member',$config); - $groups = $oMemberModel->getGroups(); if(!count($groups)) { @@ -144,7 +79,7 @@ class member extends ModuleObject { } // Register denied ID(default + module name) $oModuleModel = getModel('module'); - $module_list = $oModuleModel->getModuleList(); + $module_list = ModuleModel::getModuleList(); foreach($module_list as $key => $val) { $oMemberAdminController->insertDeniedID($val->module,''); @@ -160,10 +95,6 @@ class member extends ModuleObject { FileHandler::makeDir('./files/member_extra_info/image_mark'); FileHandler::makeDir('./files/member_extra_info/profile_image'); FileHandler::makeDir('./files/member_extra_info/signature'); - - // 2013. 11. 22 add menu when popup document menu called - $oModuleController->insertTrigger('document.getDocumentMenu', 'member', 'controller', 'triggerGetDocumentMenu', 'after'); - $oModuleController->insertTrigger('comment.getCommentMenu', 'member', 'controller', 'triggerGetCommentMenu', 'after'); } /** @@ -173,46 +104,100 @@ class member extends ModuleObject { */ function checkUpdate() { - $oDB = &DB::getInstance(); - $oModuleModel = getModel('module'); + $oDB = DB::getInstance(); // check member directory (11/08/2007 added) if(!is_dir("./files/member_extra_info")) return true; // check member directory (22/10/2007 added) if(!is_dir("./files/member_extra_info/profile_image")) return true; - // Add a column(is_register) to "member_auth_mail" table (22/04/2008) - $act = $oDB->isColumnExists("member_auth_mail", "is_register"); - if(!$act) return true; - // Add a column(site_srl) to "member_group_member" table (11/15/2008) - if(!$oDB->isColumnExists("member_group_member", "site_srl")) return true; - if(!$oDB->isColumnExists("member_group", "site_srl")) return true; - if($oDB->isIndexExists("member_group","uni_member_group_title")) return true; - // Add a column for list_order (05/18/2011) - if(!$oDB->isColumnExists("member_group", "list_order")) return true; + // Check length of password column + if($oDB->getColumnInfo('member', 'password')->size < 250) + { + return true; + } + if($oDB->getColumnInfo('member_auth_mail', 'new_password')->size < 250) + { + return true; + } - // image_mark 추가 (2009. 02. 14) - if(!$oDB->isColumnExists("member_group", "image_mark")) return true; - // Add c column for password expiration date - if(!$oDB->isColumnExists("member", "change_password_date")) return true; + // Add columns for phone number + if(!$oDB->isColumnExists("member", "phone_number")) return true; + if(!$oDB->isIndexExists("member","idx_phone_number")) return true; + if(!$oDB->isColumnExists("member", "phone_country")) return true; + if(!$oDB->isIndexExists("member","idx_phone_country")) return true; + if(!$oDB->isColumnExists("member", "phone_type")) return true; + if(!$oDB->isIndexExists("member","idx_phone_type")) return true; - // Add columns of question and answer to verify a password - if(!$oDB->isColumnExists("member", "find_account_question")) return true; - if(!$oDB->isColumnExists("member", "find_account_answer")) return true; + // Add columns for IP address + if(!$oDB->isColumnExists("member", "ipaddress")) return true; + if(!$oDB->isIndexExists("member","idx_ipaddress")) return true; + if(!$oDB->isColumnExists("member", "last_login_ipaddress")) return true; + if(!$oDB->isIndexExists("member","idx_last_login_ipaddress")) return true; + // Add column for status + if(!$oDB->isColumnExists("member", "status")) return true; + if(!$oDB->isIndexExists("member", "idx_status")) return true; + + // Add column for list order if(!$oDB->isColumnExists("member", "list_order")) return true; if(!$oDB->isIndexExists("member","idx_list_order")) return true; - + // Check autologin table if(!$oDB->isColumnExists("member_autologin", "security_key")) return true; - + if(!$oDB->isColumnExists("member_autologin", "previous_key")) return true; + // Check scrap folder table if(!$oDB->isColumnExists("member_scrap", "folder_srl")) return true; - - $oModuleModel = getModel('module'); - $config = $oModuleModel->getModuleConfig('member'); - // check signup form ordering info + + if(!$oDB->isIndexExists('member_nickname_log', 'idx_before_nick_name')) return true; + if(!$oDB->isIndexExists('member_nickname_log', 'idx_after_nick_name')) return true; + if(!$oDB->isIndexExists('member_nickname_log', 'idx_user_id')) return true; + + // Check individual indexes for member_group_member table + if(!$oDB->isIndexExists('member_group_member', 'idx_member_srl')) return true; + + // Add device token type and last active date 2020.10.28 + if(!$oDB->isColumnExists('member_devices', 'device_token_type')) return true; + if(!$oDB->isColumnExists('member_devices', 'last_active_date')) return true; + + // Check member_auth_mail table + if(!$oDB->isColumnExists('member_auth_mail', 'auth_type')) return true; + if(!$oDB->isIndexExists('member_auth_mail', 'unique_auth_key')) return true; + if(!$oDB->isIndexExists('member_auth_mail', 'idx_member_srl')) return true; + if($oDB->isIndexExists('member_auth_mail', 'unique_key')) return true; + + // Check join form options column + if(!$oDB->isColumnExists('member_join_form', 'options')) return true; + + // Update status column + $output = executeQuery('member.getDeniedAndStatus'); + if ($output->data->count) + { + return true; + } + + // Check mid + $config = ModuleModel::getModuleConfig('member'); + if (empty($config->mid) || $this->checkMid($config->mid) !== 1) + { + return true; + } + + // Check members with phone country in old format + if ($config->phone_number_default_country && !preg_match('/^[A-Z]{3}$/', $config->phone_number_default_country)) + { + return true; + } + $output = executeQuery('member.getMemberCountByPhoneCountry', (object)['phone_country' => '82']); + if ($output->data->count) + { + return true; + } + + // Check signup form if(!$config->signupForm || !is_array($config->signupForm)) return true; + $phone_found = false; foreach($config->signupForm as $signupItem) { if($signupItem->name === 'find_account_question') @@ -223,31 +208,26 @@ class member extends ModuleObject { { return true; } + if($signupItem->name === 'phone_number') + { + $phone_found = true; + } } + if(!$phone_found) + { + return true; + } + + // Check agreements if(!$config->agreements) { return true; } - if($config->skin) - { - $config_parse = explode('.', $config->skin); - if(count($config_parse) > 1) - { - $template_path = sprintf('./themes/%s/modules/member/', $config_parse[0]); - if(is_dir($template_path)) return true; - } - } - // supprot multilanguage agreement. - if(is_readable('./files/member_extra_info/agreement.txt')) return true; - - if(!is_readable('./files/ruleset/insertMember.xml')) return true; - if(!is_readable('./files/ruleset/login.xml')) return true; - - // 2013. 11. 22 add menu when popup document menu called - if(!$oModuleModel->getTrigger('document.getDocumentMenu', 'member', 'controller', 'triggerGetDocumentMenu', 'after')) return true; - if(!$oModuleModel->getTrigger('comment.getCommentMenu', 'member', 'controller', 'triggerGetCommentMenu', 'after')) return true; + if(FileHandler::exists('./files/member_extra_info/agreement.txt')) return true; + if(FileHandler::exists('./files/ruleset/insertMember.xml')) return true; + if(FileHandler::exists('./files/ruleset/login.xml')) return true; // Allow duplicate nickname if($config->allow_duplicate_nickname == 'Y') @@ -257,7 +237,7 @@ class member extends ModuleObject { return true; } } - + return false; } @@ -268,63 +248,80 @@ class member extends ModuleObject { */ function moduleUpdate() { - $oDB = &DB::getInstance(); + $oDB = DB::getInstance(); $oModuleController = getController('module'); + // Check member directory FileHandler::makeDir('./files/member_extra_info/image_name'); FileHandler::makeDir('./files/member_extra_info/image_mark'); FileHandler::makeDir('./files/member_extra_info/signature'); FileHandler::makeDir('./files/member_extra_info/profile_image'); - // Add a column - if(!$oDB->isColumnExists("member_auth_mail", "is_register")) + + // Check length of password column + if($oDB->getColumnInfo('member', 'password')->size < 250) { - $oDB->addColumn("member_auth_mail", "is_register", "char", 1, "N", true); + $oDB->modifyColumn('member', 'password', 'varchar', 250, null, true); } - // Add a column(site_srl) to "member_group_member" table (11/15/2008) - if(!$oDB->isColumnExists("member_group_member", "site_srl")) + if($oDB->getColumnInfo('member_auth_mail', 'new_password')->size < 250) { - $oDB->addColumn("member_group_member", "site_srl", "number", 11, 0, true); - $oDB->addIndex("member_group_member", "idx_site_srl", "site_srl", false); - } - if(!$oDB->isColumnExists("member_group", "site_srl")) - { - $oDB->addColumn("member_group", "site_srl", "number", 11, 0, true); - $oDB->addIndex("member_group","idx_site_title", array("site_srl","title"),true); - } - if($oDB->isIndexExists("member_group","uni_member_group_title")) - { - $oDB->dropIndex("member_group","uni_member_group_title",true); + $oDB->modifyColumn('member_auth_mail', 'new_password', 'varchar', 250, null, true); } - // Add a column(list_order) to "member_group" table (05/18/2011) - if(!$oDB->isColumnExists("member_group", "list_order")) + // Add columns for phone number + if(!$oDB->isColumnExists("member", "phone_number")) { - $oDB->addColumn("member_group", "list_order", "number", 11, '', true); - $oDB->addIndex("member_group","idx_list_order", "list_order",false); - $output = executeQuery('member.updateAllMemberGroupListOrder'); + $oDB->addColumn("member", "phone_number", "varchar", 80, null, false, 'email_host'); } - // Add a column for image_mark (02/14/2009) - if(!$oDB->isColumnExists("member_group", "image_mark")) + if(!$oDB->isColumnExists("member", "phone_country")) { - $oDB->addColumn("member_group", "image_mark", "text"); + $oDB->addColumn("member", "phone_country", "varchar", 10, null, false, 'phone_number'); } - // Add a column for password expiration date - if(!$oDB->isColumnExists("member", "change_password_date")) + if(!$oDB->isColumnExists("member", "phone_type")) { - $oDB->addColumn("member", "change_password_date", "date"); - executeQuery('member.updateAllChangePasswordDate'); + $oDB->addColumn("member", "phone_type", "varchar", 10, null, false, 'phone_country'); + } + if(!$oDB->isIndexExists("member","idx_phone_number")) + { + $oDB->addIndex("member","idx_phone_number", array("phone_number")); + } + if(!$oDB->isIndexExists("member","idx_phone_country")) + { + $oDB->addIndex("member","idx_phone_country", array("phone_country")); + } + if(!$oDB->isIndexExists("member","idx_phone_type")) + { + $oDB->addIndex("member","idx_phone_type", array("phone_type")); } - // Add columns of question and answer to verify a password - if(!$oDB->isColumnExists("member", "find_account_question")) + // Add columns for IP address + if(!$oDB->isColumnExists("member", "ipaddress")) { - $oDB->addColumn("member", "find_account_question", "number", 11); + $oDB->addColumn("member", "ipaddress", "varchar", 60, null, false, 'regdate'); } - if(!$oDB->isColumnExists("member", "find_account_answer")) + if(!$oDB->isColumnExists("member", "last_login_ipaddress")) { - $oDB->addColumn("member", "find_account_answer", "varchar", 250); + $oDB->addColumn("member", "last_login_ipaddress", "varchar", 60, null, false, 'last_login'); + } + if(!$oDB->isIndexExists("member","idx_ipaddress")) + { + $oDB->addIndex("member","idx_ipaddress", array("ipaddress")); + } + if(!$oDB->isIndexExists("member","idx_last_login_ipaddress")) + { + $oDB->addIndex("member","idx_last_login_ipaddress", array("last_login_ipaddress")); } + // Add column for status + if(!$oDB->isColumnExists("member", "status")) + { + $oDB->addColumn("member", "status", "varchar", 20, 'APPROVED', true, 'denied'); + } + if(!$oDB->isIndexExists("member", "idx_status")) + { + $oDB->addIndex("member", "idx_status", array("status")); + } + + // Add column for list order if(!$oDB->isColumnExists("member", "list_order")) { $oDB->addColumn("member", "list_order", "number", 11); @@ -338,12 +335,16 @@ class member extends ModuleObject { { $oDB->addIndex("member","idx_list_order", array("list_order")); } - + // Check autologin table if(!$oDB->isColumnExists("member_autologin", "security_key")) { $oDB->dropTable('member_autologin'); - $oDB->createTableByXmlFile($this->module_path . '/schemas/member_autologin.xml'); + $oDB->createTable($this->module_path . '/schemas/member_autologin.xml'); + } + if(!$oDB->isColumnExists("member_autologin", "previous_key")) + { + $oDB->addColumn("member_autologin", "previous_key", "varchar", 80, null, false, "security_key"); } // Check scrap folder table @@ -352,75 +353,226 @@ class member extends ModuleObject { $oDB->addColumn("member_scrap", "folder_srl", "number", 11); $oDB->addIndex("member_scrap","idx_folder_srl", array("folder_srl")); } - - $oModuleModel = getModel('module'); - $config = $oModuleModel->getModuleConfig('member'); - $oModuleController = getController('module'); - $oMemberAdminController = getAdminController('member'); - // check signup form ordering info - if(!$config->signupForm || !is_array($config->signupForm)) + // Add to index in member nickname log table. 2020. 07 .20 @BJRambo + if(!$oDB->isIndexExists('member_nickname_log', 'idx_before_nick_name')) + { + $oDB->addIndex('member_nickname_log', 'idx_before_nick_name', array('before_nick_name')); + $oDB->addIndex('member_nickname_log', 'idx_after_nick_name', array('after_nick_name')); + $oDB->addIndex('member_nickname_log', 'idx_user_id', array('user_id')); + } + + // Check index for member_group_member table + if(!$oDB->isIndexExists('member_group_member', 'idx_member_srl')) + { + $oDB->addIndex('member_group_member', 'idx_member_srl', array('member_srl')); + } + + // Add device token type and last active date 2020.10.28 + if(!$oDB->isColumnExists('member_devices', 'device_token_type')) + { + $oDB->addColumn('member_devices', 'device_token_type', 'varchar', '20', '', true, 'device_token'); + $oDB->addIndex('member_devices', 'idx_device_token_type', array('device_token_type')); + $oDB->query("UPDATE member_devices SET device_token_type = 'fcm' WHERE device_type = 'android' OR LENGTH(device_token) > 64"); + $oDB->query("UPDATE member_devices SET device_token_type = 'apns' WHERE device_type = 'ios' AND LENGTH(device_token) = 64"); + } + if(!$oDB->isColumnExists('member_devices', 'last_active_date')) + { + $oDB->addColumn('member_devices', 'last_active_date', 'date', '', '', true, 'regdate'); + $oDB->addIndex('member_devices', 'idx_last_active_date', array('last_active_date')); + $oDB->query("UPDATE member_devices SET last_active_date = regdate WHERE last_active_date = ''"); + } + + // Check member_auth_mail table + if(!$oDB->isColumnExists('member_auth_mail', 'auth_type')) + { + $oDB->addColumn('member_auth_mail', 'auth_type', 'varchar', '20', 'password_v1', true, 'new_password'); + $oDB->query("UPDATE member_auth_mail SET auth_type = 'signup' WHERE is_register = 'Y'"); + } + if(!$oDB->isIndexExists('member_auth_mail', 'unique_auth_key')) + { + $output = $oDB->addIndex('member_auth_mail', 'unique_auth_key', ['auth_key'], true); + if (!$output->toBool()) + { + return $output; + } + } + if(!$oDB->isIndexExists('member_auth_mail', 'idx_member_srl')) + { + $oDB->addIndex('member_auth_mail', 'idx_member_srl', ['member_srl']); + } + if($oDB->isIndexExists('member_auth_mail', 'unique_key')) + { + $oDB->dropIndex('member_auth_mail', 'unique_key'); + } + + // Check join form options column + if(!$oDB->isColumnExists('member_join_form', 'options')) + { + $oDB->addColumn('member_join_form', 'options', 'text', null, null, null, 'default_value'); + } + + // Update status column + $output = executeQuery('member.getDeniedAndStatus'); + if ($output->data->count) + { + $oDB->begin(); + $result = $oDB->query("UPDATE `member` SET `status` = 'DENIED' WHERE `denied` = 'Y'"); + if ($result) + { + $result = $oDB->query("UPDATE `member` AS `m` " . + "JOIN `member_auth_mail` AS `a` ON `m`.`member_srl` = `a`.`member_srl` " . + "SET `m`.`status` = 'UNAUTHED' WHERE `m`.`status` = 'DENIED' " . + "AND `a`.`is_register` = 'Y'"); + if ($result) + { + $oDB->commit(); + } + else + { + $oDB->rollback(); + } + } + else + { + $oDB->rollback(); + } + } + + // Get module config + $config = ModuleModel::getModuleConfig('member') ?: new stdClass; + $changed = false; + + // Check mid + if (empty($config->mid) || $this->checkMid($config->mid) !== 1) + { + $config->mid = 'member'; + $output = $this->createMid($config->mid, $config->skin ?: 'default', $config->mskin ?: 'default'); + if (!$output->toBool()) + { + return $output; + } + $changed = true; + } + + // Check members with phone country in old format + if ($config->phone_number_default_country && !preg_match('/^[A-Z]{3}$/', $config->phone_number_default_country)) + { + $config->phone_number_default_country = Rhymix\Framework\i18n::getCountryCodeByCallingCode($config->phone_number_default_country); + $changed = true; + } + $output = executeQuery('member.getMemberCountByPhoneCountry', (object)['phone_country' => '82']); + if ($output->data->count) + { + executeQuery('member.updateMemberPhoneCountry', (object)array( + 'old_phone_country' => '82', + 'new_phone_country' => 'KOR', + )); + } + + // Check signup form + $oModuleController = getController('module'); + if(empty($config->identifier)) { $config->identifier = 'user_id'; - $config->signupForm = $oMemberAdminController->createSignupForm($config->identifier); - $output = $oModuleController->updateModuleConfig('member', $config); + $changed = true; } - foreach($config->signupForm as $signupItem) + if(empty($config->identifiers)) + { + $config->identifiers = array('user_id', 'email_address'); + $changed = true; + } + if(empty($config->signupForm) || !is_array($config->signupForm)) + { + $config->signupForm = MemberAdminController::createSignupForm($config); + $changed = true; + } + if($changed) + { + $oModuleController->updateModuleConfig('member', $config); + } + + $phone_found = false; + foreach($config->signupForm as $no => $signupItem) { if($signupItem->name === 'find_account_question') { - $config->identifier = $config->identifier ?: 'user_id'; - $config->signupForm = $oMemberAdminController->createSignupForm($config->identifier); - $output = $oModuleController->updateModuleConfig('member', $config); + unset($config->signupForm[$no]); + $config->signupForm = array_values($config->signupForm); + $changed = true; + continue; } if($signupItem->name === 'email_address' && $signupItem->isPublic !== 'N') { $signupItem->isPublic = 'N'; - $output = $oModuleController->updateModuleConfig('member', $config); + $changed = true; + continue; + } + if($signupItem->name === 'phone_number') + { + $phone_found = true; + continue; } } - if(!$config->agreements) + // Insert phone number after email address + if(!$phone_found) { - $config = memberModel::getMemberConfig(); - $config->identifier = $config->identifier ?: 'user_id'; - $config->signupForm = $oMemberAdminController->createSignupForm($config->identifier); - $output = $oModuleController->updateModuleConfig('member', $config); - } - - if($config->skin) - { - $config_parse = explode('.', $config->skin); - if (count($config_parse) > 1) + $newForm = array(); + foreach($config->signupForm as $signupItem) { - $template_path = sprintf('./themes/%s/modules/member/', $config_parse[0]); - if(is_dir($template_path)) + $newForm[] = $signupItem; + if($signupItem->name === 'email_address') { - $config->skin = implode('|@|', $config_parse); - $oModuleController = getController('module'); - $oModuleController->updateModuleConfig('member', $config); + $newItem = new stdClass; + $newItem->isDefaultForm = true; + $newItem->name = $newItem->title = 'phone_number'; + $newItem->mustRequired = false; + $newItem->imageType = false; + $newItem->required = false; + $newItem->isUse = false; + $newItem->isPublic = 'N'; + $newForm[] = $newItem; } } + $config->signupForm = $newForm; + $changed = true; } - if(is_readable('./files/member_extra_info/agreement.txt')) + // Check agreements + if(!$config->agreements) { - $source_file = _XE_PATH_.'files/member_extra_info/agreement.txt'; - $target_file = _XE_PATH_.'files/member_extra_info/agreement_' . Context::get('lang_type') . '.txt'; + $agreement = new stdClass; + $agreement->title = lang('agreement'); + $agreement->content = $config->agreement; + $agreement->use_editor = 'Y'; + $agreement->type = 'required'; + $config->agreements[] = $agreement; + $config->agreement = null; + $changed = true; + } + + // Save updated config + if($changed) + { + $oModuleController->updateModuleConfig('member', $config); + } + + if(file_exists('./files/member_extra_info/agreement.txt')) + { + $source_file = RX_BASEDIR.'files/member_extra_info/agreement.txt'; + $target_file = RX_BASEDIR.'files/member_extra_info/agreement_' . Context::get('lang_type') . '.txt'; FileHandler::rename($source_file, $target_file); } - FileHandler::makeDir('./files/ruleset'); - if(!is_readable('./files/ruleset/insertMember.xml')) - $oMemberAdminController->_createSignupRuleset($config->signupForm); - if(!is_readable('./files/ruleset/login.xml')) - $oMemberAdminController->_createLoginRuleset($config->identifier); - - // 2013. 11. 22 add menu when popup document menu called - if(!$oModuleModel->getTrigger('document.getDocumentMenu', 'member', 'controller', 'triggerGetDocumentMenu', 'after')) - $oModuleController->insertTrigger('document.getDocumentMenu', 'member', 'controller', 'triggerGetDocumentMenu', 'after'); - if(!$oModuleModel->getTrigger('comment.getCommentMenu', 'member', 'controller', 'triggerGetCommentMenu', 'after')) - $oModuleController->insertTrigger('comment.getCommentMenu', 'member', 'controller', 'triggerGetCommentMenu', 'after'); + if(FileHandler::exists('./files/ruleset/insertMember.xml')) + { + FileHandler::removeFile('./files/ruleset/insertMember.xml'); + } + if(FileHandler::exists('./files/ruleset/login.xml')) + { + FileHandler::removeFile('./files/ruleset/login.xml'); + } // Allow duplicate nickname if($config->allow_duplicate_nickname == 'Y') @@ -437,12 +589,64 @@ class member extends ModuleObject { } /** - * Re-generate the cache file + * Check mid * - * @return void + * This method returns 0 if the mid doesn't exist, + * 1 if the mid exists and belongs to this module, + * and -1 if the mid exists but belongs to a different module. + * + * @param string $mid + * @return int */ - function recompileCache() + public function checkMid($mid = 'member') { + $module_info = \ModuleModel::getModuleInfoByMid($mid); + if (!$module_info) + { + return 0; + } + elseif ($module_info->module === $this->module) + { + return 1; + } + else + { + return -1; + } + } + + /** + * Create mid + * + * @param string $mid + * @param string $skin + * @param string $mskin + * @return BaseObject + */ + public function createMid($mid = 'member', $skin = 'default', $mskin = 'default') + { + $check = $this->checkMid($mid); + if ($check == 1) + { + return new BaseObject(); + } + if ($check == -1) + { + return new BaseObject(-1, sprintf(lang('msg_exists_member_mid'), $mid)); + } + + return ModuleController::getInstance()->insertModule((object)array( + 'mid' => $mid, + 'module' => $this->module, + 'browser_title' => lang('member'), + 'description' => '', + 'layout_srl' => -1, + 'mlayout_srl' => -1, + 'skin' => $skin, + 'mskin' => $mskin, + 'use_mobile' => 'Y', + 'isMenuCreate' => false, + )); } /** @@ -453,15 +657,14 @@ class member extends ModuleObject { if($error == 0) return new BaseObject($error, $message); // Create a member model object - $oMemberModel = getModel('member'); - $config = $oMemberModel->getMemberConfig(); + $config = MemberModel::getMemberConfig(); // Check if there is recoding table. - $oDB = &DB::getInstance(); + $oDB = DB::getInstance(); if(!$oDB->isTableExists('member_login_count') || $config->enable_login_fail_report == 'N') return new BaseObject($error, $message); $args = new stdClass(); - $args->ipaddress = $_SERVER['REMOTE_ADDR']; + $args->ipaddress = \RX_CLIENT_IP; $output = executeQuery('member.getLoginCountByIp', $args); if($output->data && $output->data->count) @@ -477,8 +680,6 @@ class member extends ModuleObject { { $args->count = 1; } - unset($oMemberModel); - unset($config); $output = executeQuery('member.updateLoginCountByIp', $args); } else @@ -502,7 +703,7 @@ class member extends ModuleObject { $config = $oMemberModel->getMemberConfig(); // Check if there is recoding table. - $oDB = &DB::getInstance(); + $oDB = DB::getInstance(); if(!$oDB->isTableExists('member_count_history') || $config->enable_login_fail_report == 'N') return new BaseObject($error, $message); $output = executeQuery('member.getLoginCountHistoryByMemberSrl', $args); @@ -510,14 +711,18 @@ class member extends ModuleObject { { //update $content = unserialize($output->data->content); - $content[] = array($_SERVER['REMOTE_ADDR'],lang($message),$_SERVER['REQUEST_TIME']); + if (is_array($content) && count($content) >= 250) + { + $content = array_slice($content, -200); + } + $content[] = array(\RX_CLIENT_IP, lang($message), \RX_TIME); $args->content = serialize($content); $output = executeQuery('member.updateLoginCountHistoryByMemberSrl', $args); } else { //insert - $content[0] = array($_SERVER['REMOTE_ADDR'],lang($message),$_SERVER['REQUEST_TIME']); + $content[0] = array(\RX_CLIENT_IP, lang($message), \RX_TIME); $args->content = serialize($content); $output = executeQuery('member.insertLoginCountHistoryByMemberSrl', $args); } diff --git a/modules/member/member.controller.php b/modules/member/member.controller.php index 64afc68e7..a47d72a55 100644 --- a/modules/member/member.controller.php +++ b/modules/member/member.controller.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * Controller class of member module */ -class memberController extends member +class MemberController extends Member { /** * Initialization @@ -33,23 +33,50 @@ class memberController extends member throw new Rhymix\Framework\Exception('null_user_id'); } - // Variables - if(!$user_id) $user_id = Context::get('user_id'); - $user_id = trim($user_id); + $config = MemberModel::getMemberConfig(); - if(!$password) $password = Context::get('password'); - $password = trim($password); + // User ID, email address or phone number + if (!$user_id) + { + $user_id = (string)Context::get('user_id'); + } + if (!$user_id && $config->identifiers && in_array('email_address', $config->identifiers)) + { + $user_id = (string)Context::get('email_address'); + } + if (!$user_id && $config->identifiers && in_array('phone_number', $config->identifiers)) + { + $user_id = (string)Context::get('phone_number'); + } + if (!$user_id) + { + throw new Rhymix\Framework\Exception('null_user_id'); + } - if(!$keep_signed) $keep_signed = Context::get('keep_signed'); - // Return an error when id and password doesn't exist - if(!$user_id) throw new Rhymix\Framework\Exception('null_user_id'); - if(!$password) throw new Rhymix\Framework\Exception('null_password'); + // Password + if (!$password) + { + $password = (string)Context::get('password'); + } + if (!$password) + { + throw new Rhymix\Framework\Exception('null_password'); + } - $output = $this->doLogin($user_id, $password, $keep_signed=='Y'?true:false); - if (!$output->toBool()) return $output; + // Autologin option + if (!$keep_signed) + { + $keep_signed = Context::get('keep_signed'); + } - $oModuleModel = getModel('module'); - $config = $oModuleModel->getModuleConfig('member'); + // Attempt login + $output = $this->doLogin($user_id, $password, $keep_signed === 'Y' ? true : false); + if (!$output->toBool()) + { + return $output; + } + + // Get info of member who just logged in $member_info = Context::get('logged_info'); // Check change_password_date @@ -61,7 +88,8 @@ class memberController extends member if($member_info->change_password_date < date ('YmdHis', strtotime ('-' . $limit_date . ' day'))) { $msg = sprintf(lang('msg_change_password_date'), $limit_date); - return $this->setRedirectUrl(getNotEncodedUrl('','vid',Context::get('vid'),'mid',Context::get('mid'),'act','dispMemberModifyPassword'), new BaseObject(-1, $msg)); + $change_password_url = getNotEncodedUrl('', 'mid', Context::get('mid'), 'act', 'dispMemberModifyPassword'); + return $this->setRedirectUrl($change_password_url, new BaseObject(-1, $msg)); } } @@ -70,6 +98,9 @@ class memberController extends member $args->member_srl = $member_info->member_srl; executeQuery('member.deleteAuthMail', $args); + // If a device token is supplied, attempt to register it. + Rhymix\Modules\Member\Controllers\Device::getInstance()->autoRegisterDevice($member_info->member_srl); + if(!$config->after_login_url) { $returnUrl = Context::get('success_return_url') ? Context::get('success_return_url') : getNotEncodedUrl('', 'mid', Context::get('mid'), 'act', ''); @@ -92,23 +123,36 @@ class memberController extends member $logged_info = Context::get('logged_info'); $trigger_output = ModuleHandler::triggerCall('member.doLogout', 'before', $logged_info); if(!$trigger_output->toBool()) return $trigger_output; - + // Destroy session information Rhymix\Framework\Session::logout(); - $this->_clearMemberCache($logged_info->member_srl); - + self::clearMemberCache($logged_info->member_srl); + // Call a trigger after log-out (after) ModuleHandler::triggerCall('member.doLogout', 'after', $logged_info); - $output = new BaseObject(); - - $oModuleModel = getModel('module'); - $config = $oModuleModel->getModuleConfig('member'); - if($config->after_logout_url) + // If a device key is present, unregister it. + Rhymix\Modules\Member\Controllers\Device::getInstance()->autoUnregisterDevice($logged_info->member_srl); + if (isset($_COOKIE['device_key'])) { - $output->redirect_url = $config->after_logout_url; + Rhymix\Framework\Cookie::remove('device_key'); } + // Set redirect URL. + $output = new BaseObject(); + $redirect_url = Context::get('redirect_url'); + if ($redirect_url && Rhymix\Framework\URL::isInternalURL($redirect_url)) + { + $output->redirect_url = $redirect_url; + } + else + { + $config = ModuleModel::getModuleConfig('member'); + if($config->after_logout_url) + { + $output->redirect_url = $config->after_logout_url; + } + } return $output; } @@ -119,45 +163,43 @@ class memberController extends member */ function procMemberScrapDocument() { - $document_srl = (int) (Context::get('document_srl') ?: Context::get('target_srl')); + $document_srl = intval(Context::get('document_srl') ?: Context::get('target_srl')); if(!$document_srl) { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - - $oDocumentModel = getModel('document'); - $oDocument = $oDocumentModel->getDocument($document_srl); - + + $oDocument = DocumentModel::getDocument($document_srl); + // Check document if(!$oDocument->isAccessible()) { throw new Rhymix\Framework\Exception('msg_is_secret'); } - - $oModuleModel = getModel('module'); - $module_info = $oModuleModel->getModuleInfoByModuleSrl($oDocument->get('module_srl')); - + + $module_info = ModuleModel::getModuleInfoByModuleSrl($oDocument->get('module_srl')); + $logged_info = Context::get('logged_info'); - $grant = $oModuleModel->getGrant($module_info, $logged_info); - + $grant = ModuleModel::getGrant($module_info, $logged_info); + // Check access to module of the document if(!$grant->access) { throw new Rhymix\Framework\Exceptions\NotPermitted; } - + // Check grant to module of the document if(isset($grant->list) && isset($grant->view) && (!$grant->list || !$grant->view)) { throw new Rhymix\Framework\Exceptions\NotPermitted; } - + // Check consultation option if(isset($grant->consultation_read) && $module_info->consultation == 'Y' && !$grant->consultation_read && !$oDocument->isGranted()) { throw new Rhymix\Framework\Exceptions\NotPermitted; } - + // Find default scrap folder $args = new stdClass(); $args->member_srl = $logged_info->member_srl; @@ -169,32 +211,53 @@ class memberController extends member } else { - $default_folder_srl = null; + $output = $this->migrateMemberScrappedDocuments($logged_info->member_srl); + if ($output instanceof BaseObject) + { + return $output; + } + else + { + $default_folder_srl = $output; + } } - + // Variables $args = new stdClass(); - $args->document_srl = $document_srl; - $args->member_srl = $logged_info->member_srl; - $args->folder_srl = $default_folder_srl; + $args->document_srl = intval($document_srl); + $args->member_srl = intval($logged_info->member_srl); + $args->folder_srl = intval($default_folder_srl); $args->user_id = $oDocument->get('user_id'); $args->user_name = $oDocument->get('user_name'); $args->nick_name = $oDocument->get('nick_name'); $args->target_member_srl = $oDocument->get('member_srl'); $args->title = $oDocument->get('title'); - + // Check if already scrapped $output = executeQuery('member.getScrapDocument', $args); if($output->data->count) { throw new Rhymix\Framework\Exception('msg_alreay_scrapped'); } - + + // Call trigger (before) + $trigger_output = ModuleHandler::triggerCall('member.procMemberScrapDocument', 'before', $args); + if (!$trigger_output->toBool()) + { + return $trigger_output; + } + // Insert $output = executeQuery('member.addScrapDocument', $args); - if(!$output->toBool()) return $output; - - $this->setError(-1); + if(!$output->toBool()) + { + return $output; + } + + // Call trigger (after) + ModuleHandler::triggerCall('member.procMemberScrapDocument', 'after', $args); + + //$this->setError(-1); $this->setMessage('success_registed'); } @@ -209,14 +272,33 @@ class memberController extends member if(!Context::get('is_logged')) throw new Rhymix\Framework\Exceptions\MustLogin; $logged_info = Context::get('logged_info'); - $document_srl = (int)Context::get('document_srl'); - if(!$document_srl) throw new Rhymix\Framework\Exceptions\InvalidRequest; + $document_srl = intval(Context::get('document_srl') ?: Context::get('target_srl')); + if(!$document_srl) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } // Variables $args = new stdClass; $args->member_srl = $logged_info->member_srl; $args->document_srl = $document_srl; - return executeQuery('member.deleteScrapDocument', $args); + + // Call trigger (before) + $trigger_output = ModuleHandler::triggerCall('member.deleteScrapDocument', 'before', $args); + if (!$trigger_output->toBool()) + { + return $trigger_output; + } + + // Delete + $output = executeQuery('member.deleteScrapDocument', $args); + if (!$output->toBool()) + { + return $output; + } + + // Call trigger (after) + ModuleHandler::triggerCall('member.deleteScrapDocument', 'after', $args); } /** @@ -230,13 +312,13 @@ class memberController extends member if(!Context::get('is_logged')) throw new Rhymix\Framework\Exceptions\MustLogin; $logged_info = Context::get('logged_info'); - $document_srl = (int)Context::get('document_srl'); - $folder_srl = (int)Context::get('folder_srl'); + $document_srl = intval(Context::get('document_srl') ?: Context::get('target_srl')); + $folder_srl = intval(Context::get('folder_srl')); if(!$document_srl || !$folder_srl) { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - + // Check that the target folder exists and belongs to member $args = new stdClass; $args->member_srl = $logged_info->member_srl; @@ -246,7 +328,7 @@ class memberController extends member { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - + // Move $args = new stdClass; $args->member_srl = $logged_info->member_srl; @@ -265,7 +347,7 @@ class memberController extends member // Check login information if(!Context::get('is_logged')) throw new Rhymix\Framework\Exceptions\MustLogin; $logged_info = Context::get('logged_info'); - + // Get new folder name $folder_name = Context::get('name'); $folder_name = escape(trim(utf8_normalize_spaces($folder_name))); @@ -273,7 +355,7 @@ class memberController extends member { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - + // Check existing folder with same name $args = new stdClass; $args->member_srl = $logged_info->member_srl; @@ -283,7 +365,7 @@ class memberController extends member { throw new Rhymix\Framework\Exception('msg_folder_alreay_exists'); } - + // Create folder $args = new stdClass; $args->folder_srl = getNextSequence(); @@ -304,7 +386,7 @@ class memberController extends member // Check login information if(!Context::get('is_logged')) throw new Rhymix\Framework\Exceptions\MustLogin; $logged_info = Context::get('logged_info'); - + // Get new folder name $folder_srl = intval(Context::get('folder_srl')); $folder_name = Context::get('name'); @@ -313,7 +395,7 @@ class memberController extends member { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - + // Check that the original folder exists and belongs to member $args = new stdClass; $args->member_srl = $logged_info->member_srl; @@ -327,7 +409,7 @@ class memberController extends member { throw new Rhymix\Framework\Exception('msg_folder_is_default'); } - + // Check existing folder with same name $args = new stdClass; $args->member_srl = $logged_info->member_srl; @@ -338,7 +420,7 @@ class memberController extends member { throw new Rhymix\Framework\Exception('msg_folder_alreay_exists'); } - + // Rename folder $args = new stdClass; $args->folder_srl = $folder_srl; @@ -356,14 +438,14 @@ class memberController extends member // Check login information if(!Context::get('is_logged')) throw new Rhymix\Framework\Exceptions\MustLogin; $logged_info = Context::get('logged_info'); - + // Get folder_srl to delete $folder_srl = intval(Context::get('folder_srl')); if(!$folder_srl) { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - + // Check that the folder exists and belongs to member $args = new stdClass; $args->member_srl = $logged_info->member_srl; @@ -377,7 +459,7 @@ class memberController extends member { throw new Rhymix\Framework\Exception('msg_folder_is_default'); } - + // Check that the folder is empty $args = new stdClass; $args->member_srl = $logged_info->member_srl; @@ -387,7 +469,7 @@ class memberController extends member { throw new Rhymix\Framework\Exception('msg_folder_not_empty'); } - + // Delete folder $args = new stdClass; $args->folder_srl = $folder_srl; @@ -398,7 +480,7 @@ class memberController extends member * Migrate a member's scrapped documents to the new folder system. * * @param int $member_srl - * @return void|Object (void : success, Object : fail) + * @return int|BaseObject */ function migrateMemberScrappedDocuments($member_srl) { @@ -417,6 +499,8 @@ class memberController extends member { return $output; } + + return $args->folder_srl; } /** @@ -440,16 +524,15 @@ class memberController extends member if(!Context::get('is_logged')) throw new Rhymix\Framework\Exceptions\MustLogin; $logged_info = Context::get('logged_info'); - $document_srl = (int)Context::get('document_srl'); + $document_srl = intval(Context::get('document_srl') ?: Context::get('target_srl')); if(!$document_srl) throw new Rhymix\Framework\Exceptions\InvalidRequest; - - $oDocumentModel = getModel('document'); - $oDocument = $oDocumentModel->getDocument($document_srl); + + $oDocument = DocumentModel::getDocument($document_srl); if ($oDocument->get('member_srl') != $logged_info->member_srl) { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - $configStatusList = $oDocumentModel->getStatusList(); + $configStatusList = DocumentModel::getStatusList(); if ($oDocument->get('status') != $configStatusList['temp']) { throw new Rhymix\Framework\Exceptions\InvalidRequest; @@ -459,7 +542,7 @@ class memberController extends member $oDocumentController = getController('document'); $oDocumentController->deleteDocument($document_srl); } - + /** * Delete an autologin */ @@ -468,14 +551,14 @@ class memberController extends member // Check login information if(!Context::get('is_logged')) throw new Rhymix\Framework\Exceptions\MustLogin; $logged_info = Context::get('logged_info'); - + $autologin_id = intval(Context::get('autologin_id')); $autologin_key = Context::get('autologin_key'); if (!$autologin_id || !$autologin_key) { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - + $args = new stdClass; $args->autologin_id = $autologin_id; $args->autologin_key = $autologin_key; @@ -517,8 +600,7 @@ class memberController extends member $value = Context::get('value'); if(!$value) return; - $oMemberModel = getModel('member'); - $config = $oMemberModel->getMemberConfig(); + $config = MemberModel::getMemberConfig(); // Check if logged-in $logged_info = Context::get('logged_info'); @@ -527,33 +609,33 @@ class memberController extends member { case 'user_id' : // Check denied ID - if($oMemberModel->isDeniedID($value)) return new BaseObject(0,'denied_user_id'); + if(MemberModel::isDeniedID($value)) return new BaseObject(0,'denied_user_id'); // Check if duplicated - $member_srl = $oMemberModel->getMemberSrlByUserID($value); + $member_srl = MemberModel::getMemberSrlByUserID($value); if($member_srl && $logged_info->member_srl != $member_srl ) return new BaseObject(0,'msg_exists_user_id'); break; case 'nick_name' : // Check denied ID - if($oMemberModel->isDeniedNickName($value)) + if(MemberModel::isDeniedNickName($value)) { return new BaseObject(0,'denied_nick_name'); } // Check if duplicated if($config->allow_duplicate_nickname !== 'Y') { - $member_srl = $oMemberModel->getMemberSrlByNickName($value); + $member_srl = MemberModel::getMemberSrlByNickName($value); if($member_srl && $logged_info->member_srl != $member_srl ) return new BaseObject(0,'msg_exists_nick_name'); } break; case 'email_address' : // Check managed Email Host - if($oMemberModel->isDeniedEmailHost($value)) + if(MemberModel::isDeniedEmailHost($value)) { $emailhost_check = $config->emailhost_check; $managed_email_host = lang('managed_email_host'); - $email_hosts = $oMemberModel->getManagedEmailHosts(); + $email_hosts = MemberModel::getManagedEmailHosts(); foreach ($email_hosts as $host) { $hosts[] = $host->email_host; @@ -563,7 +645,7 @@ class memberController extends member } // Check if duplicated - $member_srl = $oMemberModel->getMemberSrlByEmailAddress($value); + $member_srl = MemberModel::getMemberSrlByEmailAddress($value); if($member_srl && $logged_info->member_srl != $member_srl ) return new BaseObject(0,'msg_exists_email_address'); break; } @@ -580,33 +662,54 @@ class memberController extends member { throw new Rhymix\Framework\Exceptions\SecurityViolation; } - - $oMemberModel = &getModel ('member'); - $config = $oMemberModel->getMemberConfig(); + + $config = MemberModel::getMemberConfig(); // call a trigger (before) - $trigger_output = ModuleHandler::triggerCall ('member.procMemberInsert', 'before', $config); + $trigger_output = ModuleHandler::triggerCall('member.procMemberInsert', 'before', $config); if(!$trigger_output->toBool ()) return $trigger_output; + // Check if an administrator allows a membership - if($config->enable_join != 'Y') throw new Rhymix\Framework\Exceptions\FeatureDisabled('msg_signup_disabled'); + if ($config->enable_join !== 'Y' || !$config->signupForm) + { + if (empty($config->enable_join_key) || !isset($_SESSION['signup_allowed']) || !$_SESSION['signup_allowed']) + { + throw new Rhymix\Framework\Exceptions\FeatureDisabled('msg_signup_disabled'); + } + } // Check if the user accept the license terms (only if terms exist) $accept_agreement = Context::get('accept_agreement'); + if(!is_array($accept_agreement)) + { + $accept_agreement = array_fill(0, count($config->agreements), $accept_agreement); + } + $accept_agreement_rearranged = array(); foreach($config->agreements as $i => $agreement) { - if($agreement->type === 'required' && $accept_agreement !== 'Y' && $accept_agreement[$i] !== 'Y') + if($agreement->type === 'disabled') + { + continue; + } + if($agreement->type === 'required' && $accept_agreement[$i] !== 'Y') { throw new Rhymix\Framework\Exception('msg_accept_agreement'); } + $accept_agreement_rearranged[$i] = $accept_agreement[$i] === 'Y' ? 'Y' : 'N'; } // Extract the necessary information in advance $getVars = array(); + $use_phone = false; if($config->signupForm) { foreach($config->signupForm as $formInfo) { - if($formInfo->isDefaultForm && ($formInfo->isUse || $formInfo->required || $formInfo->mustRequired)) + if($formInfo->name === 'phone_number' && $formInfo->isUse) + { + $use_phone = true; + } + if($formInfo->isUse || $formInfo->required || $formInfo->mustRequired) { $getVars[] = $formInfo->name; } @@ -617,13 +720,16 @@ class memberController extends member foreach($getVars as $val) { $args->{$val} = Context::get($val); - - if($val == 'birthday') + if ($val === 'birthday') { $args->birthday_ui = Context::get('birthday_ui'); } + if ($val === 'phone_number') + { + $args->phone_country = preg_replace('/[^A-Z]/', '', Context::get('phone_country')); + } } - + // mobile input date format can be different if($args->birthday) { @@ -636,60 +742,121 @@ class memberController extends member $args->birthday = intval($args->birthday); } } - + if(!$args->birthday && $args->birthday_ui) { $args->birthday = intval(strtr($args->birthday_ui, array('-'=>'', '/'=>'', '.'=>'', ' '=>''))); } - + $args->allow_mailing = Context::get('allow_mailing'); $args->allow_message = Context::get('allow_message'); - if($args->password1) $args->password = $args->password1; + // Check all required fields + $output = $this->_checkSignUpFields($config, $args, 'insert'); + if (!$output->toBool()) + { + return $output; + } + + // Check phone number + if ($use_phone) + { + $output = $this->_checkPhoneNumber($config, $args, 'insert'); + if (!$output->toBool()) + { + return $output; + } + } + // check password strength - if(!$oMemberModel->checkPasswordStrength($args->password, $config->password_strength)) + if(!MemberModel::checkPasswordStrength($args->password, $config->password_strength)) { $message = lang('about_password_strength'); throw new Rhymix\Framework\Exception($message[$config->password_strength]); } - // Remove some unnecessary variables from all the vars + // Get list of extra vars $all_args = Context::getRequestVars(); - unset($all_args->module); - unset($all_args->act); - unset($all_args->is_admin); - unset($all_args->member_srl); - unset($all_args->description); - unset($all_args->group_srl_list); - unset($all_args->body); - unset($all_args->accept_agreement); - unset($all_args->signature); - unset($all_args->password); - unset($all_args->password2); - unset($all_args->mid); - unset($all_args->error_return_url); - unset($all_args->ruleset); - unset($all_args->captchaType); - unset($all_args->secret_text); - - // Set the user state as "denied" when using mail authentication - if($config->enable_confirm == 'Y') $args->denied = 'Y'; - // Add extra vars after excluding necessary information from all the requested arguments - $extra_vars = delObjectVars($all_args, $args); - $args->extra_vars = serialize($extra_vars); - - // remove whitespace - $checkInfos = array('user_id', 'user_name', 'nick_name', 'email_address'); - foreach($checkInfos as $val) + $extra_vars = new stdClass; + foreach($config->signupForm as $formInfo) { - if(isset($args->{$val})) + if (!$formInfo->isDefaultForm) { - $args->{$val} = preg_replace('/[\pZ\pC]+/u', '', html_entity_decode($args->{$val})); + $extra_vars->{$formInfo->name} = $all_args->{$formInfo->name}; } } + $args->extra_vars = serialize($extra_vars); + + // Set the user state as "denied" when using mail authentication + if($config->enable_confirm == 'Y') + { + $args->denied = 'Y'; + $args->status = 'UNAUTHED'; + } + + // remove whitespace + foreach (['user_id', 'email_address'] as $val) + { + if (isset($args->{$val})) + { + $args->{$val} = preg_replace('/[\pZ\pC]+/u', '', utf8_clean(html_entity_decode($args->{$val}))); + } + } + if (isset($args->user_name)) + { + $args->user_name = utf8_normalize_spaces(utf8_clean(html_entity_decode($args->user_name))); + } + if (isset($args->nick_name)) + { + if (isset($config->nickname_spaces) && $config->nickname_spaces === 'Y') + { + $args->nick_name = utf8_normalize_spaces(utf8_clean(html_entity_decode($args->nick_name))); + } + else + { + $args->nick_name = preg_replace('/[\pZ\pC]+/u', '', utf8_clean(html_entity_decode($args->nick_name))); + } + + } + + // Check symbols in nickname + if($config->nickname_symbols === 'N') + { + if(preg_match('/[^\pL\d\s]/u', $args->nick_name, $matches)) + { + throw new Rhymix\Framework\Exception(sprintf(lang('msg_invalid_symbol_in_nickname'), escape($matches[0]))); + } + } + elseif($config->nickname_symbols === 'LIST') + { + $list = preg_quote($config->nickname_symbols_allowed_list, '/'); + if(preg_match('/[^\pL\d\s' . $list . ']/u', $args->nick_name, $matches)) + { + throw new Rhymix\Framework\Exception(sprintf(lang('msg_invalid_symbol_in_nickname'), escape($matches[0]))); + } + } + + // Insert member info $output = $this->insertMember($args); - if(!$output->toBool()) return $output; + if($output instanceof BaseObject && !$output->toBool()) + { + return $output; + } + + // Insert agreement info + foreach($accept_agreement_rearranged as $agreement_sequence => $agreed) + { + $ag_args = new stdClass; + $ag_args->member_srl = $args->member_srl; + $ag_args->agreement_sequence = $agreement_sequence; + $ag_args->agreed = $agreed; + $output = executeQuery('member.insertAgreed', $ag_args); + if($output instanceof BaseObject && !$output->toBool()) + { + return $output; + } + } // insert ProfileImage, ImageName, ImageMark $profile_image = Context::get('profile_image'); @@ -714,75 +881,75 @@ class memberController extends member $signature = Context::get('signature'); $this->putSignature($args->member_srl, $signature); - // If a virtual site, join the site - $site_module_info = Context::get('site_module_info'); - if($site_module_info->site_srl > 0) - { - $columnList = array('site_srl', 'group_srl'); - $default_group = $oMemberModel->getDefaultGroup($site_module_info->site_srl, $columnList); - if($default_group->group_srl) - { - $this->addMemberToGroup($args->member_srl, $default_group->group_srl, $site_module_info->site_srl); - } - - } // Log-in - if($config->enable_confirm != 'Y') + if ($config->enable_confirm != 'Y') { - if($config->identifier == 'email_address') + if (isset($config->identifiers) && is_array($config->identifiers)) { - $output = $this->doLogin($args->email_address); + $identifier = array_first($config->identifiers); } else { - $output = $this->doLogin($args->user_id); + $identifier = $config->identifier ?? 'user_id'; } - if(!$output->toBool()) { - if($output->error == -9) + + $output = $this->doLogin($args->{$identifier}); + if (!$output->toBool()) + { + if ($output->error == -9) + { $output->error = -11; + } return $this->setRedirectUrl(getUrl('', 'act', 'dispMemberLoginForm'), $output); } } - // Results - $this->add('member_srl', $args->member_srl); - if($config->redirect_url) $this->add('redirect_url', $config->redirect_url); - if($config->enable_confirm == 'Y') - { - $msg = sprintf(lang('msg_confirm_mail_sent'), $args->email_address); - $this->setMessage($msg); - return $this->setRedirectUrl(getUrl('', 'act', 'dispMemberLoginForm'), new BaseObject(-12, $msg)); - } - else $this->setMessage('success_registed'); - - // Call a trigger (after) - ModuleHandler::triggerCall('member.procMemberInsert', 'after', $config); + // Register device + Rhymix\Modules\Member\Controllers\Device::getInstance()->autoRegisterDevice($args->member_srl, false); + // Call a trigger (after) + $this->add('member_srl', $args->member_srl); + ModuleHandler::triggerCall('member.procMemberInsert', 'after', $config); + self::clearMemberCache($args->member_srl); + + // Redirect if($config->redirect_url) { - $returnUrl = $config->redirect_url; + $this->setRedirectUrl($config->redirect_url); + if ($config->enable_confirm === 'Y') + { + $this->setMessage(sprintf(lang('msg_confirm_mail_sent'), $args->email_address)); + } + } + elseif($config->enable_confirm === 'Y') + { + $this->setRedirectUrl(getNotEncodedUrl('', 'act', 'dispMemberLoginForm')); + $this->setMessage(sprintf(lang('msg_confirm_mail_sent'), $args->email_address)); } else { - if(Context::get('success_return_url')) + $this->setMessage('success_registed'); + + if (Context::get('success_return_url')) { - $returnUrl = Context::get('success_return_url'); + $this->setRedirectUrl(Context::get('success_return_url')); } - else if($_COOKIE['XE_REDIRECT_URL']) + elseif (isset($_SESSION['member_auth_referer'])) { - $returnUrl = $_COOKIE['XE_REDIRECT_URL']; - setcookie("XE_REDIRECT_URL", '', 1); + $redirect_url = $_SESSION['member_auth_referer']; + unset($_SESSION['member_auth_referer']); + $this->setRedirectUrl($redirect_url); + } + else + { + $this->setRedirectUrl(getNotEncodedUrl('')); } } - - $this->_clearMemberCache($args->member_srl, $site_module_info->site_srl); - - $this->setRedirectUrl($returnUrl); } function procMemberModifyInfoBefore() { - if($_SESSION['rechecked_password_step'] != 'INPUT_PASSWORD') + if (!isset($_SESSION['rechecked_password_step']) || $_SESSION['rechecked_password_step'] !== 'INPUT_PASSWORD') { throw new Rhymix\Framework\Exceptions\InvalidRequest; } @@ -799,16 +966,13 @@ class memberController extends member throw new Rhymix\Framework\Exceptions\InvalidRequest; } - $oMemberModel = getModel('member'); - // Get information of logged-in user $logged_info = Context::get('logged_info'); $member_srl = $logged_info->member_srl; - $columnList = array('member_srl', 'password'); - $member_info = $oMemberModel->getMemberInfoByMemberSrl($member_srl, 0, $columnList); - + $member_info = MemberModel::getMemberInfoByMemberSrl($member_srl); + // Verify the current password - if(!$oMemberModel->isValidPassword($member_info->password, $password)) + if(!MemberModel::isValidPassword($member_info->password, $password)) { throw new Rhymix\Framework\Exception('invalid_password'); } @@ -838,24 +1002,27 @@ class memberController extends member throw new Rhymix\Framework\Exceptions\MustLogin; } - if($_SESSION['rechecked_password_step'] != 'INPUT_DATA') + if (!isset($_SESSION['rechecked_password_step']) || $_SESSION['rechecked_password_step'] !== 'INPUT_DATA') { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - unset($_SESSION['rechecked_password_step']); + + // Get current module config and user info + $config = MemberModel::getMemberConfig(); + $logged_info = Context::get('logged_info'); // Extract the necessary information in advance - $oMemberModel = &getModel ('member'); - $config = $oMemberModel->getMemberConfig (); $getVars = array('allow_mailing','allow_message'); - if($config->signupForm) + $use_phone = false; + foreach($config->signupForm as $formInfo) { - foreach($config->signupForm as $formInfo) + if($formInfo->name === 'phone_number' && $formInfo->isUse) { - if($formInfo->isDefaultForm && ($formInfo->isUse || $formInfo->required || $formInfo->mustRequired)) - { - $getVars[] = $formInfo->name; - } + $use_phone = true; + } + if($formInfo->isUse || $formInfo->required || $formInfo->mustRequired) + { + $getVars[] = $formInfo->name; } } @@ -863,16 +1030,15 @@ class memberController extends member foreach($getVars as $val) { $args->{$val} = Context::get($val); - - if($val == 'birthday') + if($val === 'birthday') { $args->birthday_ui = Context::get('birthday_ui'); } + if ($val === 'phone_number') + { + $args->phone_country = preg_replace('/[^A-Z]/', '', Context::get('phone_country')); + } } - - // Login Information - $logged_info = Context::get('logged_info'); - $args->member_srl = $logged_info->member_srl; // mobile input date format can be different if($args->birthday) @@ -886,40 +1052,117 @@ class memberController extends member $args->birthday = intval($args->birthday); } } - + if(!$args->birthday && $args->birthday_ui) { $args->birthday = intval(strtr($args->birthday_ui, array('-'=>'', '/'=>'', '.'=>'', ' '=>''))); } - // Remove some unnecessary variables from all the vars - $all_args = Context::getRequestVars(); - unset($all_args->module); - unset($all_args->act); - unset($all_args->member_srl); - unset($all_args->is_admin); - unset($all_args->description); - unset($all_args->group_srl_list); - unset($all_args->body); - unset($all_args->accept_agreement); - unset($all_args->signature); - unset($all_args->_filter); - unset($all_args->mid); - unset($all_args->error_return_url); - unset($all_args->ruleset); - unset($all_args->password); + // Check all required fields + $skip = []; + foreach (['profile_image', 'image_name', 'image_mark'] as $key) + { + if (!empty($logged_info->{$key})) + { + $skip[] = $key; + } + } + $output = $this->_checkSignUpFields($config, $args, 'update', $skip); + if (!$output->toBool()) + { + return $output; + } + + // Check phone number + if ($use_phone) + { + $output = $this->_checkPhoneNumber($config, $args, 'update', $logged_info); + if (!$output->toBool()) + { + return $output; + } + } + + // Fill in member_srl + $args->member_srl = $logged_info->member_srl; + + // Get existing extra vars + $output = executeQuery('member.getMemberInfoByMemberSrl', ['member_srl' => $args->member_srl], ['extra_vars']); + $extra_vars = ($output->data && $output->data->extra_vars) ? unserialize($output->data->extra_vars) : new stdClass; + foreach(self::NOUSE_EXTRA_VARS as $key) + { + unset($extra_vars->$key); + } + + // Update extra vars + $all_args = Context::getRequestVars(); + foreach($config->signupForm as $formInfo) + { + if (!$formInfo->isDefaultForm) + { + $extra_vars->{$formInfo->name} = $all_args->{$formInfo->name}; + } + } - // Add extra vars after excluding necessary information from all the requested arguments - $extra_vars = delObjectVars($all_args, $args); $args->extra_vars = serialize($extra_vars); // remove whitespace - $checkInfos = array('user_id', 'user_name', 'nick_name', 'email_address'); - foreach($checkInfos as $val) + foreach (['user_id', 'email_address'] as $val) { - if(isset($args->{$val})) + if (isset($args->{$val})) { - $args->{$val} = preg_replace('/[\pZ\pC]+/u', '', html_entity_decode($args->{$val})); + $args->{$val} = preg_replace('/[\pZ\pC]+/u', '', utf8_clean(html_entity_decode($args->{$val}))); + } + } + if (isset($args->user_name)) + { + $args->user_name = utf8_normalize_spaces(utf8_clean(html_entity_decode($args->user_name))); + } + if (isset($args->nick_name)) + { + if (isset($config->nickname_spaces) && $config->nickname_spaces === 'Y') + { + $args->nick_name = utf8_normalize_spaces(utf8_clean(html_entity_decode($args->nick_name))); + } + else + { + $args->nick_name = preg_replace('/[\pZ\pC]+/u', '', utf8_clean(html_entity_decode($args->nick_name))); + } + + } + + // Check if nickname change is allowed + if(isset($config->allow_nickname_change) && $config->allow_nickname_change === 'N') + { + if (!empty($args->nick_name) && $args->nick_name !== $logged_info->nick_name) + { + return new BaseObject(-1, 'msg_nickname_not_changeable'); + } + } + + // Check if email address change is allowed + if(isset($config->enable_confirm) && $config->enable_confirm === 'Y') + { + if (!empty($args->email_address) && $args->email_address !== $logged_info->email_address) + { + return new BaseObject(-1, 'msg_email_address_not_changeable'); + } + } + + // Check symbols in nickname + if($config->nickname_symbols === 'N') + { + if(preg_match('/[^\pL\d\s]/u', $args->nick_name, $matches)) + { + throw new Rhymix\Framework\Exception(sprintf(lang('msg_invalid_symbol_in_nickname'), escape($matches[0]))); + } + } + elseif($config->nickname_symbols === 'LIST') + { + $list = preg_quote($config->nickname_symbols_allowed_list, '/'); + if(preg_match('/[^\pL\d\s' . $list . ']/u', $args->nick_name, $matches)) + { + throw new Rhymix\Framework\Exception(sprintf(lang('msg_invalid_symbol_in_nickname'), escape($matches[0]))); } } @@ -948,20 +1191,24 @@ class memberController extends member // Save Signature $signature = Context::get('signature'); $this->putSignature($args->member_srl, $signature); + if($config->member_allow_fileupload === 'Y') + { + getController('file')->setFilesValid($args->member_srl, 'sig'); + } // Get user_id information - $member_info = $oMemberModel->getMemberInfoByMemberSrl($args->member_srl); + $member_info = MemberModel::getMemberInfoByMemberSrl($args->member_srl); // Call a trigger after successfully modified (after) ModuleHandler::triggerCall('member.procMemberModifyInfo', 'after', $member_info); + unset($_SESSION['rechecked_password_step']); $this->setSessionInfo(); - + // Return result $this->add('member_srl', $args->member_srl); $this->setMessage('success_updated'); - $site_module_info = Context::get('site_module_info'); - $this->_clearMemberCache($args->member_srl, $site_module_info->site_srl); + self::clearMemberCache($args->member_srl); $returnUrl = Context::get('success_return_url') ? Context::get('success_return_url') : getNotEncodedUrl('', 'mid', Context::get('mid'), 'act', 'dispMemberInfo'); $this->setRedirectUrl($returnUrl); @@ -972,46 +1219,127 @@ class memberController extends member * * @return void|Object (void : success, Object : fail) */ - function procMemberModifyPassword() + public function procMemberModifyPassword() { - if(!Context::get('is_logged')) throw new Rhymix\Framework\Exceptions\MustLogin; - // Extract the necessary information in advance - $current_password = trim(Context::get('current_password')); - $password = trim(Context::get('password1')); - // Get information of logged-in user - $logged_info = Context::get('logged_info'); - $member_srl = $logged_info->member_srl; - // Create a member model object - $oMemberModel = getModel('member'); - // Get information of member_srl - $columnList = array('member_srl', 'password'); + $config = MemberModel::getMemberConfig(); + $vars = Context::getRequestVars(); + if (!$this->user->member_srl) + { + throw new Rhymix\Framework\Exceptions\MustLogin; + } - $member_info = $oMemberModel->getMemberInfoByMemberSrl($member_srl, 0, $columnList); - // Verify the cuttent password - if(!$oMemberModel->isValidPassword($member_info->password, $current_password, $member_srl)) throw new Rhymix\Framework\Exception('invalid_password'); + // Extract the necessary information in advance + $current_password = trim($vars->current_password); + $password = trim($vars->password1); + + // Get information of logged-in user + $member_srl = $this->user->member_srl; + $member_info = MemberModel::getMemberInfoByMemberSrl($member_srl); + + // Verify the current password + if (!MemberModel::isValidPassword($member_info->password, $current_password, $member_srl)) + { + throw new Rhymix\Framework\Exception('invalid_current_password'); + } // Check if a new password is as same as the previous password - if($current_password == $password) throw new Rhymix\Framework\Exception('invalid_new_password'); + if ($current_password === $password) + { + throw new Rhymix\Framework\Exception('invalid_new_password'); + } // Execute insert or update depending on the value of member_srl $args = new stdClass; $args->member_srl = $member_srl; $args->password = $password; $output = $this->updateMemberPassword($args); - if(!$output->toBool()) return $output; - + if (!$output->toBool()) + { + return $output; + } + // Log out all other sessions. - $oModuleModel = getModel('module'); - $member_config = $oModuleModel->getModuleConfig('member'); - if ($member_config->password_change_invalidate_other_sessions === 'Y') + if ($config->password_change_invalidate_other_sessions === 'Y') { Rhymix\Framework\Session::destroyOtherSessions($member_srl); } - $this->add('member_srl', $args->member_srl); - $this->setMessage('success_updated'); + $this->add('member_srl', $member_srl); + $this->setMessage('member.msg_password_changed'); - $returnUrl = Context::get('success_return_url') ? Context::get('success_return_url') : getNotEncodedUrl('', 'mid', Context::get('mid'), 'act', 'dispMemberInfo'); + if (Context::get('success_return_url')) + { + $returnUrl = Context::get('success_return_url'); + } + else + { + $returnUrl = getNotEncodedUrl('', 'mid', Context::get('mid'), 'act', 'dispMemberInfo'); + } + $this->setRedirectUrl($returnUrl); + } + + /** + * Change password using auth_key instead of current password + */ + public function procMemberResetPassword() + { + $config = MemberModel::getMemberConfig(); + $vars = Context::getRequestVars(); + + // Check auth_key + if (empty($vars->auth_key)) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + + $output = executeQuery('member.getAuthMail', ['auth_key' => $vars->auth_key]); + if(!$output->toBool() || $output->data->auth_key !== $vars->auth_key) + { + executeQuery('member.deleteAuthMail', ['auth_key' => $vars->auth_key]); + throw new Rhymix\Framework\Exception('msg_invalid_auth_key'); + } + + $member_srl = $output->data->member_srl; + if (!$member_srl || $output->data->auth_type !== 'password_v2') + { + executeQuery('member.deleteAuthMail', ['auth_key' => $vars->auth_key]); + throw new Rhymix\Framework\Exception('msg_invalid_auth_key'); + } + + $expires = (intval($config->authmail_expires) * intval($config->authmail_expires_unit)) ?: 86400; + if(ztime($output->data->regdate) < time() - $expires) + { + executeQuery('member.deleteAuthMail', ['auth_key' => $vars->auth_key]); + throw new Rhymix\Framework\Exception('msg_expired_auth_key'); + } + + // Update the password + $args = new stdClass; + $args->member_srl = $member_srl; + $args->password = trim($vars->password1); + $output = $this->updateMemberPassword($args); + if (!$output->toBool()) + { + return $output; + } + + // Log out all other sessions. + if ($config->password_change_invalidate_other_sessions === 'Y') + { + Rhymix\Framework\Session::destroyOtherSessions($member_srl); + } + + $this->add('member_srl', $member_srl); + $this->setMessage('member.msg_password_changed'); + + if (Context::get('success_return_url')) + { + $returnUrl = Context::get('success_return_url'); + } + else + { + $returnUrl = getNotEncodedUrl('', 'mid', Context::get('mid'), 'act', 'dispMemberLoginForm'); + } $this->setRedirectUrl($returnUrl); } @@ -1024,17 +1352,16 @@ class memberController extends member { if(!Context::get('is_logged')) throw new Rhymix\Framework\Exceptions\MustLogin; // Extract the necessary information in advance - $password = trim(Context::get('password')); + $password = (string)Context::get('password'); // Get information of logged-in user $logged_info = Context::get('logged_info'); $member_srl = $logged_info->member_srl; - // Create a member model object - $oMemberModel = getModel('member'); - // Get information of member_srl - $columnList = array('member_srl', 'password'); - $member_info = $oMemberModel->getMemberInfoByMemberSrl($member_srl, 0, $columnList); + $member_info = MemberModel::getMemberInfoByMemberSrl($member_srl); // Verify the cuttent password - if(!$oMemberModel->isValidPassword($member_info->password, $password)) throw new Rhymix\Framework\Exception('invalid_password'); + if (!MemberModel::isValidPassword($member_info->password, $password)) + { + throw new Rhymix\Framework\Exception('invalid_password'); + } $output = $this->deleteMember($member_srl); if(!$output->toBool()) return $output; @@ -1065,8 +1392,7 @@ class memberController extends member $logged_info = Context::get('logged_info'); if($logged_info->is_admin != 'Y' && $logged_info->member_srl != $member_srl) throw new Rhymix\Framework\Exception('msg_not_uploaded_profile_image'); // Return if member module is set not to use an image name or the user is not an administrator ; - $oMemberModel = getModel('member'); - $config = $oMemberModel->getMemberConfig(); + $config = MemberModel::getMemberConfig(); if($logged_info->is_admin != 'Y' && $config->profile_image != 'Y') throw new Rhymix\Framework\Exception('msg_not_uploaded_profile_image'); $output = $this->insertProfileImage($member_srl, $file['tmp_name']); @@ -1080,24 +1406,23 @@ class memberController extends member * Insert a profile image * * @param int $member_srl - * @param object $target_file + * @param string $target_file * * @return void */ function insertProfileImage($member_srl, $target_file) { - $oMemberModel = getModel('member'); - $config = $oMemberModel->getMemberConfig(); - + $config = MemberModel::getMemberConfig(); + // Get an image size $max_width = $config->profile_image_max_width; $max_height = $config->profile_image_max_height; $max_filesize = $config->profile_image_max_filesize; - Context::loadLang(_XE_PATH_ . 'modules/file/lang'); + Context::loadLang(RX_BASEDIR . 'modules/file/lang'); // Get file information - FileHandler::clearStatCache($target_file); + clearstatcache($target_file); list($width, $height, $type) = @getimagesize($target_file); if(IMAGETYPE_PNG == $type) $ext = 'png'; elseif(IMAGETYPE_JPEG == $type) $ext = 'jpg'; @@ -1108,17 +1433,34 @@ class memberController extends member } $target_path = sprintf('files/member_extra_info/profile_image/%s', getNumberingPath($member_srl)); + $target_filename = sprintf('%s%d.%s', $target_path, $member_srl, $ext); FileHandler::makeDir($target_path); - $target_filename = sprintf('%s%d.%s', $target_path, $member_srl, $ext); // Convert if the image size is larger than a given size - if($width > $max_width || $height > $max_height) + if ($width > $max_width || $height > $max_height) + { + $resize = true; + } + elseif ($config->profile_image_force_ratio !== 'N' && ($width / $height !== $max_width / $max_height)) + { + $resize = true; + } + else + { + $resize = false; + } + + // Check image rotation + $rotate = $ext === 'jpg' ? FileHandler::checkImageRotation($target_file) : 0; + + // Resize or rotate if necessary + if ($resize || $rotate) { $temp_filename = sprintf('files/cache/tmp/profile_image_%d.%s', $member_srl, $ext); - FileHandler::createImageFile($target_file, $temp_filename, $max_width, $max_height, $ext); + FileHandler::createImageFile($target_file, $temp_filename, $max_width, $max_height, $ext, 'fill', 75, $rotate); // 파일 용량 제한 - FileHandler::clearStatCache($temp_filename); + clearstatcache($temp_filename); $filesize = filesize($temp_filename); if($max_filesize && $filesize > ($max_filesize * 1024)) { @@ -1131,7 +1473,6 @@ class memberController extends member FileHandler::removeFilesInDir($target_path); FileHandler::moveFile($temp_filename, $target_filename); - FileHandler::clearStatCache($target_filename); } else { @@ -1147,9 +1488,14 @@ class memberController extends member FileHandler::removeFilesInDir($target_path); @copy($target_file, $target_filename); - FileHandler::clearStatCache($target_filename); } + // Clear cache + foreach (['jpg', 'jpeg', 'gif', 'png'] as $ext) + { + clearstatcache(true, \RX_BASEDIR . sprintf('files/member_extra_info/profile_image/%s%d.%s', getNumberingPath($member_srl), $member_srl, $ext)); + } + self::clearMemberCache($member_srl); return new BaseObject(0, 'success'); } @@ -1170,8 +1516,7 @@ class memberController extends member $logged_info = Context::get('logged_info'); if($logged_info->is_admin != 'Y' && $logged_info->member_srl != $member_srl) throw new Rhymix\Framework\Exception('msg_not_uploaded_image_name'); // Return if member module is set not to use an image name or the user is not an administrator ; - $oMemberModel = getModel('member'); - $config = $oMemberModel->getMemberConfig(); + $config = MemberModel::getMemberConfig(); if($logged_info->is_admin != 'Y' && $config->image_name != 'Y') throw new Rhymix\Framework\Exception('msg_not_uploaded_image_name'); $output = $this->insertImageName($member_srl, $file['tmp_name']); @@ -1194,15 +1539,14 @@ class memberController extends member */ function insertImageName($member_srl, $target_file) { - $oMemberModel = getModel('member'); - $config = $oMemberModel->getMemberConfig(); - + $config = MemberModel::getMemberConfig(); + // Get an image size $max_width = $config->image_name_max_width; $max_height = $config->image_name_max_height; $max_filesize = $config->image_name_max_filesize; - Context::loadLang(_XE_PATH_ . 'modules/file/lang'); + Context::loadLang(RX_BASEDIR . 'modules/file/lang'); // Get a target path to save $target_path = sprintf('files/member_extra_info/image_name/%s/', getNumberingPath($member_srl)); @@ -1231,7 +1575,6 @@ class memberController extends member FileHandler::removeFilesInDir($target_path); FileHandler::moveFile($temp_filename, $target_filename); - FileHandler::clearStatCache($target_filename); } else { @@ -1247,9 +1590,11 @@ class memberController extends member FileHandler::removeFilesInDir($target_path); @copy($target_file, $target_filename); - FileHandler::clearStatCache($target_filename); + } + clearstatcache(true, $target_filename); + self::clearMemberCache($member_srl); return new BaseObject(0, 'success'); } @@ -1270,11 +1615,14 @@ class memberController extends member if($logged_info && ($logged_info->is_admin == 'Y' || $logged_info->member_srl == $member_srl)) { - $oMemberModel = getModel('member'); - $profile_image = $oMemberModel->getProfileImage($member_srl); - FileHandler::removeFile($profile_image->file); - Rhymix\Framework\Storage::deleteEmptyDirectory(dirname(FileHandler::getRealPath($profile_image->file)), true); - $this->_clearMemberCache($member_srl); + $profile_image = MemberModel::getProfileImage($member_srl); + if(!empty($profile_image->file)) + { + FileHandler::removeFile($profile_image->file); + Rhymix\Framework\Storage::deleteEmptyDirectory(dirname(FileHandler::getRealPath($profile_image->file)), true); + FileHandler::clearStatCache($profile_image->file); + self::clearMemberCache($member_srl); + } } return new BaseObject(0,'success'); } @@ -1296,10 +1644,14 @@ class memberController extends member if($logged_info && ($logged_info->is_admin == 'Y' || $logged_info->member_srl == $member_srl)) { - $oMemberModel = getModel('member'); - $image_name = $oMemberModel->getImageName($member_srl); - FileHandler::removeFile($image_name->file); - Rhymix\Framework\Storage::deleteEmptyDirectory(dirname(FileHandler::getRealPath($image_name->file)), true); + $image_name = MemberModel::getImageName($member_srl); + if(!empty($image_name->file)) + { + FileHandler::removeFile($image_name->file); + Rhymix\Framework\Storage::deleteEmptyDirectory(dirname(FileHandler::getRealPath($image_name->file)), true); + FileHandler::clearStatCache($profile_image->file); + self::clearMemberCache($member_srl); + } } return new BaseObject(0,'success'); } @@ -1321,8 +1673,7 @@ class memberController extends member $logged_info = Context::get('logged_info'); if($logged_info->is_admin != 'Y' && $logged_info->member_srl != $member_srl) throw new Rhymix\Framework\Exception('msg_not_uploaded_image_mark'); // Membership in the images mark the module using the ban was set by an administrator or return; - $oMemberModel = getModel('member'); - $config = $oMemberModel->getMemberConfig(); + $config = MemberModel::getMemberConfig(); if($logged_info->is_admin != 'Y' && $config->image_mark != 'Y') throw new Rhymix\Framework\Exception('msg_not_uploaded_image_mark'); $this->insertImageMark($member_srl, $file['tmp_name']); @@ -1342,15 +1693,14 @@ class memberController extends member */ function insertImageMark($member_srl, $target_file) { - $oMemberModel = getModel('member'); - $config = $oMemberModel->getMemberConfig(); - + $config = MemberModel::getMemberConfig(); + // Get an image size $max_width = $config->image_mark_max_width; $max_height = $config->image_mark_max_height; $max_filesize = $config->image_mark_max_filesize; - Context::loadLang(_XE_PATH_ . 'modules/file/lang'); + Context::loadLang(RX_BASEDIR . 'modules/file/lang'); $target_path = sprintf('files/member_extra_info/image_mark/%s/', getNumberingPath($member_srl)); FileHandler::makeDir($target_path); @@ -1378,7 +1728,6 @@ class memberController extends member FileHandler::removeFilesInDir($target_path); FileHandler::moveFile($temp_filename, $target_filename); - FileHandler::clearStatCache($target_filename); } else { @@ -1394,9 +1743,10 @@ class memberController extends member FileHandler::removeFilesInDir($target_path); @copy($target_file, $target_filename); - FileHandler::clearStatCache($target_filename); } + clearstatcache(true, $target_filename); + self::clearMemberCache($member_srl); return new BaseObject(0, 'success'); } @@ -1417,10 +1767,14 @@ class memberController extends member if($logged_info && ($logged_info->is_admin == 'Y' || $logged_info->member_srl == $member_srl)) { - $oMemberModel = getModel('member'); - $image_mark = $oMemberModel->getImageMark($member_srl); - FileHandler::removeFile($image_mark->file); - Rhymix\Framework\Storage::deleteEmptyDirectory(dirname(FileHandler::getRealPath($image_mark->file)), true); + $image_mark = MemberModel::getImageMark($member_srl); + if(!empty($image_mark->file)) + { + FileHandler::removeFile($image_mark->file); + Rhymix\Framework\Storage::deleteEmptyDirectory(dirname(FileHandler::getRealPath($image_mark->file)), true); + FileHandler::clearStatCache($profile_image->file); + self::clearMemberCache($member_srl); + } } return new BaseObject(0,'success'); } @@ -1435,16 +1789,16 @@ class memberController extends member $email_address = Context::get('email_address'); if(!$email_address) throw new Rhymix\Framework\Exceptions\InvalidRequest; - $oMemberModel = getModel('member'); - $oModuleModel = getModel('module'); - // Check if a member having the same email address exists - $member_srl = $oMemberModel->getMemberSrlByEmailAddress($email_address); - if(!$member_srl) throw new Rhymix\Framework\Exception('msg_email_not_exists'); + $member_srl = MemberModel::getMemberSrlByEmailAddress($email_address); + if(!$member_srl) throw new Rhymix\Framework\Exception('msg_not_exists_member'); // Get information of the member - $columnList = array('denied', 'member_srl', 'user_id', 'user_name', 'email_address', 'nick_name'); - $member_info = $oMemberModel->getMemberInfoByMemberSrl($member_srl, 0, $columnList); + $member_info = MemberModel::getMemberInfoByMemberSrl($member_srl); + if(!$member_info || !$member_info->member_srl) + { + throw new Rhymix\Framework\Exception('msg_not_exists_member'); + } // Check if possible to find member's ID and password if($member_info->denied == 'Y') @@ -1452,42 +1806,45 @@ class memberController extends member $chk_args = new stdClass; $chk_args->member_srl = $member_info->member_srl; $output = executeQuery('member.chkAuthMail', $chk_args); - if($output->toBool() && $output->data->count != '0') throw new Rhymix\Framework\Exception('msg_user_not_confirmed'); + if ($output->toBool() && $output->data->count > 0) + { + throw new Rhymix\Framework\Exception(sprintf('msg_user_not_confirmed', $member_info->email_address)); + } } + // Get password reset method + $member_config = ModuleModel::getModuleConfig('member'); + $password_reset_method = intval($member_config->password_reset_method ?? 1); + // Insert data into the authentication DB $args = new stdClass(); $args->user_id = $member_info->user_id; $args->member_srl = $member_info->member_srl; - $args->new_password = Rhymix\Framework\Password::getRandomPassword(8); + $args->new_password = $password_reset_method == 2 ? '' : Rhymix\Framework\Password::getRandomPassword(8); $args->auth_key = Rhymix\Framework\Security::getRandom(40, 'hex'); + $args->auth_type = 'password_v' . $password_reset_method; $args->is_register = 'N'; $output = executeQuery('member.insertAuthMail', $args); if(!$output->toBool()) return $output; + // Get content of the email to send a member + global $lang; + if ($password_reset_method == 2) + { + $args->new_password = lang('member.msg_change_after_click'); + $lang->set('msg_find_account_comment', lang('member.msg_find_account_comment_v2')); + } Context::set('auth_args', $args); - $member_config = $oModuleModel->getModuleConfig('member'); $memberInfo = array(); - global $lang; - if(is_array($member_config->signupForm)) + if (in_array('user_id', $member_config->identifiers)) { - $exceptForm=array('password', 'find_account_question'); - foreach($member_config->signupForm as $form) - { - if(!in_array($form->name, $exceptForm) && $form->isDefaultForm && ($form->required || $form->mustRequired)) - { - $memberInfo[$lang->{$form->name}] = $member_info->{$form->name}; - } - } + $memberInfo[$lang->user_id] = $member_info->user_id; } - else + if (in_array('email_address', $member_config->identifiers)) { - $memberInfo[$lang->user_id] = $args->user_id; - $memberInfo[$lang->user_name] = $args->user_name; - $memberInfo[$lang->nick_name] = $args->nick_name; - $memberInfo[$lang->email_address] = $args->email_address; + $memberInfo[$lang->email_address] = $member_info->email_address; } Context::set('memberInfo', $memberInfo); @@ -1502,12 +1859,11 @@ class memberController extends member $find_url = getFullUrl ('', 'module', 'member', 'act', 'procMemberAuthAccount', 'member_srl', $member_info->member_srl, 'auth_key', $args->auth_key); Context::set('find_url', $find_url); - $oTemplate = &TemplateHandler::getInstance(); - $content = $oTemplate->compile($tpl_path, 'find_member_account_mail'); + $oTemplate = new Rhymix\Framework\Template($tpl_path, 'find_member_account_mail'); + $content = $oTemplate->compile(); // Get information of the Webmaster - $oModuleModel = getModel('module'); - $member_config = $oModuleModel->getModuleConfig('member'); + $member_config = ModuleModel::getModuleConfig('member'); // Send a mail $oMail = new \Rhymix\Framework\Mail(); @@ -1544,7 +1900,7 @@ class memberController extends member */ function procMemberAuthAccount() { - $oMemberModel = getModel('member'); + $config = MemberModel::getMemberConfig(); // Test user_id and authkey $member_srl = Context::get('member_srl'); @@ -1566,50 +1922,79 @@ class memberController extends member } // Test logs for finding password by user_id and authkey - $args = new stdClass; - $args->member_srl = $member_srl; - $args->auth_key = $auth_key; - $output = executeQuery('member.getAuthMail', $args); - - if(!$output->toBool() || $output->data->auth_key !== $auth_key) + $output = executeQuery('member.getAuthMail', ['auth_key' => $auth_key]); + if(!$output->data || $output->data->auth_key !== $auth_key || $output->data->member_srl != $member_srl) { - executeQuery('member.deleteAuthMail', $args); + executeQuery('member.deleteAuthMail', ['member_srl' => $member_srl]); throw new Rhymix\Framework\Exception('msg_invalid_auth_key'); } - if(ztime($output->data->regdate) < time() - (86400 * 3)) + $expires = (intval($config->authmail_expires) * intval($config->authmail_expires_unit)) ?: 86400; + if(ztime($output->data->regdate) < time() - $expires) { - executeQuery('member.deleteAuthMail', $args); + executeQuery('member.deleteAuthMail', ['auth_key' => $auth_key]); throw new Rhymix\Framework\Exception('msg_expired_auth_key'); } // Back up the value of $output->data->is_register $is_register = $output->data->is_register; + $password_reset_method = $output->data->auth_type === 'password_v2' ? 2 : 1; // If credentials are correct, change the password to a new one if($is_register === 'Y') { - $args->denied = 'N'; + $query_id = 'member.updateMemberStatus'; + $args = [ + 'member_srl' => $member_srl, + 'denied' => 'N', + 'status' => 'APPROVED', + ]; + } + elseif ($password_reset_method == 1) + { + $query_id = 'member.updateMemberPassword'; + $args = [ + 'member_srl' => $member_srl, + 'password' => MemberModel::hashPassword($output->data->new_password), + ]; } else { - $args->password = $oMemberModel->hashPassword($output->data->new_password); + $this->setLayoutAndTemplatePaths(Context::get('m') ? 'M' : 'P', $config); + $tpl_file = sprintf('%s%s', $this->getTemplatePath(), 'reset_password.html'); + if (!Rhymix\Framework\Storage::exists($tpl_file)) + { + $tpl_file = sprintf('%s%s', $this->getTemplatePath(), 'reset_password.blade.php'); + if (!Rhymix\Framework\Storage::exists($tpl_file)) + { + $this->setTemplatePath(sprintf('%sskins/%s', $this->module_path, 'default')); + } + } + $this->setTemplateFile('reset_password'); + Context::set('member_config', $config ?? ''); + return; } - $output = executeQuery('member.updateMemberPassword', $args); + $output = executeQuery($query_id, $args); if(!$output->toBool()) { return $output; } - // Remove all values having the member_srl from authentication table - executeQuery('member.deleteAuthMail',$args); + // 인증 정보를 여기서 삭제하지 않고 로그인 시점에 삭제되도록 함 + // https://github.com/rhymix/rhymix/issues/1232 + // executeQuery('member.deleteAuthMail', $args); - $this->_clearMemberCache($args->member_srl); + // Clear login failure log + // https://github.com/rhymix/rhymix/issues/1429 + executeQuery('member.deleteLoginCountByIp', ['ipaddress' => \RX_CLIENT_IP]); + + // Clear member cache + self::clearMemberCache($member_srl); // Call a trigger (after) $trigger_obj->is_register = $is_register; - $trigger_output = ModuleHandler::triggerCall('member.procMemberAuthAccount', 'after', $trigger_obj); + ModuleHandler::triggerCall('member.procMemberAuthAccount', 'after', $trigger_obj); // Notify the result $message = $is_register === 'Y' ? lang('msg_success_confirmed') : lang('msg_success_authed'); @@ -1628,18 +2013,25 @@ class memberController extends member $email_address = Context::get('email_address'); if(!$email_address) throw new Rhymix\Framework\Exceptions\InvalidRequest; // Log test by using email_address - $oMemberModel = getModel('member'); - $args = new stdClass; $args->email_address = $email_address; - $memberSrl = $oMemberModel->getMemberSrlByEmailAddress($email_address); - if(!$memberSrl) throw new Rhymix\Framework\Exception('msg_not_exists_member'); + $member_srl = MemberModel::getMemberSrlByEmailAddress($email_address); + if(!$member_srl) + { + throw new Rhymix\Framework\Exception('msg_not_exists_member'); + } - $columnList = array('member_srl', 'user_id', 'user_name', 'nick_name', 'email_address'); - $member_info = $oMemberModel->getMemberInfoByMemberSrl($memberSrl, 0, $columnList); + $member_info = MemberModel::getMemberInfoByMemberSrl($member_srl); + if(!$member_info || !$member_info->member_srl) + { + throw new Rhymix\Framework\Exception('msg_not_exists_member'); + } + if($member_info->status !== 'UNAUTHED') + { + throw new Rhymix\Framework\Exception('msg_activation_not_needed'); + } - $oModuleModel = getModel('module'); - $member_config = $oModuleModel->getModuleConfig('member'); + $member_config = ModuleModel::getModuleConfig('member'); if(!$member_config->skin) $member_config->skin = "default"; if(!$member_config->colorset) $member_config->colorset = "white"; @@ -1647,19 +2039,26 @@ class memberController extends member $chk_args = new stdClass; $chk_args->member_srl = $member_info->member_srl; $output = executeQuery('member.chkAuthMail', $chk_args); - if($output->toBool() && $output->data->count == '0') throw new Rhymix\Framework\Exceptions\InvalidRequest; + if($output->toBool() && $output->data->count == '0') + { + throw new Rhymix\Framework\Exception('msg_activation_key_not_found'); + } $auth_args = new stdClass; $auth_args->member_srl = $member_info->member_srl; $output = executeQueryArray('member.getAuthMailInfo', $auth_args); - if(!$output->data || !$output->data[0]->auth_key) throw new Rhymix\Framework\Exceptions\InvalidRequest; + if(!$output->data || !$output->data[0]->auth_key) + { + throw new Rhymix\Framework\Exception('msg_activation_key_not_found'); + } + $auth_info = $output->data[0]; // Update the regdate of authmail entry $renewal_args = new stdClass; $renewal_args->member_srl = $member_info->member_srl; $renewal_args->auth_key = $auth_info->auth_key; - $output = executeQuery('member.updateAuthMail', $renewal_args); + $output = executeQuery('member.updateAuthMail', $renewal_args); $memberInfo = array(); global $lang; @@ -1692,8 +2091,8 @@ class memberController extends member $auth_url = getFullUrl('','module','member','act','procMemberAuthAccount','member_srl',$member_info->member_srl, 'auth_key',$auth_info->auth_key); Context::set('auth_url', $auth_url); - $oTemplate = &TemplateHandler::getInstance(); - $content = $oTemplate->compile($tpl_path, 'confirm_member_account_mail'); + $oTemplate = new Rhymix\Framework\Template($tpl_path, 'confirm_member_account_mail'); + $content = $oTemplate->compile(); // Send a mail $oMail = new \Rhymix\Framework\Mail(); @@ -1709,101 +2108,9 @@ class memberController extends member $this->setRedirectUrl($returnUrl); } - function procMemberResetAuthMail() - { - $memberInfo = $_SESSION['auth_member_info']; - unset($_SESSION['auth_member_info']); - - if(!$memberInfo) - { - throw new Rhymix\Framework\Exceptions\InvalidRequest; - } - - $newEmail = Context::get('email_address'); - - if(!$newEmail) - { - throw new Rhymix\Framework\Exceptions\InvalidRequest; - } - - $oMemberModel = getModel('member'); - $member_srl = $oMemberModel->getMemberSrlByEmailAddress($newEmail); - if($member_srl) - { - throw new Rhymix\Framework\Exception('msg_exists_email_address'); - } - - // Check managed Email Host - if($oMemberModel->isDeniedEmailHost($newEmail)) - { - $config = $oMemberModel->getMemberConfig(); - $emailhost_check = $config->emailhost_check; - - $managed_email_host = lang('managed_email_host'); - $email_hosts = $oMemberModel->getManagedEmailHosts(); - foreach ($email_hosts as $host) - { - $hosts[] = $host->email_host; - } - $message = sprintf($managed_email_host[$emailhost_check], implode(', ',$hosts), 'id@' . implode(', id@', $hosts)); - throw new Rhymix\Framework\Exception($message); - } - - // remove all key by member_srl - $args = new stdClass; - $args->member_srl = $memberInfo->member_srl; - $output = executeQuery('member.deleteAuthMail', $args); - - if(!$output->toBool()) - { - return $output; - } - - // update member info - $args->email_address = $newEmail; - list($args->email_id, $args->email_host) = explode('@', $newEmail); - - $output = executeQuery('member.updateMemberEmailAddress', $args); - if(!$output->toBool()) - { - return $output; - } - - $this->_clearMemberCache($args->member_srl); - - // Call a trigger (after) - $trigger_obj = new stdClass; - $trigger_obj->member_srl = $args->member_srl; - $trigger_obj->email_address = $args->email_address; - $trigger_output = ModuleHandler::triggerCall('member.updateMemberEmailAddress', 'after', $trigger_obj); - - // generate new auth key - $auth_args = new stdClass(); - $auth_args->user_id = $memberInfo->user_id; - $auth_args->member_srl = $memberInfo->member_srl; - $auth_args->new_password = $memberInfo->password; - $auth_args->auth_key = Rhymix\Framework\Security::getRandom(40, 'hex'); - $auth_args->is_register = 'Y'; - - $output = executeQuery('member.insertAuthMail', $auth_args); - if(!$output->toBool()) return $output; - - $memberInfo->email_address = $newEmail; - - // resend auth mail. - $this->_sendAuthMail($auth_args, $memberInfo); - - $msg = sprintf(lang('msg_confirm_mail_sent'), $memberInfo->email_address); - $this->setMessage($msg); - - $returnUrl = getUrl(''); - $this->setRedirectUrl($returnUrl); - } - function _sendAuthMail($auth_args, $member_info) { - $oMemberModel = getModel('member'); - $member_config = $oMemberModel->getMemberConfig(); + $member_config = MemberModel::getMemberConfig(); // Get content of the email to send a member Context::set('auth_args', $auth_args); @@ -1841,9 +2148,9 @@ class memberController extends member $auth_url = getFullUrl('','module','member','act','procMemberAuthAccount','member_srl',$member_info->member_srl, 'auth_key',$auth_args->auth_key); Context::set('auth_url', $auth_url); - $oTemplate = &TemplateHandler::getInstance(); - $content = $oTemplate->compile($tpl_path, 'confirm_member_account_mail'); - + $oTemplate = new Rhymix\Framework\Template($tpl_path, 'confirm_member_account_mail'); + $content = $oTemplate->compile(); + // Send a mail $oMail = new \Rhymix\Framework\Mail(); $oMail->setSubject(lang('msg_confirm_account_title')); @@ -1855,75 +2162,34 @@ class memberController extends member /** * Join a virtual site * - * @return void|Object (void : success, Object : fail) + * @deprecated + * @return void */ function procMemberSiteSignUp() { - $site_module_info = Context::get('site_module_info'); - $logged_info = Context::get('logged_info'); - if(!$site_module_info->site_srl || !Context::get('is_logged') || count($logged_info->group_srl_list) ) throw new Rhymix\Framework\Exceptions\InvalidRequest; - $oMemberModel = getModel('member'); - $columnList = array('site_srl', 'group_srl', 'title'); - $default_group = $oMemberModel->getDefaultGroup($site_module_info->site_srl, $columnList); - $this->addMemberToGroup($logged_info->member_srl, $default_group->group_srl, $site_module_info->site_srl); - $groups[$default_group->group_srl] = $default_group->title; - $logged_info->group_list = $groups; } /** * Leave the virtual site * - * @return void|Object (void : success, Object : fail) + * @deprecated + * @return void */ function procMemberSiteLeave() { - $site_module_info = Context::get('site_module_info'); - $logged_info = Context::get('logged_info'); - if(!$site_module_info->site_srl || !Context::get('is_logged') || count($logged_info->group_srl_list) ) throw new Rhymix\Framework\Exceptions\InvalidRequest; - $args = new stdClass; - $args->site_srl= $site_module_info->site_srl; - $args->member_srl = $logged_info->member_srl; - $output = executeQuery('member.deleteMembersGroup', $args); - if(!$output->toBool()) return $output; - $this->setMessage('success_deleted'); - $this->_clearMemberCache($args->member_srl, $site_module_info->site_srl); } /** * Save the member configurations * - * @param object $args - * + * @deprecated * @return void */ function setMemberConfig($args) { - if(!$args->skin) $args->skin = "default"; - if(!$args->colorset) $args->colorset = "white"; - if(!$args->editor_skin) $args->editor_skin= "ckeditor"; - if(!$args->editor_colorset) $args->editor_colorset = "moono-lisa"; - if($args->enable_join!='Y') $args->enable_join = 'N'; - $args->enable_openid= 'N'; - if($args->profile_image !='Y') $args->profile_image = 'N'; - if($args->image_name!='Y') $args->image_name = 'N'; - if($args->image_mark!='Y') $args->image_mark = 'N'; - if($args->group_image_mark!='Y') $args->group_image_mark = 'N'; - if(!trim(strip_tags($args->agreement))) $args->agreement = null; - $args->limit_day = (int)$args->limit_day; - - $agreement = trim($args->agreement); - unset($args->agreement); - - $oModuleController = getController('module'); - $output = $oModuleController->insertModuleConfig('member',$args); - if(!$output->toBool()) return $output; - - $agreement_file = _XE_PATH_.'files/member_extra_info/agreement.txt'; - FileHandler::writeFile($agreement_file, $agreement); - - return new BaseObject(); + return getController('module')->updateModuleConfig('member', $args); } /** @@ -1934,17 +2200,17 @@ class memberController extends member * * @return void */ - function putSignature($member_srl, $signature) + public static function putSignature($member_srl, $signature) { if((!$signature = utf8_trim(removeHackTag($signature))) || is_empty_html_content($signature)) { getController('member')->delSignature($member_srl); return; } - + // Editor converter $obj = new stdClass; - $config = getModel('member')->getMemberConfig(); + $config = MemberModel::getMemberConfig(); if($config->signature_html == 'N') { $obj->converter = 'text'; @@ -1952,11 +2218,13 @@ class memberController extends member $obj->content = $signature; $obj->editor_skin = $config->signature_editor_skin; $signature = getModel('editor')->converter($obj); - - $filename = sprintf('files/member_extra_info/signature/%s%d.signature.php', getNumberingPath($member_srl), $member_srl); + + $filename = RX_BASEDIR . sprintf('files/member_extra_info/signature/%s%d.signature.php', getNumberingPath($member_srl), $member_srl); $buff = sprintf('%s', $signature); Rhymix\Framework\Storage::write($filename, $buff); - + clearstatcache(true, $filename); + + self::clearMemberCache($member_srl); return $signature; } @@ -1967,34 +2235,98 @@ class memberController extends member * * @return void */ - function delSignature($member_srl) + public static function delSignature($member_srl) { $dirname = RX_BASEDIR . sprintf('files/member_extra_info/signature/%s', getNumberingPath($member_srl)); - Rhymix\Framework\Storage::deleteDirectory($dirname, false); + $filename = sprintf('%s%d.signature.php', $dirname, $member_srl); + Rhymix\Framework\Storage::delete($filename); Rhymix\Framework\Storage::deleteEmptyDirectory($dirname, true); + clearstatcache(true, $filename); + self::clearMemberCache($member_srl); } /** - * Add group_srl to member_srl + * Add member to group * * @param int $member_srl * @param int $group_srl - * @param int $site_srl - * - * @return Object + * @return BaseObject */ - function addMemberToGroup($member_srl, $group_srl, $site_srl=0) + public static function addMemberToGroup($member_srl, $group_srl): BaseObject { + // Return if member already belongs to group $args = new stdClass(); $args->member_srl = $member_srl; $args->group_srl = $group_srl; - if($site_srl) $args->site_srl = $site_srl; + $output = executeQueryArray('member.getMemberGroupMember', $args); + if ($output->data && count($output->data) == 1) + { + return $output; + } - // Add - $output = executeQuery('member.addMemberToGroup',$args); + // Call trigger (before) + $trigger_output = ModuleHandler::triggerCall('member.addMemberToGroup', 'before', $args); + if (!$trigger_output->toBool()) + { + return $trigger_output; + } + + // Delete duplicate records + if ($output->data && count($output->data) > 1) + { + executeQuery('member.deleteMemberGroupMember', $args); + } + + // Add member to group + $output = executeQuery('member.addMemberToGroup', $args); + if (!$output->toBool()) + { + return $output; + } + + // Call trigger (after) ModuleHandler::triggerCall('member.addMemberToGroup', 'after', $args); + self::clearMemberCache($member_srl); - $this->_clearMemberCache($member_srl, $site_srl); + return $output; + } + + /** + * Remove member from group + * + * @param int $member_srl + * @param int $group_srl + * @return BaseObject + */ + public static function removeMemberFromGroup(int $member_srl, int $group_srl): BaseObject + { + // Return if member does not belong to group + $args = new stdClass(); + $args->member_srl = $member_srl; + $args->group_srl = $group_srl; + $output = executeQueryArray('member.getMemberGroupMember', $args); + if ($output->data && count($output->data) < 1) + { + return $output; + } + + // Call trigger (before) + $trigger_output = ModuleHandler::triggerCall('member.removeMemberFromGroup', 'before', $args); + if (!$trigger_output->toBool()) + { + return $trigger_output; + } + + // Remove member from group + $output = executeQuery('member.deleteMemberGroupMember', $args); + if (!$output->toBool()) + { + return $output; + } + + // Call trigger (after) + ModuleHandler::triggerCall('member.removeMemberFromGroup', 'after', $args); + self::clearMemberCache($member_srl); return $output; } @@ -2007,14 +2339,19 @@ class memberController extends member * * @return Object */ - function replaceMemberGroup($args) + public static function replaceMemberGroup($args) { $obj = new stdClass; - $obj->site_srl = $args->site_srl; - $obj->member_srl = implode(',',$args->member_srl); + $obj->member_srl = $args->member_srl; $output = executeQueryArray('member.getMembersGroup', $obj); - if($output->data) foreach($output->data as $key => $val) $date[$val->member_srl] = $val->regdate; + if($output->data) + { + foreach($output->data as $key => $val) + { + $date[$val->member_srl] = $val->regdate; + } + } $output = executeQuery('member.deleteMembersGroup', $obj); if(!$output->toBool()) return $output; @@ -2029,12 +2366,11 @@ class memberController extends member $obj = new stdClass; $obj->member_srl = $val; $obj->group_srl = $args->group_srl; - $obj->site_srl = $args->site_srl; $obj->regdate = $date[$obj->member_srl]; $output = executeQuery('member.addMemberToGroup', $obj); if(!$output->toBool()) return $output; - $this->_clearMemberCache($obj->member_srl, $args->site_srl); + self::clearMemberCache($obj->member_srl); } return new BaseObject(); @@ -2059,10 +2395,11 @@ class memberController extends member { return false; } - + // Fetch autologin information from DB. $args = new stdClass; $args->autologin_key = $autologin_key; + $args->page = 0; $output = executeQuery('member.getAutologin', $args); if (!$output->toBool() || !$output->data) { @@ -2072,34 +2409,63 @@ class memberController extends member { $output->data = array_first($output->data); } - + if (!$output->data || !$output->data->member_srl) + { + return false; + } + // Hash the security key. - $valid_security_keys = array(base64_encode(hash_hmac('sha256', $security_key, $autologin_key, true))); - + $hashed_security_key = base64_encode(hash_hmac('sha256', $security_key, $autologin_key, true)); + // Check the security key. - if (!in_array($output->data->security_key, $valid_security_keys) || !$output->data->member_srl) + if ($hashed_security_key !== $output->data->security_key && $hashed_security_key !== $output->data->previous_key ?? '') { $args = new stdClass; $args->autologin_key = $autologin_key; executeQuery('member.deleteAutologin', $args); return false; } - - // Update the security key. - $new_security_key = Rhymix\Framework\Security::getRandom(24, 'alnum'); - $args = new stdClass; - $args->autologin_key = $autologin_key; - $args->security_key = base64_encode(hash_hmac('sha256', $new_security_key, $autologin_key, true)); - $update_output = executeQuery('member.updateAutologin', $args); - if ($update_output->toBool()) + + // If the current security key matches, generate a new key. + // If the previous key matches, don't update until the client has the current key. + // Resending the current key in this case will be handled by the Session class. + if ($hashed_security_key === $output->data->security_key && config('session.autologin_refresh') !== false) { - Rhymix\Framework\Session::setAutologinKeys($autologin_key, $new_security_key); + $new_security_key = Rhymix\Framework\Security::getRandom(24, 'alnum'); + $new_hash = base64_encode(hash_hmac('sha256', $new_security_key, $autologin_key, true)); + + $args = new stdClass; + $args->autologin_key = $autologin_key; + $args->security_key = $new_hash; + $args->previous_key = $output->data->security_key; + $args->user_agent = json_encode(Rhymix\Framework\UA::getBrowserInfo()); + $update_output = executeQuery('member.updateAutologin', $args); + if ($update_output->toBool()) + { + Rhymix\Framework\Session::setAutologinKeys($autologin_key, $new_security_key); + } } - + else + { + $args = new stdClass; + $args->autologin_key = $autologin_key; + $args->user_agent = json_encode(Rhymix\Framework\UA::getBrowserInfo()); + $update_output = executeQuery('member.updateAutologin', $args); + if ($update_output->toBool()) + { + Rhymix\Framework\Session::setAutologinKeys($autologin_key, $security_key); + } + } + // Update the last login time. executeQuery('member.updateLastLogin', (object)['member_srl' => $output->data->member_srl]); - $this->_clearMemberCache($output->data->member_srl); - + self::clearMemberCache($output->data->member_srl); + + // Call a trigger after validate security key (after) + $trigger_obj = new stdClass(); + $trigger_obj->member_srl = $output->data->member_srl; + ModuleHandler::triggerCall('member.doAutoLogin', 'after', $trigger_obj); + // Return the member_srl. return intval($output->data->member_srl); } @@ -2116,36 +2482,97 @@ class memberController extends member function doLogin($user_id, $password = '', $keep_signed = false) { $user_id = strtolower($user_id); - if(!$user_id) return new BaseObject(-1, 'null_user_id'); + if (!$user_id) + { + return new BaseObject(-1, 'null_user_id'); + } + // Call a trigger before log-in (before) $trigger_obj = new stdClass(); $trigger_obj->user_id = $user_id; $trigger_obj->password = $password; $trigger_output = ModuleHandler::triggerCall('member.doLogin', 'before', $trigger_obj); if(!$trigger_output->toBool()) return $trigger_output; - // Create a member model object - $oMemberModel = getModel('member'); // check IP access count. - $config = $oMemberModel->getMemberConfig(); + $config = MemberModel::getMemberConfig(); $args = new stdClass(); - $args->ipaddress = $_SERVER['REMOTE_ADDR']; + $args->ipaddress = \RX_CLIENT_IP; + $used_identifier = null; // check identifier - if($config->identifier == 'email_address' || strpos($user_id, '@') !== false) + if((!$config->identifiers || in_array('email_address', $config->identifiers)) && strpos($user_id, '@') !== false) { - // Get user_id information - $member_info = $oMemberModel->getMemberInfoByEmailAddress($user_id); - // Set an invalid user if no value returned - if(!$user_id || strtolower($member_info->email_address) != strtolower($user_id)) return $this->recordLoginError(-1, 'invalid_email_address'); + $member_info = MemberModel::getMemberInfoByEmailAddress($user_id); + $used_identifier = 'email_address'; + if(!$member_info || strtolower($member_info->email_address) !== strtolower($user_id)) + { + return $this->recordLoginError(-1, 'invalid_email_address'); + } } + elseif($config->identifiers && in_array('phone_number', $config->identifiers) && strpos($user_id, '@') === false && !preg_match('/^[a-z]/i', $user_id)) + { + if(preg_match('/^\+([0-9-]+)\.([0-9.-]+)$/', $user_id, $matches)) + { + $user_id = $matches[2]; + $phone_country = $matches[1]; + if($config->phone_number_hide_country === 'Y') + { + $phone_country = $config->phone_number_default_country; + } + } + elseif($config->phone_number_default_country) + { + $phone_country = $config->phone_number_default_country; + } + else + { + return $this->recordLoginError(-1, 'invalid_user_id'); + } + + if($phone_country && !preg_match('/^[A-Z]{3}$/', $phone_country)) + { + $phone_country = Rhymix\Framework\i18n::getCountryCodeByCallingCode($phone_country); + } + + $numbers_only = preg_replace('/[^0-9]/', '', $user_id); + if (!$numbers_only) + { + return $this->recordLoginError(-1, 'null_user_id'); + } + + $member_info = MemberModel::getMemberInfoByPhoneNumber($numbers_only, $phone_country); + $used_identifier = 'phone_number'; + if(!$member_info || preg_replace('/[^0-9]/', '', $member_info->phone_number) !== $numbers_only) + { + if(in_array('user_id', $config->identifiers)) + { + $member_info = MemberModel::getMemberInfoByUserID($user_id); + $used_identifier = 'user_id'; + if(!$member_info || strtolower($member_info->user_id) !== strtolower($user_id)) + { + return $this->recordLoginError(-1, 'invalid_user_id'); + } + } + else + { + return $this->recordLoginError(-1, 'invalid_user_id'); + } + } + } + elseif(!$config->identifiers || in_array('user_id', $config->identifiers)) + { + $member_info = MemberModel::getMemberInfoByUserID($user_id); + $used_identifier = 'user_id'; + if(!$member_info || strtolower($member_info->user_id) !== strtolower($user_id)) + { + return $this->recordLoginError(-1, 'invalid_user_id'); + } + } else { - // Get user_id information - $member_info = $oMemberModel->getMemberInfoByUserID($user_id); - // Set an invalid user if no value returned - if(!$user_id || strtolower($member_info->user_id) != strtolower($user_id)) return $this->recordLoginError(-1, 'invalid_user_id'); + return $this->recordLoginError(-1, 'invalid_user_id'); } $output = executeQuery('member.getLoginCountByIp', $args); @@ -2166,40 +2593,39 @@ class memberController extends member } else { - $args->ipaddress = $_SERVER['REMOTE_ADDR']; + $args->ipaddress = \RX_CLIENT_IP; $output = executeQuery('member.deleteLoginCountByIp', $args); } } // Password Check - if($password && !$oMemberModel->isValidPassword($member_info->password, $password, $member_info->member_srl)) + if($password && !MemberModel::isValidPassword($member_info->password, $password, $member_info->member_srl)) { - return $this->recordMemberLoginError(-1, 'invalid_password', $member_info); + $msg = ($used_identifier === 'email_address') ? 'invalid_email_address' : 'invalid_user_id'; + return $this->recordMemberLoginError(-1, $msg, $member_info); } // If denied == 'Y', notify - if($member_info->denied == 'Y') + if($member_info->denied === 'Y') { - $args->member_srl = $member_info->member_srl; - $output = executeQuery('member.chkAuthMail', $args); - if ($output->toBool() && $output->data->count != '0') + if ($member_info->status === 'UNAUTHED') { - $_SESSION['auth_member_srl'] = $member_info->member_srl; - $redirectUrl = getUrl('', 'act', 'dispMemberResendAuthMail'); - return $this->setRedirectUrl($redirectUrl, new BaseObject(-1,'msg_user_not_confirmed')); + return new BaseObject(-1, sprintf(lang('msg_user_not_confirmed'), $member_info->email_address)); + } + else + { + $refused_reason = $member_info->refused_reason ? ("\n" . lang('refused_reason') . ': ' . $member_info->refused_reason) : ''; + return new BaseObject(-1, lang('msg_user_denied') . $refused_reason); } - - $refused_reason = $member_info->refused_reason ? ('
                ' . lang('refused_reason') . ': ' . $member_info->refused_reason) : ''; - return new BaseObject(-1, lang('msg_user_denied') . $refused_reason); } - + // Notify if user is limited if($member_info->limit_date && substr($member_info->limit_date,0,8) >= date("Ymd")) { - $limited_reason = $member_info->limited_reason ? ('
                ' . lang('refused_reason') . ': ' . $member_info->limited_reason) : ''; + $limited_reason = $member_info->limited_reason ? ("\n" . lang('refused_reason') . ': ' . $member_info->limited_reason) : ''; return new BaseObject(-9, sprintf(lang('msg_user_limited'), zdate($member_info->limit_date,"Y-m-d")) . $limited_reason); } - + // Do not allow login as admin if not in allowed IP list if($member_info->is_admin === 'Y' && $this->act === 'procMemberLogin') { @@ -2209,16 +2635,14 @@ class memberController extends member return new BaseObject(-1, 'msg_admin_ip_not_allowed'); } } - + // Update the latest login time $args->member_srl = $member_info->member_srl; $output = executeQuery('member.updateLastLogin', $args); - - $site_module_info = Context::get('site_module_info'); - $this->_clearMemberCache($args->member_srl, $site_module_info->site_srl); + self::clearMemberCache($args->member_srl); // Check if there is recoding table. - $oDB = &DB::getInstance(); + $oDB = DB::getInstance(); if($oDB->isTableExists('member_count_history') && $config->enable_login_fail_report != 'N') { // check if there is login fail records. @@ -2239,7 +2663,7 @@ class memberController extends member //send message $oCommunicationController = getController('communication'); - $oCommunicationController->sendMessage($args->member_srl, $args->member_srl, $title, $content, true); + $oCommunicationController->sendMessage($args->member_srl, $args->member_srl, $title, $content, true, null, false); if($member_info->email_address && $member_info->allow_mailing == 'Y') { @@ -2255,10 +2679,7 @@ class memberController extends member } } } - - // Call a trigger after successfully log-in (after) - ModuleHandler::triggerCall('member.doLogin', 'after', $member_info); - + // When user checked to use auto-login if($keep_signed) { @@ -2275,8 +2696,19 @@ class memberController extends member } } + // Log in! Rhymix\Framework\Session::login($member_info->member_srl); $this->setSessionInfo(); + + // Log out all other sessions if so configured. + if ($config->login_invalidate_other_sessions === 'Y') + { + Rhymix\Framework\Session::destroyOtherSessions($member_info->member_srl); + } + + // Call a trigger after successfully log-in (after) + ModuleHandler::triggerCall('member.doLogin', 'after', $member_info); + return $output; } @@ -2297,7 +2729,7 @@ class memberController extends member Context::set('logged_info', $member_info); // Only the menu configuration of the user (such as an add-on to the menu can be changed) - $config = getModel('member')->getMemberConfig(); + $config = MemberModel::getMemberConfig(); $this->addMemberMenu( 'dispMemberInfo', 'cmd_view_member_info'); if ($config->features['scrapped_documents'] !== false) { @@ -2329,24 +2761,34 @@ class memberController extends member * Logged method for providing a personalized menu * Login information is used in the output widget, or personalized page */ - function addMemberMenu($act, $str) + public static function addMemberMenu($act, $str) { $logged_info = Context::get('logged_info'); - - if(!is_object($logged_info)) + if(is_object($logged_info)) { - return; + $logged_info->menu_list[$act] = $str; + Context::set('logged_info', $logged_info); + } + } + + /** + * Replace lang codes in member menu + * + * @param $logged_info + * @return void + */ + public static function replaceLangForMemberMenu(&$logged_info) + { + if(is_object($logged_info) && !empty($logged_info->menu_list)) + { + $logged_info->menu_list = array_map('lang', $logged_info->menu_list); } - - $logged_info->menu_list[$act] = lang($str); - - Context::set('logged_info', $logged_info); } /** * Nickname and click Log In to add a pop-up menu that appears when the method */ - function addMemberPopupMenu($url, $str, $icon = '', $target = 'self') + function addMemberPopupMenu($url, $str, $icon = '', $target = '_blank', $class = '') { $member_popup_menu_list = Context::get('member_popup_menu_list'); if(!is_array($member_popup_menu_list)) $member_popup_menu_list = array(); @@ -2354,7 +2796,8 @@ class memberController extends member $obj = new stdClass; $obj->url = $url; $obj->str = $str; - $obj->icon = $icon; + $obj->class = $class; + $obj->icon = $icon ?: null; $obj->target = $target; $member_popup_menu_list[] = $obj; @@ -2370,8 +2813,7 @@ class memberController extends member $output = ModuleHandler::triggerCall('member.insertMember', 'before', $args); if(!$output->toBool()) return $output; // Terms and Conditions portion of the information set up by members reaffirmed - $oMemberModel = getModel('member'); - $config = $oMemberModel->getMemberConfig(); + $config = MemberModel::getMemberConfig(); $logged_info = Context::get('logged_info'); // limit_date format is YYYYMMDD @@ -2393,10 +2835,15 @@ class memberController extends member $args->member_srl = getNextSequence(); $args->list_order = -1 * $args->member_srl; - // Execute insert or update depending on the value of member_srl - if(!$args->user_id) $args->user_id = 't'.$args->member_srl; - // Enter the user's identity changed to lowercase - else $args->user_id = strtolower($args->user_id); + // Set user_id if empty. Otherwise convert to lowercase. + if (trim($args->user_id ?? '') === '') + { + $args->user_id = 't' . $args->member_srl; + } + else + { + $args->user_id = strtolower($args->user_id); + } if(!$args->user_name) $args->user_name = $args->member_srl; if(!$args->nick_name) $args->nick_name = $args->member_srl; @@ -2426,10 +2873,11 @@ class memberController extends member if($args->blog && !preg_match("/^[a-z]+:\/\//i",$args->blog)) $args->blog = 'http://'.$args->blog; - $extend_form_list = $oMemberModel->getJoinFormlist(); + $extend_form_list = MemberModel::getJoinFormlist(); $security = new Security($extend_form_list); - $security->encodeHTML('..column_title', '..description', '..default_value.'); - if($config->signupForm) { + $security->encodeHTML('..column_title', '..description', '..default_value', '..options.'); + if($config->signupForm) + { foreach($config->signupForm as $no => $formInfo) { if(!$formInfo->isUse) continue; @@ -2467,36 +2915,33 @@ class memberController extends member } } } - - // Create a model object - $oMemberModel = getModel('member'); - + // Check password strength if($args->password && !$password_is_hashed) { - if(!$oMemberModel->checkPasswordStrength($args->password, $config->password_strength)) + if(!MemberModel::checkPasswordStrength($args->password, $config->password_strength)) { $message = lang('about_password_strength'); return new BaseObject(-1, $message[$config->password_strength]); } - $args->password = $oMemberModel->hashPassword($args->password); + $args->password = MemberModel::hashPassword($args->password); } - + // Check if ID is prohibited - if($logged_info->is_admin !== 'Y' && $oMemberModel->isDeniedID($args->user_id)) + if($logged_info->is_admin !== 'Y' && MemberModel::isDeniedID($args->user_id)) { return new BaseObject(-1, 'denied_user_id'); } // Check if ID is duplicate - $member_srl = $oMemberModel->getMemberSrlByUserID($args->user_id); + $member_srl = MemberModel::getMemberSrlByUserID($args->user_id); if($member_srl) { return new BaseObject(-1, 'msg_exists_user_id'); } // Check if nickname is prohibited - if($logged_info->is_admin !== 'Y' && $oMemberModel->isDeniedNickName($args->nick_name)) + if($logged_info->is_admin !== 'Y' && MemberModel::isDeniedNickName($args->nick_name)) { return new BaseObject(-1, 'denied_nick_name'); } @@ -2504,7 +2949,7 @@ class memberController extends member // Check if nickname is duplicate if($config->allow_duplicate_nickname !== 'Y') { - $member_srl = $oMemberModel->getMemberSrlByNickName($args->nick_name); + $member_srl = MemberModel::getMemberSrlByNickName($args->nick_name); if($member_srl) { return new BaseObject(-1, 'msg_exists_nick_name'); @@ -2512,12 +2957,12 @@ class memberController extends member } // Check managed Email Host - if($logged_info->is_admin !== 'Y' && $oMemberModel->isDeniedEmailHost($args->email_address)) + if($logged_info->is_admin !== 'Y' && MemberModel::isDeniedEmailHost($args->email_address)) { $emailhost_check = $config->emailhost_check; $managed_email_host = lang('managed_email_host'); - $email_hosts = $oMemberModel->getManagedEmailHosts(); + $email_hosts = MemberModel::getManagedEmailHosts(); foreach ($email_hosts as $host) { $hosts[] = $host->email_host; @@ -2526,20 +2971,62 @@ class memberController extends member return new BaseObject(-1, $message); } + // Format phone number + if (strval($args->phone_number) !== '') + { + $args->phone_country = trim(preg_replace('/[^A-Z]/', '', $args->phone_country), '-'); + $args->phone_number = preg_replace('/[^0-9]/', '', $args->phone_number); + $args->phone_type = ''; + if ($config->phone_number_hide_country === 'Y' || (!$args->phone_country && $config->phone_number_default_country)) + { + $args->phone_country = $config->phone_number_default_country; + } + if ($args->phone_country && !preg_match('/^[A-Z]{3}$/', $args->phone_country)) + { + $args->phone_country = Rhymix\Framework\i18n::getCountryCodeByCallingCode($args->phone_country); + } + if ($args->phone_country === 'KOR' && !Rhymix\Framework\Korea::isValidPhoneNumber($args->phone_number)) + { + return new BaseObject(-1, 'msg_invalid_phone_number'); + } + } + else + { + $args->phone_country = ''; + $args->phone_number = ''; + $args->phone_type = ''; + } + // Check if email address is duplicate - $member_srl = $oMemberModel->getMemberSrlByEmailAddress($args->email_address); + $member_srl = MemberModel::getMemberSrlByEmailAddress($args->email_address); if($member_srl) { return new BaseObject(-1, 'msg_exists_email_address'); } + // Check if phone number is duplicate + if ($config->phone_number_allow_duplicate !== 'Y' && $args->phone_number) + { + if (!$config->special_phone_number || $config->special_phone_number !== preg_replace('/[^0-9]/', '', $args->phone_number)) + { + $member_srl = MemberModel::getMemberSrlByPhoneNumber($args->phone_number, $args->phone_country); + if($member_srl) + { + return new BaseObject(-1, 'msg_exists_phone_number'); + } + } + } + + // Set status + if (!isset($args->status)) + { + $args->status = ($args->denied === 'Y') ? 'UNAUTHED' : 'APPROVED'; + } + // Insert data into the DB $args->list_order = -1 * $args->member_srl; - if(!$args->user_id) $args->user_id = 't'.$args->member_srl; - if(!$args->user_name) $args->user_name = $args->member_srl; - - $oDB = &DB::getInstance(); + $oDB = DB::getInstance(); $oDB->begin(); $output = executeQuery('member.insertMember', $args); @@ -2554,25 +3041,24 @@ class memberController extends member // If no value is entered the default group, the value of group registration if(!$args->group_srl_list) { - $columnList = array('site_srl', 'group_srl'); - $default_group = $oMemberModel->getDefaultGroup(0, $columnList); + $default_group = MemberModel::getDefaultGroup(0); if($default_group) { // Add to the default group - $output = $this->addMemberToGroup($args->member_srl,$default_group->group_srl); + $output = $this->addMemberToGroup($args->member_srl, $default_group->group_srl); if(!$output->toBool()) { $oDB->rollback(); return $output; } } - // If the value is the value of the group entered the group registration } + // If the value is the value of the group entered the group registration else { - for($i=0;$iaddMemberToGroup($args->member_srl,$group_srl_list[$i]); + $output = $this->addMemberToGroup($args->member_srl, $group_srl); if(!$output->toBool()) { @@ -2591,6 +3077,7 @@ class memberController extends member $auth_args->member_srl = $args->member_srl; $auth_args->new_password = $args->password; $auth_args->auth_key = Rhymix\Framework\Security::getRandom(40, 'hex'); + $args->auth_type = 'signup'; $auth_args->is_register = 'Y'; $output = executeQuery('member.insertAuthMail', $auth_args); @@ -2601,10 +3088,13 @@ class memberController extends member } $this->_sendAuthMail($auth_args, $args); } - + ModuleHandler::triggerCall('member.insertMember', 'after', $args); - $oDB->commit(true); + $oDB->commit(); + + // Remove from cache + self::clearMemberCache($args->member_srl); $output->add('member_srl', $args->member_srl); return $output; @@ -2613,22 +3103,21 @@ class memberController extends member /** * Modify member information * - * @param bool $is_admin , modified 2013-11-22 + * @param bool $deprecated_allow_update_other */ - function updateMember($args, $is_admin = FALSE) + function updateMember($args, $deprecated_allow_update_other = FALSE) { // Call a trigger (before) $output = ModuleHandler::triggerCall('member.updateMember', 'before', $args); if(!$output->toBool()) return $output; // Create a model object - $oMemberModel = getModel('member'); - $config = $oMemberModel->getMemberConfig(); + $config = MemberModel::getMemberConfig(); $logged_info = Context::get('logged_info'); - + // Get what you want to modify the original information - $orgMemberInfo = $oMemberModel->getMemberInfoByMemberSrl($args->member_srl); - + $orgMemberInfo = MemberModel::getMemberInfoByMemberSrl($args->member_srl); + // Control of essential parameters if($args->allow_mailing!='Y') $args->allow_mailing = 'N'; if($args->allow_message && !in_array($args->allow_message, array('Y','N','F'))) $args->allow_message = 'Y'; @@ -2641,11 +3130,16 @@ class memberController extends member else { unset($args->is_admin); - if($is_admin == false) - unset($args->denied); - if($logged_info->member_srl != $args->member_srl && $is_admin == false) + unset($args->limit_date); + unset($args->description); + if (!$deprecated_allow_update_other) { - return new BaseObject(-1, 'msg_invalid_request'); + unset($args->denied); + unset($args->status); + if ($logged_info->member_srl != $args->member_srl) + { + return new BaseObject(-1, 'msg_invalid_request'); + } } } @@ -2655,6 +3149,7 @@ class memberController extends member $args->nick_name = escape($args->nick_name, false); $args->homepage = escape($args->homepage, false); $args->blog = escape($args->blog, false); + $args->birthday = intval($args->birthday ?? '') ?: ''; if($args->homepage && !preg_match("/^[a-z]+:\/\//is",$args->homepage)) $args->homepage = 'http://'.$args->homepage; if($args->blog && !preg_match("/^[a-z]+:\/\//is",$args->blog)) $args->blog = 'http://'.$args->blog; @@ -2674,10 +3169,11 @@ class memberController extends member } } - $extend_form_list = $oMemberModel->getJoinFormlist(); + $extend_form_list = MemberModel::getJoinFormlist(); $security = new Security($extend_form_list); - $security->encodeHTML('..column_title', '..description', '..default_value.'); - if($config->signupForm){ + $security->encodeHTML('..column_title', '..description', '..default_value', '..options.'); + if($config->signupForm) + { foreach($config->signupForm as $no => $formInfo) { if(!$formInfo->isUse) continue; @@ -2716,13 +3212,39 @@ class memberController extends member } } + // Format phone number + if (strval($args->phone_number) !== '') + { + $args->phone_country = trim(preg_replace('/[^A-Z]/', '', $args->phone_country), '-'); + $args->phone_number = preg_replace('/[^0-9]/', '', $args->phone_number); + $args->phone_type = ''; + if ($config->phone_number_hide_country === 'Y' || (!$args->phone_country && $config->phone_number_default_country)) + { + $args->phone_country = $config->phone_number_default_country; + } + if ($args->phone_country && !preg_match('/^[A-Z]{3}$/', $args->phone_country)) + { + $args->phone_country = Rhymix\Framework\i18n::getCountryCodeByCallingCode($args->phone_country); + } + if ($args->phone_country === 'KOR' && !Rhymix\Framework\Korea::isValidPhoneNumber($args->phone_number)) + { + return new BaseObject(-1, 'msg_invalid_phone_number'); + } + } + else + { + $args->phone_country = ''; + $args->phone_number = ''; + $args->phone_type = ''; + } + // Check managed Email Host - if($logged_info->is_admin !== 'Y' && $logged_info->email_address !== $args->email_address && $oMemberModel->isDeniedEmailHost($args->email_address)) + if($logged_info->is_admin !== 'Y' && $logged_info->email_address !== $args->email_address && MemberModel::isDeniedEmailHost($args->email_address)) { $emailhost_check = $config->emailhost_check; $managed_email_host = lang('managed_email_host'); - $email_hosts = $oMemberModel->getManagedEmailHosts(); + $email_hosts = MemberModel::getManagedEmailHosts(); foreach ($email_hosts as $host) { $hosts[] = $host->email_host; @@ -2732,28 +3254,47 @@ class memberController extends member } // Check if email address or user ID is duplicate - if($config->identifier == 'email_address') + $identifiers = $config->identifiers ?? [$config->identifier]; + if(in_array('email_address', $identifiers)) { - $member_srl = $oMemberModel->getMemberSrlByEmailAddress($args->email_address); + $member_srl = MemberModel::getMemberSrlByEmailAddress($args->email_address); if($member_srl && $args->member_srl != $member_srl) { return new BaseObject(-1, 'msg_exists_email_address'); } - $args->email_address = $orgMemberInfo->email_address; + if($logged_info->is_admin !== 'Y') + { + $args->email_address = $orgMemberInfo->email_address; + } } - else + if(in_array('user_id', $identifiers)) { - $member_srl = $oMemberModel->getMemberSrlByUserID($args->user_id); + $member_srl = MemberModel::getMemberSrlByUserID($args->user_id); if($member_srl && $args->member_srl != $member_srl) { return new BaseObject(-1, 'msg_exists_user_id'); } + if($logged_info->is_admin !== 'Y') + { + $args->user_id = $orgMemberInfo->user_id; + } + } - $args->user_id = $orgMemberInfo->user_id; + // Check if phone number is duplicate + if ($config->phone_number_allow_duplicate !== 'Y' && $args->phone_number) + { + if (!$config->special_phone_number || $config->special_phone_number !== preg_replace('/[^0-9]/', '', $args->phone_number)) + { + $member_srl = MemberModel::getMemberSrlByPhoneNumber($args->phone_number, $args->phone_country); + if ($member_srl && $args->member_srl != $member_srl) + { + return new BaseObject(-1, 'msg_exists_phone_number'); + } + } } // Check if ID is prohibited - if($logged_info->is_admin !== 'Y' && $args->user_id && $oMemberModel->isDeniedID($args->user_id)) + if($logged_info->is_admin !== 'Y' && $args->user_id && MemberModel::isDeniedID($args->user_id)) { return new BaseObject(-1, 'denied_user_id'); } @@ -2761,7 +3302,7 @@ class memberController extends member // Check if ID is duplicate if($args->user_id) { - $member_srl = $oMemberModel->getMemberSrlByUserID($args->user_id); + $member_srl = MemberModel::getMemberSrlByUserID($args->user_id); if($member_srl && $args->member_srl != $member_srl) { return new BaseObject(-1, 'msg_exists_user_id'); @@ -2769,7 +3310,7 @@ class memberController extends member } // Check if nickname is prohibited - if($logged_info->is_admin !== 'Y' && $args->nick_name && $oMemberModel->isDeniedNickName($args->nick_name)) + if($logged_info->is_admin !== 'Y' && $args->nick_name && MemberModel::isDeniedNickName($args->nick_name)) { return new BaseObject(-1, 'denied_nick_name'); } @@ -2777,7 +3318,7 @@ class memberController extends member // Check if nickname is duplicate if($config->allow_duplicate_nickname !== 'Y') { - $member_srl = $oMemberModel->getMemberSrlByNickName($args->nick_name); + $member_srl = MemberModel::getMemberSrlByNickName($args->nick_name); if($member_srl && $args->member_srl != $member_srl) { return new BaseObject(-1, 'msg_exists_nick_name'); @@ -2786,18 +3327,15 @@ class memberController extends member list($args->email_id, $args->email_host) = explode('@', $args->email_address); - $oDB = &DB::getInstance(); - $oDB->begin(); - // Check password strength if($args->password) { - if(!$oMemberModel->checkPasswordStrength($args->password, $config->password_strength)) + if(!MemberModel::checkPasswordStrength($args->password, $config->password_strength)) { $message = lang('about_password_strength'); return new BaseObject(-1, $message[$config->password_strength]); } - $args->password = $oMemberModel->hashPassword($args->password); + $args->password = MemberModel::hashPassword($args->password); } else { @@ -2807,8 +3345,13 @@ class memberController extends member if(!$args->user_name) $args->user_name = $orgMemberInfo->user_name; if(!$args->user_id) $args->user_id = $orgMemberInfo->user_id; if(!$args->nick_name) $args->nick_name = $orgMemberInfo->nick_name; - if(!$args->description) $args->description = $orgMemberInfo->description; - if(!$args->birthday) $args->birthday = $orgMemberInfo->birthday; + if($logged_info->is_admin !== 'Y') + { + $args->description = $orgMemberInfo->description; + } + + $oDB = DB::getInstance(); + $oDB->begin(); $output = executeQuery('member.updateMember', $args); @@ -2826,7 +3369,7 @@ class memberController extends member $log_args->before_nick_name = $orgMemberInfo->nick_name; $log_args->after_nick_name = $args->nick_name; $log_args->user_id = $args->user_id; - $log_output = executeQuery('member.insertMemberModifyNickName', $log_args); + executeQuery('member.insertMemberModifyNickName', $log_args); } } @@ -2837,7 +3380,6 @@ class memberController extends member // If the group information, group information changes if(count($group_srl_list) > 0) { - $args->site_srl = 0; // One of its members to delete all the group $output = executeQuery('member.deleteMemberGroupMember', $args); if(!$output->toBool()) @@ -2846,9 +3388,9 @@ class memberController extends member return $output; } // Enter one of the loop a - for($i=0;$iaddMemberToGroup($args->member_srl,$group_srl_list[$i]); + $output = $this->addMemberToGroup($args->member_srl, $group_srl); if(!$output->toBool()) { $oDB->rollback(); @@ -2860,15 +3402,14 @@ class memberController extends member $this->_updatePointByGroup($orgMemberInfo->member_srl, $group_srl_list); } } - + // Call a trigger (after) ModuleHandler::triggerCall('member.updateMember', 'after', $args); $oDB->commit(); // Remove from cache - unset($GLOBALS['__member_info__'][$args->member_srl]); - $this->_clearMemberCache($args->member_srl, $args->site_srl); + self::clearMemberCache($args->member_srl); $output->add('member_srl', $args->member_srl); return $output; @@ -2886,13 +3427,13 @@ class memberController extends member { return $output; } - + $extra_vars = $output->data->extra_vars ? unserialize($output->data->extra_vars) : new stdClass; foreach ($values as $key => $val) { $extra_vars->{$key} = $val; } - + $args = new stdClass(); $args->member_srl = $member_srl; $args->extra_vars = serialize($extra_vars); @@ -2901,9 +3442,8 @@ class memberController extends member { return $output; } - - unset($GLOBALS['__member_info__'][$member_srl]); - $this->_clearMemberCache($member_srl); + + self::clearMemberCache($member_srl); return $output; } @@ -2916,16 +3456,15 @@ class memberController extends member if($args->password) { // check password strength - $oMemberModel = getModel('member'); - $config = $oMemberModel->getMemberConfig(); + $config = MemberModel::getMemberConfig(); - if(!$oMemberModel->checkPasswordStrength($args->password, $config->password_strength)) + if(!MemberModel::checkPasswordStrength($args->password, $config->password_strength)) { $message = lang('about_password_strength'); return new BaseObject(-1, $message[$config->password_strength]); } - $args->password = $oMemberModel->hashPassword($args->password); + $args->password = MemberModel::hashPassword($args->password); } else if($args->hashed_password) { @@ -2938,8 +3477,7 @@ class memberController extends member $result = executeQuery('member.updateChangePasswordDate', $args); } - unset($GLOBALS['__member_info__'][$args->member_srl]); - $this->_clearMemberCache($args->member_srl); + self::clearMemberCache($args->member_srl); return $output; } @@ -2953,21 +3491,30 @@ class memberController extends member $trigger_obj = new stdClass(); $trigger_obj->member_srl = $member_srl; $output = ModuleHandler::triggerCall('member.deleteMember', 'before', $trigger_obj); - if(!$output->toBool()) return $output; - // Create a model object - $oMemberModel = getModel('member'); - // Bringing the user's information - $columnList = array('member_srl', 'is_admin'); - $member_info = $oMemberModel->getMemberInfoByMemberSrl($member_srl, 0, $columnList); - if(!$member_info) return new BaseObject(-1, 'msg_not_exists_member'); - // If managers can not be deleted - if($member_info->is_admin == 'Y') return new BaseObject(-1, 'msg_cannot_delete_admin'); + if (!$output->toBool()) + { + return $output; + } - $oDB = &DB::getInstance(); + // Bringing the user's information + $member_info = MemberModel::getMemberInfoByMemberSrl($member_srl); + if (!$member_info || !$member_info->member_srl) + { + return new BaseObject(-1, 'msg_not_exists_member'); + } + + // If managers can not be deleted + if ($member_info->is_admin == 'Y') + { + return new BaseObject(-1, 'msg_cannot_delete_admin'); + } + + $oDB = DB::getInstance(); $oDB->begin(); $args = new stdClass(); $args->member_srl = $member_srl; + // Delete the entries in member_auth_mail $output = executeQuery('member.deleteAuthMail', $args); if(!$output->toBool()) @@ -2975,15 +3522,23 @@ class memberController extends member $oDB->rollback(); return $output; } - executeQuery('member.deleteMemberModifyNickNameLog', $args); - // TODO: If the table is not an upgrade may fail. - /* - if(!$output->toBool()) { - $oDB->rollback(); - return $output; - } - */ + // Delete agreement info + $output = executeQuery('member.deleteAgreed', $args); + if(!$output->toBool()) + { + $oDB->rollback(); + return $output; + } + + // Delete nickname log + $output = executeQuery('member.deleteMemberModifyNickNameLog', $args); + if(!$output->toBool()) + { + $oDB->rollback(); + return $output; + } + // Delete the entries in member_group_member $output = executeQuery('member.deleteMemberGroupMember', $args); if(!$output->toBool()) @@ -2991,25 +3546,27 @@ class memberController extends member $oDB->rollback(); return $output; } - // member removed from the table + + // Delete main member info $output = executeQuery('member.deleteMember', $args); if(!$output->toBool()) { $oDB->rollback(); return $output; } + // Call a trigger (after) ModuleHandler::triggerCall('member.deleteMember', 'after', $trigger_obj); $oDB->commit(); - + // Name, image, image, mark, sign, delete $this->procMemberDeleteImageName($member_srl); $this->procMemberDeleteImageMark($member_srl); $this->procMemberDeleteProfileImage($member_srl); $this->delSignature($member_srl); - $this->_clearMemberCache($member_srl); - + self::clearMemberCache($member_srl); + // Delete all remaining extra info $dirs = Rhymix\Framework\Storage::readDirectory(RX_BASEDIR . 'files/member_extra_info', true, true, false); foreach ($dirs as $dir) @@ -3032,8 +3589,7 @@ class memberController extends member function _updatePointByGroup($memberSrl, $groupSrlList) { - $oModuleModel = getModel('module'); - $pointModuleConfig = $oModuleModel->getModuleConfig('point'); + $pointModuleConfig = ModuleModel::getModuleConfig('point'); $pointGroup = $pointModuleConfig->point_group; $levelGroup = array(); @@ -3049,8 +3605,7 @@ class memberController extends member if($maxLevel > 0) { - $oPointModel = getModel('point'); - $originPoint = $oPointModel->getPoint($memberSrl); + $originPoint = PointModel::getPoint($memberSrl); if($pointModuleConfig->level_step[$maxLevel] > $originPoint) { @@ -3064,20 +3619,26 @@ class memberController extends member { if(!Context::get('is_logged')) throw new Rhymix\Framework\Exceptions\MustLogin; + if (!isset($_SESSION['rechecked_password_step']) || $_SESSION['rechecked_password_step'] !== 'INPUT_DATA') + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + $member_info = Context::get('logged_info'); $newEmail = Context::get('email_address'); - - if(!$newEmail) throw new Rhymix\Framework\Exceptions\InvalidRequest; - - $oMemberModel = getModel('member'); - // Check managed Email Host - if($oMemberModel->isDeniedEmailHost($newEmail)) + if(!$newEmail) { - $config = $oMemberModel->getMemberConfig(); + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + + // Check managed Email Host + if(MemberModel::isDeniedEmailHost($newEmail)) + { + $config = MemberModel::getMemberConfig(); $emailhost_check = $config->emailhost_check; $managed_email_host = lang('managed_email_host'); - $email_hosts = $oMemberModel->getManagedEmailHosts(); + $email_hosts = MemberModel::getManagedEmailHosts(); foreach ($email_hosts as $host) { $hosts[] = $host->email_host; @@ -3087,51 +3648,40 @@ class memberController extends member } // Check if the e-mail address is already registered - $member_srl = $oMemberModel->getMemberSrlByEmailAddress($newEmail); + $member_srl = MemberModel::getMemberSrlByEmailAddress($newEmail); if($member_srl) throw new Rhymix\Framework\Exception('msg_exists_email_address'); - if($_SESSION['rechecked_password_step'] != 'INPUT_DATA') - { - throw new Rhymix\Framework\Exceptions\InvalidRequest; - } - unset($_SESSION['rechecked_password_step']); - $auth_args = new stdClass(); $auth_args->user_id = $newEmail; $auth_args->member_srl = $member_info->member_srl; $auth_args->auth_key = Rhymix\Framework\Security::getRandom(40, 'hex'); $auth_args->new_password = 'XE_change_emaill_address'; + $auth_args->auth_type = 'change_email'; + $auth_args->is_register = 'N'; - $oDB = &DB::getInstance(); - $oDB->begin(); $output = executeQuery('member.insertAuthMail', $auth_args); if(!$output->toBool()) { - $oDB->rollback(); return $output; } - $oModuleModel = getModel('module'); - $member_config = $oModuleModel->getModuleConfig('member'); + $member_config = ModuleModel::getModuleConfig('member'); $tpl_path = sprintf('%sskins/%s', $this->module_path, $member_config->skin); if(!is_dir($tpl_path)) $tpl_path = sprintf('%sskins/%s', $this->module_path, 'default'); - global $lang; - $memberInfo = array(); - $memberInfo[$lang->email_address] = $member_info->email_address; - $memberInfo[$lang->nick_name] = $member_info->nick_name; + $memberInfo[lang('email_address')] = $member_info->email_address; + $memberInfo[lang('nick_name')] = $member_info->nick_name; Context::set('memberInfo', $memberInfo); - Context::set('newEmail', $newEmail); $auth_url = getFullUrl('','module','member','act','procMemberAuthEmailAddress','member_srl',$member_info->member_srl, 'auth_key',$auth_args->auth_key); Context::set('auth_url', $auth_url); - $oTemplate = &TemplateHandler::getInstance(); - $content = $oTemplate->compile($tpl_path, 'confirm_member_new_email'); + $oTemplate = new Rhymix\Framework\Template($tpl_path, 'confirm_member_new_email'); + $content = $oTemplate->compile(); $oMail = new \Rhymix\Framework\Mail(); $oMail->setSubject(lang('title_modify_email_address')); @@ -3139,6 +3689,8 @@ class memberController extends member $oMail->addTo($newEmail, $member_info->nick_name); $oMail->send(); + unset($_SESSION['rechecked_password_step']); + $msg = sprintf(lang('msg_confirm_mail_sent'), $newEmail); $this->setMessage($msg); @@ -3170,21 +3722,165 @@ class memberController extends member $output = executeQuery('member.updateMemberEmailAddress', $args); if(!$output->toBool()) return $output; - // Remove all values having the member_srl and new_password equal to 'XE_change_emaill_address' from authentication table + // Remove all values having the member_srl and auth_type = change_email executeQuery('member.deleteAuthChangeEmailAddress',$args); - $this->_clearMemberCache($args->member_srl); + self::clearMemberCache($args->member_srl); // Call a trigger (after) $trigger_obj = new stdClass; $trigger_obj->member_srl = $args->member_srl; $trigger_obj->email_address = $args->email_address; $trigger_output = ModuleHandler::triggerCall('member.updateMemberEmailAddress', 'after', $trigger_obj); - + // Redirect to member info page $this->setRedirectUrl(getNotEncodedUrl('', 'act', 'dispMemberInfo')); } + function procMemberSendVerificationSMS() + { + $config = MemberModel::getMemberConfig(); + if ($config->phone_number_verify_by_sms !== 'Y') + { + throw new Rhymix\Framework\Exceptions\FeatureDisabled; + } + + $phone_country = Context::get('phone_country'); + $phone_number = Context::get('phone_number'); + + if ($config->phone_number_default_country && (!$phone_country || $config->phone_number_hide_country === 'Y')) + { + $phone_country = $config->phone_number_default_country; + } + if (preg_match('/[A-Z]{3}/', $phone_country)) + { + $phone_country_calling_code = preg_replace('/[^0-9]/', '', Rhymix\Framework\i18n::getCallingCodeByCountryCode($phone_country)); + if (!$phone_country_calling_code) + { + return new BaseObject(-1, 'msg_invalid_phone_country'); + } + } + else + { + return new BaseObject(-1, 'msg_invalid_phone_country'); + } + + if (!preg_match('/[0-9]{2,}/', $phone_number)) + { + return new BaseObject(-1, 'msg_invalid_phone_number'); + } + if ($phone_country === 'KOR' && !Rhymix\Framework\Korea::isValidPhoneNumber($phone_number ?? '')) + { + return new BaseObject(-1, 'msg_invalid_phone_number'); + } + + $is_special = ($config->special_phone_number && $config->special_phone_number === preg_replace('/[^0-9]/', '', $phone_number)); + + // Check if SMS has already been sent + if (!$is_special) + { + $args = new stdClass; + $args->phone_number = $phone_number; + $args->phone_country = $phone_country; + $args->ipaddress = \RX_CLIENT_IP; + $args->regdate_since = date('YmdHis', time() - ($config->max_auth_sms_count_time ?: 600)); + $output = executeQuery('member.chkAuthSms', $args); + if ($output->data->count >= ($config->max_auth_sms_count ?: 5)) + { + return new BaseObject(-1, 'msg_auth_sms_rate_limited'); + } + } + + // Check if phone number is duplicate + if (!$is_special && $config->phone_number_allow_duplicate !== 'Y') + { + $member_srl = MemberModel::getMemberSrlByPhoneNumber($phone_number, $phone_country); + if($member_srl) + { + return new BaseObject(-1, 'msg_exists_phone_number'); + } + } + + // Generate code and store in session + $code = intval(mt_rand(100000, 999999)); + $_SESSION['verify_by_sms'] = array( + 'country' => $phone_country, + 'number' => $phone_number, + 'code' => $is_special ? intval($config->special_phone_code) : $code, + 'time' => time(), + 'count' => 0, + 'status' => false, + ); + + // Store in DB + $args = new stdClass; + $args->member_srl = $this->user->member_srl ?: 0; + $args->phone_number = $phone_number; + $args->phone_country = $phone_country; + $args->code = $is_special ? intval($config->special_phone_code) : $code; + executeQuery('member.insertAuthSms', $args); + + if ($is_special) + { + return new BaseObject(0, 'verify_by_sms_code_sent'); + } + + $sms = new Rhymix\Framework\SMS; + $sms->addTo($phone_number, $phone_country_calling_code); + $site_title = Context::replaceUserLang(Context::get('site_module_info')->settings->title); + $message = sprintf(lang('member.verify_by_sms_message'), $code); + $content = sprintf('[%s] %s', $site_title, $message); + $sms->setContent($content); + $result = $sms->send(); + if ($result && config('sms.type') !== 'dummy') + { + return new BaseObject(0, 'verify_by_sms_code_sent'); + } + else + { + return new BaseObject(0, 'verify_by_sms_error'); + } + } + + function procMemberConfirmVerificationSMS() + { + $config = MemberModel::getMemberConfig(); + if ($config->phone_number_verify_by_sms !== 'Y') + { + throw new Rhymix\Framework\Exceptions\FeatureDisabled; + } + + $code = Context::get('code'); + if(!preg_match('/^[0-9]{6}$/', $code)) + { + throw new Rhymix\Framework\Exception('verify_by_sms_code_incorrect'); + } + + $code = intval($code); + if(!isset($_SESSION['verify_by_sms'])) + { + throw new Rhymix\Framework\Exception('verify_by_sms_code_incorrect'); + } + if (isset($_SESSION['verify_by_sms']['count']) && $_SESSION['verify_by_sms']['count'] >= 10) + { + unset($_SESSION['verify_by_sms']); + throw new Rhymix\Framework\Exception('verify_by_sms_code_too_many_tries'); + } + if (isset($_SESSION['verify_by_sms']['time']) && $_SESSION['verify_by_sms']['time'] < time() - 600) + { + unset($_SESSION['verify_by_sms']); + throw new Rhymix\Framework\Exception('verify_by_sms_code_expired'); + } + if ($_SESSION['verify_by_sms']['code'] !== $code) + { + $_SESSION['verify_by_sms']['count']++; + throw new Rhymix\Framework\Exception('verify_by_sms_code_incorrect'); + } + + $_SESSION['verify_by_sms']['status'] = true; + return new BaseObject(0, 'verify_by_sms_code_confirmed'); + } + /** * trigger for document.getDocumentMenu. Append to popup menu a button for procMemberSpammerManage() * @@ -3199,14 +3895,13 @@ class memberController extends member $logged_info = Context::get('logged_info'); $document_srl = Context::get('target_srl'); - $oDocumentModel = getModel('document'); $columnList = array('document_srl', 'module_srl', 'member_srl', 'ipaddress'); - $oDocument = $oDocumentModel->getDocument($document_srl, false, false, $columnList); - $member_srl = $oDocument->get('member_srl'); + $oDocument = DocumentModel::getDocument($document_srl, false, false, $columnList); + $member_srl = abs($oDocument->get('member_srl')); $module_srl = $oDocument->get('module_srl'); - if(!$member_srl) return; - if($oDocumentModel->grant->manager != 1 || $member_srl==$logged_info->member_srl) return; + if(!$member_srl || $member_srl == $logged_info->member_srl) return; + if(!ModuleModel::getGrant(ModuleModel::getModuleInfoByModuleSrl($module_srl), $logged_info)->manager) return; $oDocumentController = getController('document'); $url = getUrl('','module','member','act','dispMemberSpammer','member_srl',$member_srl,'module_srl',$module_srl); @@ -3227,20 +3922,38 @@ class memberController extends member $logged_info = Context::get('logged_info'); $comment_srl = Context::get('target_srl'); - $oCommentModel = getModel('comment'); $columnList = array('comment_srl', 'module_srl', 'member_srl', 'ipaddress'); - $oComment = $oCommentModel->getComment($comment_srl, FALSE, $columnList); + $oComment = CommentModel::getComment($comment_srl, FALSE, $columnList); $module_srl = $oComment->get('module_srl'); - $member_srl = $oComment->get('member_srl'); + $member_srl = abs($oComment->get('member_srl')); - if(!$member_srl) return; - if($oCommentModel->grant->manager != 1 || $member_srl==$logged_info->member_srl) return; + if(!$member_srl || $member_srl == $logged_info->member_srl) return; + if(!ModuleModel::getGrant(ModuleModel::getModuleInfoByModuleSrl($module_srl), $logged_info)->manager) return; $oCommentController = getController('comment'); $url = getUrl('','module','member','act','dispMemberSpammer','member_srl',$member_srl,'module_srl',$module_srl); $oCommentController->addCommentPopupMenu($url,'cmd_spammer','','popup'); } + /** + * trigger for document.deleteDocument. Delete to document scrap for deleteDocument trigger. + * @param object $obj + * @return void + */ + function triggerDeleteDocument($obj) + { + // Variables + $args = new stdClass; + $args->document_srl = intval($obj->document_srl); + + // Delete + $output = executeQuery('member.deleteScrapDocumentByDocumentSrl', $args); + if (!$output->toBool()) + { + return $output; + } + } + /** * Spammer manage. Denied user login. And delete or trash all documents. Response Ajax string * @@ -3255,23 +3968,15 @@ class memberController extends member $module_srl = Context::get('module_srl'); $cnt_loop = Context::get('cnt_loop'); $proc_type = Context::get('proc_type'); - $isMoveToTrash = true; - if($proc_type == "delete") - $isMoveToTrash = false; + $isMoveToTrash = ($proc_type !== 'delete') ? true : false; // check grant - $oModuleModel = getModel('module'); $columnList = array('module_srl', 'module'); - $module_info = $oModuleModel->getModuleInfoByModuleSrl($module_srl, $columnList); - $grant = $oModuleModel->getGrant($module_info, $logged_info); + $module_info = ModuleModel::getModuleInfoByModuleSrl($module_srl, $columnList); + $grant = ModuleModel::getGrant($module_info, $logged_info); if(!$grant->manager) throw new Rhymix\Framework\Exceptions\NotPermitted; - $proc_msg = ""; - - $oDocumentModel = getModel('document'); - $oCommentModel = getModel('comment'); - // delete or trash destination // proc member if($cnt_loop == 1) @@ -3281,8 +3986,8 @@ class memberController extends member $this->_spammerDocuments($member_srl, $isMoveToTrash); // get destination count - $cnt_document = $oDocumentModel->getDocumentCountByMemberSrl($member_srl); - $cnt_comment = $oCommentModel->getCommentCountByMemberSrl($member_srl); + $cnt_document = DocumentModel::getDocumentCountByMemberSrl($member_srl); + $cnt_comment = CommentModel::getCommentCountByMemberSrl($member_srl); $total_count = Context::get('total_count'); $remain_count = $cnt_document + $cnt_comment; @@ -3312,35 +4017,35 @@ class memberController extends member * * @return object **/ - private function _spammerMember($member_srl) { + protected function _spammerMember($member_srl) { $logged_info = Context::get('logged_info'); $spam_description = trim( Context::get('spam_description') ); - $oMemberModel = getModel('member'); - $columnList = array('member_srl', 'email_address', 'user_id', 'nick_name', 'description'); - // get member current infomation - $member_info = $oMemberModel->getMemberInfoByMemberSrl($member_srl, 0, $columnList); - - $oDocumentModel = getModel('document'); - $oCommentModel = getModel('comment'); - $cnt_comment = $oCommentModel->getCommentCountByMemberSrl($member_srl); - $cnt_document = $oDocumentModel->getDocumentCountByMemberSrl($member_srl); - $total_count = $cnt_comment + $cnt_document; + // get member current information + $member_info = MemberModel::getMemberInfoByMemberSrl($member_srl); + $cnt_comment = CommentModel::getCommentCountByMemberSrl($member_srl); + $cnt_document = DocumentModel::getDocumentCountByMemberSrl($member_srl); $args = new stdClass(); $args->member_srl = $member_info->member_srl; $args->email_address = $member_info->email_address; $args->user_id = $member_info->user_id; $args->nick_name = $member_info->nick_name; - $args->denied = "Y"; - $args->description = trim( $member_info->description ); - if( $args->description != "" ) $args->description .= "\n"; // add new line - - $args->description .= lang('cmd_spammer') . "[" . date("Y-m-d H:i:s") . " from:" . $logged_info->user_id . " info:" . $spam_description . " docuemnts count:" . $total_count . "]"; + $args->denied = 'Y'; + $args->status = 'DENIED'; + $args->description = trim(vsprintf("%s\n%s [%s %s]\ninfo: %s\ndocuments: %d\ncomments: %d]", [ + trim($member_info->description), + lang('cmd_spammer'), + date("Y-m-d H:i:s"), + $logged_info->nick_name, + $spam_description, + $cnt_document, + $cnt_comment, + ])); $output = $this->updateMember($args, true); - $this->_clearMemberCache($args->member_srl); + self::clearMemberCache($args->member_srl); return $output; } @@ -3353,34 +4058,46 @@ class memberController extends member * * @return object **/ - private function _spammerDocuments($member_srl, $isMoveToTrash) { + protected function _spammerDocuments($member_srl, $isMoveToTrash = true) + { $oDocumentController = getController('document'); - $oDocumentModel = getModel('document'); $oCommentController = getController('comment'); - $oCommentModel = getModel('comment'); // delete count by one request $getContentsCount = 10; // 1. proc comment, 2. proc document - $cnt_comment = $oCommentModel->getCommentCountByMemberSrl($member_srl); - $cnt_document = $oDocumentModel->getDocumentCountByMemberSrl($member_srl); + $cnt_comment = CommentModel::getCommentCountByMemberSrl($member_srl); + $cnt_document = DocumentModel::getDocumentCountByMemberSrl($member_srl); if($cnt_comment > 0) { $columnList = array(); - $commentList = $oCommentModel->getCommentListByMemberSrl($member_srl, $columnList, 0, false, $getContentsCount); + $commentList = CommentModel::getCommentListByMemberSrl($member_srl, $columnList, 0, false, $getContentsCount); if($commentList) { foreach($commentList as $v) { - $oCommentController->deleteComment($v->comment_srl, true, $isMoveToTrash); + if($isMoveToTrash) + { + $oCommentController->moveCommentToTrash($v); + } + else + { + $oCommentController->deleteComment($v->comment_srl, true); + } } } } elseif($cnt_document > 0) { $columnList = array(); - $documentList = $oDocumentModel->getDocumentListByMemberSrl($member_srl, $columnList, 0, false, $getContentsCount); + $documentList = DocumentModel::getDocumentListByMemberSrl($member_srl, $columnList, 0, false, $getContentsCount); if($documentList) { foreach($documentList as $v) { - if($isMoveToTrash) $oDocumentController->moveDocumentToTrash($v); - else $oDocumentController->deleteDocument($v->document_srl); + if($isMoveToTrash) + { + $oDocumentController->moveDocumentToTrash($v); + } + else + { + $oDocumentController->deleteDocument($v->document_srl, true); + } } } } @@ -3388,16 +4105,194 @@ class memberController extends member return array(); } - function _clearMemberCache($member_srl, $site_srl = 0) + /** + * Check required fields on signup or modify info + * + * @param object $config + * @param object $args + * @param string $mode + * @param array $skip + * @return object + */ + protected function _checkSignUpFields($config, $args, $mode = 'insert', $skip = []) + { + $not_required_if_indirect_insert = ['user_id']; + $not_required_in_update = ['password']; + foreach($config->signupForm as $formInfo) + { + if($formInfo->isUse && ($formInfo->required || $formInfo->mustRequired)) + { + if ($mode === 'update' && in_array($formInfo->name, $not_required_in_update)) + { + // pass + } + elseif (!isset($args->{$formInfo->name}) || $this->_checkEmpty($args->{$formInfo->name} ?? '', $formInfo->type ?? '')) + { + if (in_array($formInfo->name, $not_required_if_indirect_insert) && !preg_match('/^procMember.+/i', Context::get('act'))) + { + // pass + } + elseif (!in_array($formInfo->name, $skip)) + { + return new BaseObject(-1, sprintf(lang('common.filter.isnull'), $formInfo->title)); + } + } + } + if ($formInfo->name === 'email_address' && $args->{$formInfo->name} && !Mail::isVaildMailAddress($args->{$formInfo->name})) + { + return new BaseObject(-1, sprintf(lang('common.filter.invalid_email'), $formInfo->title)); + } + if ($formInfo->name === 'user_id' && $args->{$formInfo->name} && !preg_match('/^[a-z]+[\w-]*[a-z0-9_]+$/i', $args->{$formInfo->name})) + { + return new BaseObject(-1, sprintf(lang('common.filter.invalid_user_id'), $formInfo->title)); + } + if ($formInfo->name === 'password' && $args->{$formInfo->name}) + { + $password_check = (string)Context::get('password2'); + if ($password_check !== '' && !hash_equals($args->password, $password_check)) + { + return new BaseObject(-1, 'msg_password_mismatch'); + } + } + } + + return new BaseObject; + } + + /** + * Check required fields on signup or modify info + * + * @param object $config + * @param object $args + * @param string $mode + * @param object $original + * @return object + */ + protected function _checkPhoneNumber($config, &$args, $mode = 'insert', $original = null) + { + $required = false; + $verify = $config->phone_number_verify_by_sms === 'Y'; + foreach ($config->signupForm as $formInfo) + { + if ($formInfo->name === 'phone_number' && $formInfo->isUse && $formInfo->required) + { + $required = true; + break; + } + } + + if ($verify) + { + // Attempt to fill in the country code. + if ($config->phone_number_default_country && (!$args->phone_country || $config->phone_number_hide_country === 'Y')) + { + $args->phone_country = $config->phone_number_default_country; + } + if ($args->phone_country && !preg_match('/^[A-Z]{3}$/', $args->phone_country)) + { + $args->phone_country = Rhymix\Framework\i18n::getCountryCodeByCallingCode($args->phone_country); + } + if ($args->phone_country === 'KOR' && !Rhymix\Framework\Korea::isValidPhoneNumber($args->phone_number ?? '')) + { + if ($required) + { + return new BaseObject(-1, 'msg_invalid_phone_number'); + } + else + { + $args->phone_number = ''; + return new BaseObject; + } + } + + // If updating, check if the new info is the same as the old info. + if ($mode === 'update' && $original) + { + $recheck_needed = false; + if ($args->phone_country !== $original->phone_country) + { + $recheck_needed = true; + } + if (preg_replace('/[^0-9]/', '', $args->phone_number) !== $original->phone_number) + { + $recheck_needed = true; + } + if (!$recheck_needed) + { + return new BaseObject; + } + } + + // Check if verified by SMS. + if (!isset($_SESSION['verify_by_sms']) || !$_SESSION['verify_by_sms']['status']) + { + return new BaseObject(-1, 'verify_by_sms_incomplete'); + } + if ($args->phone_country !== $_SESSION['verify_by_sms']['country']) + { + return new BaseObject(-1, 'verify_by_sms_incomplete'); + } + if ($args->phone_number !== $_SESSION['verify_by_sms']['number']) + { + return new BaseObject(-1, 'verify_by_sms_incomplete'); + } + } + + return new BaseObject; + } + + /** + * Check if a variable is empty. + * + * @param mixed $var + * @param string $type + * @return bool + */ + protected function _checkEmpty($var, $type = '') + { + if ($type === 'tel' || $type === 'kr_zip') + { + $condition = (is_array($var) && count($var) >= 3 && trim($var[0]) !== '' && trim($var[1]) !== '' && trim($var[2]) !== ''); + return !$condition; + } + elseif (is_array($var)) + { + return implode('', array_map('trim', $var)) === ''; + } + else + { + return trim(strval($var)) === ''; + } + } + + /** + * @deprecated + * @return void + */ + public static function _clearMemberCache($member_srl) + { + self::clearMemberCache($member_srl); + } + + /** + * Clear all cache entries about a member. + * + * @param int $member_srl + * @return void + */ + public static function clearMemberCache($member_srl) { $member_srl = intval($member_srl); Rhymix\Framework\Cache::delete("member:member_info:$member_srl"); - Rhymix\Framework\Cache::delete("member:member_groups:$member_srl:site:$site_srl"); + Rhymix\Framework\Cache::delete("member:member_groups:$member_srl"); Rhymix\Framework\Cache::delete("site_and_module:accessible_modules:$member_srl"); - if ($site_srl != 0) - { - Rhymix\Framework\Cache::delete("member:member_groups:$member_srl:site:0"); - } + unset($GLOBALS['__member_info__'][$member_srl]); + unset($GLOBALS['__member_groups__'][$member_srl]); + unset($GLOBALS['__member_info__']['profile_image'][$member_srl]); + unset($GLOBALS['__member_info__']['image_name'][$member_srl]); + unset($GLOBALS['__member_info__']['image_mark'][$member_srl]); + unset($GLOBALS['__member_info__']['group_image_mark'][$member_srl]); + unset($GLOBALS['__member_info__']['signature'][$member_srl]); } } /* End of file member.controller.php */ diff --git a/modules/member/member.mobile.php b/modules/member/member.mobile.php index 345e7f1da..bd6b7fbc7 100644 --- a/modules/member/member.mobile.php +++ b/modules/member/member.mobile.php @@ -1,10 +1,10 @@ */ -class memberMobile extends memberView +class MemberMobile extends MemberView { /** - * Support method are + * Support method are * dispMemberInfo, dispMemberSignUpForm, dispMemberFindAccount, dispMemberGetTempPassword, dispMemberModifyInfo, dispMemberModifyInfoBefore */ var $memberInfo; @@ -18,25 +18,6 @@ class memberMobile extends memberView $oSecurity = new Security(); $oSecurity->encodeHTML('member_config.signupForm..'); - // Set the template path - $mskin = $this->member_config->mskin; - if(!$mskin) - { - $template_path = sprintf('%sm.skins/%s/', $this->module_path, 'default'); - } - elseif($mskin === '/USE_RESPONSIVE/') - { - $template_path = sprintf("%sskins/%s/", $this->module_path, $this->member_config->skin); - if(!is_dir($template_path) || !$this->member_config->skin) - { - $template_path = sprintf("%sskins/%s/", $this->module_path, 'default'); - } - } - else - { - $template_path = sprintf('%sm.skins/%s', $this->module_path, $mskin); - } - // if member_srl exists, set memberInfo $member_srl = Context::get('member_srl'); if($member_srl) @@ -53,15 +34,8 @@ class memberMobile extends memberView } } - $this->setTemplatePath($template_path); - - $oLayoutModel = getModel('layout'); - $layout_info = $oLayoutModel->getLayout($this->member_config->mlayout_srl); - if($layout_info) - { - $this->module_info->mlayout_srl = $this->member_config->mlayout_srl; - $this->setLayoutPath($layout_info->path); - } + // Set layout and skin paths + $this->setLayoutAndTemplatePaths('M', $this->member_config); } function dispMemberModifyInfo() diff --git a/modules/member/member.model.php b/modules/member/member.model.php index 7b3f5900d..2cdda377e 100644 --- a/modules/member/member.model.php +++ b/modules/member/member.model.php @@ -5,111 +5,167 @@ * @author NAVER (developers@xpressengine.com) * @brief Model class of the member module */ -class memberModel extends member +class MemberModel extends Member { /** * @brief Keep data internally which may be frequently called ... */ - var $join_form_list = NULL; + protected static $_denied_id_list; + protected static $_join_form_list; + protected static $_managed_email_hosts; + protected static $_member_config; /** * @brief Initialization */ - function init() + public function init() { + } /** * @brief Return member's configuration */ - function getMemberConfig() + public static function getMemberConfig() { - static $member_config; - - if($member_config) + if (self::$_member_config) { - return $member_config; + return self::$_member_config; } - // Get member configuration stored in the DB - $oModuleModel = getModel('module'); - $config = $oModuleModel->getModuleConfig('member'); + $config = ModuleModel::getModuleConfig('member') ?: new stdClass; - if(!$config->signupForm || !is_array($config->signupForm)) - { - $oMemberAdminController = getAdminController('member'); - $identifier = ($config->identifier) ? $config->identifier : 'email_address'; - $config->signupForm = $oMemberAdminController->createSignupForm($identifier); - } - //for multi language - foreach($config->signupForm AS $key=>$value) - { - $config->signupForm[$key]->title = ($value->isDefaultForm) ? lang($value->name) : $value->title; - if($config->signupForm[$key]->isPublic != 'N') $config->signupForm[$key]->isPublic = 'Y'; - if($value->name == 'find_account_question') $config->signupForm[$key]->isPublic = 'N'; - } + // Set default config + $config->enable_join = $config->enable_join ?? 'Y'; + $config->enable_confirm = $config->enable_confirm ?? 'N'; + $config->authmail_expires = $config->authmail_expires ?? 1; + $config->authmail_expires_unit = $config->authmail_expires_unit ?? 86400; + $config->member_profile_view = $config->member_profile_view ?? 'N'; + $config->update_nickname_log = $config->update_nickname_log ?? 'N'; + $config->nickname_symbols = $config->nickname_symbols ?? 'Y'; + $config->nickname_symbols_allowed_list = $config->nickname_symbols_allowed_list ?? ''; + $config->password_strength = $config->password_strength ?? 'normal'; + $config->password_hashing_algorithm = $config->password_hashing_algorithm ?? Rhymix\Framework\Password::getBestSupportedAlgorithm(); + $config->password_hashing_work_factor = $config->password_hashing_work_factor ?? 10; + $config->password_hashing_auto_upgrade = $config->password_hashing_auto_upgrade ?? 'Y'; + $config->password_change_invalidate_other_sessions = $config->password_change_invalidate_other_sessions ?? 'N'; - // Get terms of user - if(!$config->agreements) + // Set features config + if(!isset($config->features)) $config->features = array(); + $config->features['scrapped_documents'] = $config->features['scrapped_documents'] ?? true; + $config->features['saved_documents'] = $config->features['saved_documents'] ?? true; + $config->features['my_documents'] = $config->features['my_documents'] ?? true; + $config->features['my_comments'] = $config->features['my_comments'] ?? true; + $config->features['active_logins'] = $config->features['active_logins'] ?? true; + $config->features['nickname_log'] = $config->features['nickname_log'] ?? true; + + // Set agreements config + if(!isset($config->agreements) || !is_array($config->agreements)) { - $config->agreement = memberModel::_getAgreement(); + $config->agreements = array(); $config->agreements[1] = new stdClass; $config->agreements[1]->title = lang('agreement'); - $config->agreements[1]->content = $config->agreement; - $config->agreements[1]->type = $config->agreement ? 'required' : 'disabled'; + $config->agreements[1]->content = self::_getAgreement() ?: ($config->agreement ?? ''); + $config->agreements[1]->type = !empty($config->agreements[1]->content) ? 'required' : 'disabled'; } + unset($config->agreement); - if(!$config->webmaster_name) $config->webmaster_name = 'webmaster'; - - if(!$config->image_name_max_width) $config->image_name_max_width = 90; - if(!$config->image_name_max_height) $config->image_name_max_height = 20; - if(!$config->image_name_max_filesize) $config->image_name_max_filesize = null; - if(!$config->image_mark_max_width) $config->image_mark_max_width = 20; - if(!$config->image_mark_max_height) $config->image_mark_max_height = 20; - if(!$config->image_mark_max_filesize) $config->image_mark_max_filesize = null; - if(!$config->profile_image_max_width) $config->profile_image_max_width = 90; - if(!$config->profile_image_max_height) $config->profile_image_max_height = 90; - if(!$config->profile_image_max_filesize) $config->profile_image_max_filesize = null; - - if(!$config->skin) $config->skin = 'default'; - if(!$config->colorset) $config->colorset = 'white'; - if(!$config->editor_skin || $config->editor_skin == 'default') $config->editor_skin = 'ckeditor'; - if(!$config->group_image_mark) $config->group_image_mark = "N"; - - if(!$config->identifier) $config->identifier = 'user_id'; - - if(!$config->emailhost_check) $config->emailhost_check = 'allowed'; - - if(!$config->max_error_count) $config->max_error_count = 10; - if(!$config->max_error_count_time) $config->max_error_count_time = 300; - - if(!$config->signature_editor_skin || $config->signature_editor_skin == 'default') $config->signature_editor_skin = 'ckeditor'; - if(!$config->sel_editor_colorset) $config->sel_editor_colorset = 'moono-lisa'; - if(!$config->member_allow_fileupload) $config->member_allow_fileupload = 'N'; - if(!$config->member_profile_view) $config->member_profile_view = 'N'; - - if($config->redirect_mid) + // Set signup config + $config->limit_day = $config->limit_day ?? 0; + $config->emailhost_check = $config->emailhost_check ?? 'allowed'; + $config->special_phone_number = $config->special_phone_number ?? null; + $config->special_phone_code = $config->special_phone_code ?? null; + if (!empty($config->redirect_mid)) { - $config->redirect_url = getNotEncodedFullUrl('','mid',$config->redirect_mid); + $config->redirect_url = getNotEncodedFullUrl('', 'mid', $config->redirect_mid); + } + $config->phone_number_default_country = $config->phone_number_default_country ?? (Context::get('lang_type') === 'ko' ? 'KOR' : null); + $config->phone_number_hide_country = $config->phone_number_hide_country ?? 'N'; + $config->phone_number_allow_duplicate = $config->phone_number_allow_duplicate ?? 'N'; + $config->phone_number_verify_by_sms = $config->phone_number_verify_by_sms ?? 'N'; + $config->signature_editor_skin = $config->signature_editor_skin ?? 'ckeditor'; + $config->sel_editor_colorset = $config->sel_editor_colorset ?? 'moono-lisa'; + $config->signature = $config->signature ?? 'N'; + $config->signature_html = $config->signature_html ?? 'Y'; + $config->signature_html_retroact = $config->signature_html_retroact ?? 'N'; + $config->member_allow_fileupload = $config->member_allow_fileupload ?? 'N'; + $config->member_max_filesize = $config->member_max_filesize ?? 0; + $config->profile_image = $config->profile_image ?? 'N'; + $config->profile_image_max_width = $config->profile_image_max_width ?? 90; + $config->profile_image_max_height = $config->profile_image_max_height ?? 90; + $config->profile_image_max_filesize = $config->profile_image_max_filesize ?? null; + $config->image_name = $config->image_name ?? 'N'; + $config->image_name_max_width = $config->image_name_max_width ?? 90; + $config->image_name_max_height = $config->image_name_max_height ?? 20; + $config->image_name_max_filesize = $config->image_name_max_filesize ?? null; + $config->image_mark = $config->image_mark ?? 'N'; + $config->image_mark_max_width = $config->image_mark_max_width ?? 20; + $config->image_mark_max_height = $config->image_mark_max_height ?? 20; + $config->image_mark_max_filesize = $config->image_mark_max_filesize ?? null; + if($config->signature_editor_skin === 'default') + { + $config->signature_editor_skin = 'ckeditor'; } - $member_config = $config; + // Set login config + $config->identifiers = $config->identifiers ?? array('user_id', 'email_address'); + if (in_array('email_address', $config->identifiers) && $config->enable_confirm === 'Y' && Context::get('act') === 'dispMemberInfo') + { + $config->identifier = 'email_address'; + } + else + { + $config->identifier = array_first($config->identifiers) === 'email_address' ? 'email_address' : 'user_id'; + } + $config->change_password_date = $config->change_password_date ?? 0; + $config->enable_login_fail_report = $config->enable_login_fail_report ?? 'Y'; + $config->max_error_count = $config->max_error_count ?? 10; + $config->max_error_count_time = $config->max_error_count_time ?? 300; + $config->login_invalidate_other_sessions = $config->login_invalidate_other_sessions ?? 'N'; + $config->after_login_url = $config->after_login_url ?? null; + $config->after_logout_url = $config->after_logout_url ?? null; - return $config; + // Set design config + $config->layout_srl = $config->layout_srl ?? 0; + $config->skin = $config->skin ?? 'default'; + $config->colorset = $config->colorset ?? 'white'; + $config->mlayout_srl = $config->mlayout_srl ?? 0; + $config->mskin = $config->mskin ?? 'default'; + + // Set group image config + $config->group_image_mark = $config->group_image_mark ?? 'N'; + + // Set signup form + if(!isset($config->signupForm) || !is_array($config->signupForm)) + { + $config->signupForm = MemberAdminController::createSignupForm($config); + } + foreach($config->signupForm as $key => $value) + { + if(!empty($value->isDefaultForm) && empty($value->isCustomTitle)) + { + $config->signupForm[$key]->title = lang($value->name); + } + $config->signupForm[$key]->isPublic = $config->signupForm[$key]->isPublic ?? 'Y'; + } + + return self::$_member_config = $config; } /** + * Get member agreement from old version + * * @deprecated */ - function _getAgreement() + protected static function _getAgreement() { - $agreement_file = _XE_PATH_.'files/member_extra_info/agreement_' . Context::get('lang_type') . '.txt'; + $agreement_file = RX_BASEDIR.'files/member_extra_info/agreement_' . Context::get('lang_type') . '.txt'; if(is_readable($agreement_file)) { return FileHandler::readFile($agreement_file); } - $agreement_file = _XE_PATH_.'files/member_extra_info/agreement_' . config('locale.default_lang') . '.txt'; + $agreement_file = RX_BASEDIR.'files/member_extra_info/agreement_' . config('locale.default_lang') . '.txt'; if(is_readable($agreement_file)) { return FileHandler::readFile($agreement_file); @@ -118,7 +174,7 @@ class memberModel extends member $lang_selected = Context::loadLangSelected(); foreach($lang_selected as $key => $val) { - $agreement_file = _XE_PATH_.'files/member_extra_info/agreement_' . $key . '.txt'; + $agreement_file = RX_BASEDIR.'files/member_extra_info/agreement_' . $key . '.txt'; if(is_readable($agreement_file)) { return FileHandler::readFile($agreement_file); @@ -128,42 +184,83 @@ class memberModel extends member return null; } + /** + * Display login status as JSON API + */ + public function getLoginStatus() + { + // Check origin + $origin = strval(($_SERVER['HTTP_ORIGIN'] ?? '') ?: ($_SERVER['HTTP_REFERER'] ?? '')); + if ($origin !== '' && $origin !== 'null' && !Rhymix\Framework\URL::isInternalURL($origin)) + { + $this->setError(-1); + $this->setMessage('msg_security_violation'); + $this->add('errorDetail', 'ERR_CSRF_INVALID_ORIGIN'); + return; + } + + // Add CORS restriction + header('Access-Control-Allow-Origin: ' . rtrim(Rhymix\Framework\Url::getCurrentDomainURL(), '/')); + header('Cross-Origin-Resource-Policy: same-origin'); + + // Return login status and CSRF token + Context::setResponseMethod('JSON'); + $this->add('status', Rhymix\Framework\Session::getLoginStatus()); + $this->add('csrf_token', Rhymix\Framework\Session::getGenericToken()); + $this->add('last_login', Rhymix\Framework\Session::getLastLoginTime()); + } + /** * @brief Display menus of the member */ - function getMemberMenu() + public function getMemberMenu() { // Get member_srl of he target member and logged info of the current user $member_srl = Context::get('target_srl'); + if ($member_srl <= 0) + { + return; + } + $mid = Context::get('cur_mid'); $logged_info = Context::get('logged_info'); - $act = Context::get('cur_act'); - // When click user's own nickname - if($member_srl == $logged_info->member_srl) $member_info = $logged_info; - // When click other's nickname - else $member_info = $this->getMemberInfoByMemberSrl($member_srl); + $module_config = self::getMemberConfig(); + $icon_path = ''; + // Get requested member info + if($member_srl == $logged_info->member_srl) + { + $member_info = $logged_info; + } + else + { + $member_info = self::getMemberInfoByMemberSrl($member_srl); + } + + // Check if member_info is valid $member_srl = $member_info->member_srl; - if(!$member_srl) return; - // List variables - $user_id = $member_info->user_id; - $user_name = $member_info->user_name; + if (!$member_srl) + { + return; + } ModuleHandler::triggerCall('member.getMemberMenu', 'before', $member_info); - $oMemberController = getController('member'); + $oMemberController = MemberController::getInstance(); + // Display member information (Don't display to non-logged user) if($logged_info->member_srl) { $url = getUrl('','mid',$mid,'act','dispMemberInfo','member_srl',$member_srl); - $oMemberController->addMemberPopupMenu($url,'cmd_view_member_info',$icon_path,'self'); + $oMemberController->addMemberPopupMenu($url, 'cmd_view_member_info', $icon_path, 'self'); } // When click other's nickname if($member_srl != $logged_info->member_srl && $logged_info->member_srl) { // Get email config - foreach($this->module_config->signupForm as $field) + $email_config = null; + foreach($module_config->signupForm as $field) { if($field->name == 'email_address') { @@ -173,9 +270,9 @@ class memberModel extends member } // Send an email only if email address is public - if($email_config->isPublic == 'Y' && $member_info->email_address) + if($email_config && $email_config->isPublic == 'Y' && $member_info->email_address) { - $oCommunicationModel = getModel('communication'); + $oCommunicationModel = CommunicationModel::getInstance(); if($logged_info->is_admin == 'Y' || $oCommunicationModel->isFriend($member_info->member_srl)) { $url = 'mailto:'.escape($member_info->email_address); @@ -183,18 +280,18 @@ class memberModel extends member } } } - + // Check if homepage and blog are public $homepage_is_public = false; $blog_is_public = false; - if ($logged_info->is_admin === 'Y' || ($logged_info->member_srl && $logged_info->member_srl == $member_srl)) + if ($logged_info->is_admin === 'Y') { $homepage_is_public = true; $blog_is_public = true; } else { - foreach ($this->module_config->signupForm as $field) + foreach ($module_config->signupForm as $field) { if ($field->name === 'homepage' && $field->isPublic === 'Y') { @@ -206,42 +303,41 @@ class memberModel extends member } } } - + // View homepage info if($member_info->homepage && $homepage_is_public) { - $oMemberController->addMemberPopupMenu(escape($member_info->homepage, false), 'homepage', '', 'blank'); + $oMemberController->addMemberPopupMenu(escape($member_info->homepage, false), 'homepage', '', '_blank', 'homepage'); } - + // View blog info if($member_info->blog && $blog_is_public) { - $oMemberController->addMemberPopupMenu(escape($member_info->blog, false), 'blog', '', 'blank'); + $oMemberController->addMemberPopupMenu(escape($member_info->blog, false), 'blog', '', '_blank', 'blog'); } - + // Call a trigger (after) ModuleHandler::triggerCall('member.getMemberMenu', 'after', $member_info); // Display a menu for editting member info to a top administrator if($logged_info->is_admin == 'Y') { $url = getUrl('','module','admin','act','dispMemberAdminInsert','member_srl',$member_srl); - $oMemberController->addMemberPopupMenu($url,'cmd_manage_member_info',$icon_path,'MemberModifyInfo'); + $oMemberController->addMemberPopupMenu($url,'cmd_manage_member_info',$icon_path,''); $url = getUrl('','module','member','act','dispMemberSpammer','member_srl',$member_srl,'module_srl',0); $oMemberController->addMemberPopupMenu($url,'cmd_spammer',$icon_path,'popup'); - + $url = getUrl('','module','admin','act','dispDocumentAdminList','search_target','member_srl','search_keyword',$member_srl); - $oMemberController->addMemberPopupMenu($url,'cmd_trace_document',$icon_path,'TraceMemberDocument'); + $oMemberController->addMemberPopupMenu($url,'cmd_trace_document',$icon_path,''); $url = getUrl('','module','admin','act','dispCommentAdminList','search_target','member_srl','search_keyword',$member_srl); - $oMemberController->addMemberPopupMenu($url,'cmd_trace_comment',$icon_path,'TraceMemberComment'); + $oMemberController->addMemberPopupMenu($url,'cmd_trace_comment',$icon_path,''); } // Change a language of pop-up menu - $menus = Context::get('member_popup_menu_list'); - $menus_count = count($menus); - for($i=0;$i<$menus_count;$i++) + $menus = Context::get('member_popup_menu_list') ?: []; + foreach ($menus as $menu) { - $menus[$i]->str = lang($menus[$i]->str); + $menu->str = lang($menu->str ?? ''); } // Get a list of finalized pop-up menu $this->add('menus', $menus); @@ -250,7 +346,7 @@ class memberModel extends member /** * @brief Check if logged-in */ - function isLogged() + public static function isLogged() { return Rhymix\Framework\Session::getMemberSrl() ? true : false; } @@ -258,15 +354,17 @@ class memberModel extends member /** * @brief Return session information of the logged-in user */ - function getLoggedInfo() + public static function getLoggedInfo() { return Context::get('logged_info'); } /** * @brief Return member information with user_id + * + * @return object|null */ - function getMemberInfoByUserID($user_id) + public static function getMemberInfoByUserID($user_id) { if(!$user_id) return; @@ -276,46 +374,75 @@ class memberModel extends member if(!$output->toBool()) return $output; if(!$output->data) return; - $member_info = $this->arrangeMemberInfo($output->data); + $member_info = self::arrangeMemberInfo($output->data); return $member_info; } /** * @brief Return member information with email_address + * + * @return object|null */ - function getMemberInfoByEmailAddress($email_address) + public static function getMemberInfoByEmailAddress($email_address) { if(!$email_address) return; $args = new stdClass(); - - if(config('db.master.type') == 'cubrid') - { - $args->email_address = strtolower($email_address); - $output = executeQuery('member.getMemberInfoByEmailAddressForCubrid', $args); - } - else - { - $args->email_address = $email_address; - $output = executeQuery('member.getMemberInfoByEmailAddress', $args); - } - + $args->email_address = $email_address; + $output = executeQuery('member.getMemberInfoByEmailAddress', $args); if(!$output->toBool()) return $output; if(!$output->data) return; - $member_info = $this->arrangeMemberInfo($output->data); + $member_info = self::arrangeMemberInfo($output->data); + return $member_info; + } + + /** + * @brief Return member information with phone number + * + * @return object|null + */ + public static function getMemberInfoByPhoneNumber($phone_number, $phone_country = null) + { + if(!$phone_number) return; + if($phone_country) + { + if(preg_match('/^[A-Z]{3}$/', $phone_country)) + { + $phone_country = array($phone_country, preg_replace('/[^0-9]/', '', Rhymix\Framework\i18n::getCallingCodeByCountryCode($phone_country))); + } + else + { + $phone_country = array(preg_replace('/[^0-9]/', '', $phone_country), Rhymix\Framework\i18n::getCountryCodeByCallingCode($phone_country)); + } + } + + $args = new stdClass(); + $args->phone_number = $phone_number; + $args->phone_country = $phone_country; + $output = executeQuery('member.getMemberInfoByPhoneNumber', $args); + if(!$output->toBool()) return $output; + if(!$output->data) return; + + $member_info = self::arrangeMemberInfo($output->data); return $member_info; } /** * @brief Return member information with member_srl + * + * @return object */ - function getMemberInfoByMemberSrl($member_srl, $site_srl = 0) + public static function getMemberInfoByMemberSrl($member_srl) { if(!$member_srl) return new stdClass; - if(!$GLOBALS['__member_info__'][$member_srl]) + if(!isset($GLOBALS['__member_info__'])) + { + $GLOBALS['__member_info__'] = []; + } + if(!isset($GLOBALS['__member_info__'][$member_srl])) { $cache_key = sprintf('member:member_info:%d', $member_srl); $GLOBALS['__member_info__'][$member_srl] = Rhymix\Framework\Cache::get($cache_key); @@ -324,42 +451,56 @@ class memberModel extends member $args = new stdClass(); $args->member_srl = $member_srl; $output = executeQuery('member.getMemberInfoByMemberSrl', $args); - if(!$output->data) - { - return new stdClass; - } - - $member_info = $this->arrangeMemberInfo($output->data, $site_srl); - if($output->toBool()) + if($output->toBool() && $output->data) { + $member_info = self::arrangeMemberInfo($output->data); Rhymix\Framework\Cache::set($cache_key, $member_info); } + else + { + $member_info = new stdClass; + } + $GLOBALS['__member_info__'][$member_srl] = $member_info; } } return $GLOBALS['__member_info__'][$member_srl]; } + /** + * @brief Shortcut to getMemberInfoByMemberSrl() + * + * @param int $member_srl + * @return object + */ + public static function getMemberInfo($member_srl) + { + return self::getMemberInfoByMemberSrl(intval($member_srl)); + } + /** * @brief Add member info from extra_vars and other information */ - function arrangeMemberInfo($info, $site_srl = 0) + public static function arrangeMemberInfo($info) { - if(!$GLOBALS['__member_info__'][$info->member_srl]) + if(!isset($GLOBALS['__member_info__'])) { - $oModuleModel = getModel('module'); - $config = $oModuleModel->getModuleConfig('member'); + $GLOBALS['__member_info__'] = []; + } + if(!isset($GLOBALS['__member_info__'][$info->member_srl])) + { + $config = self::getMemberConfig(); - $info->profile_image = $this->getProfileImage($info->member_srl); - $info->image_name = $this->getImageName($info->member_srl); - $info->image_mark = $this->getImageMark($info->member_srl); + $info->profile_image = self::getProfileImage($info->member_srl); + $info->image_name = self::getImageName($info->member_srl); + $info->image_mark = self::getImageMark($info->member_srl); if($config->group_image_mark=='Y') { - $info->group_mark = $this->getGroupImageMark($info->member_srl,$site_srl); + $info->group_mark = self::getGroupImageMark($info->member_srl); } - $info->signature = $this->getSignature($info->member_srl); - $info->group_list = $this->getMemberGroups($info->member_srl, $site_srl); - $info->is_site_admin = $oModuleModel->isSiteAdmin($info) ? true : false; + $info->signature = self::getSignature($info->member_srl); + $info->group_list = self::getMemberGroups($info->member_srl); + $info->is_site_admin = ModuleModel::isSiteAdmin($info) ? true : false; $extra_vars = unserialize($info->extra_vars); unset($info->extra_vars); @@ -367,7 +508,7 @@ class memberModel extends member { foreach($extra_vars as $key => $val) { - if(!is_array($val) && !is_object($val) && strpos($val, '|@|') !== FALSE) + if(!is_null($val) && !is_array($val) && !is_object($val) && strpos($val, '|@|') !== FALSE) { $val = explode('|@|', $val); } @@ -378,7 +519,7 @@ class memberModel extends member } } - if(strlen($info->find_account_answer) == 32 && preg_match('/[a-zA-Z0-9]+/', $info->find_account_answer)) + if(strlen($info->find_account_answer ?? '') == 32 && preg_match('/[a-zA-Z0-9]+/', $info->find_account_answer ?? '')) { $info->find_account_answer = null; } @@ -387,8 +528,17 @@ class memberModel extends member $oSecurity = new Security($info); $oSecurity->encodeHTML('user_id', 'user_name', 'nick_name', 'find_account_answer', 'description', 'address.', 'group_list..'); - $info->homepage = strip_tags($info->homepage); - $info->blog = strip_tags($info->blog); + // Validate URLs + $info->homepage = escape(strip_tags($info->homepage)); + if ($info->homepage !== '' && !preg_match('!^https?://[^\\\\/]+!', $info->homepage)) + { + $info->homepage = ''; + } + $info->blog = escape(strip_tags($info->blog)); + if ($info->blog !== '' && !preg_match('!^https?://[^\\\\/]+!', $info->blog)) + { + $info->blog = ''; + } if($extra_vars) { @@ -405,18 +555,6 @@ class memberModel extends member } } - // Check format. - $oValidator = new Validator(); - if(!$oValidator->applyRule('url', $info->homepage)) - { - $info->homepage = ''; - } - - if(!$oValidator->applyRule('url', $info->blog)) - { - $info->blog = ''; - } - $GLOBALS['__member_info__'][$info->member_srl] = $info; } @@ -426,7 +564,7 @@ class memberModel extends member /** * @brief Get member_srl corresponding to userid */ - function getMemberSrlByUserID($user_id) + public static function getMemberSrlByUserID($user_id) { $args = new stdClass(); $args->user_id = $user_id; @@ -437,7 +575,7 @@ class memberModel extends member /** * @brief Get member_srl corresponding to EmailAddress */ - function getMemberSrlByEmailAddress($email_address) + public static function getMemberSrlByEmailAddress($email_address) { $args = new stdClass(); $args->email_address = $email_address; @@ -445,10 +583,22 @@ class memberModel extends member return $output->data->member_srl; } + /** + * @brief Get member_srl corresponding to phone number + */ + public static function getMemberSrlByPhoneNumber($phone_number, $phone_country = null) + { + $args = new stdClass(); + $args->phone_number = $phone_number; + $args->phone_country = $phone_country; + $output = executeQueryArray('member.getMemberSrl', $args); + return count($output->data) ? array_first($output->data)->member_srl : null; + } + /** * @brief Get member_srl corresponding to nickname */ - function getMemberSrlByNickName($nick_name) + public static function getMemberSrlByNickName($nick_name) { $args = new stdClass(); $args->nick_name = $nick_name; @@ -459,7 +609,7 @@ class memberModel extends member /** * @brief Return member_srl of the current logged-in user */ - function getLoggedMemberSrl() + public static function getLoggedMemberSrl() { return Rhymix\Framework\Session::getMemberSrl(); } @@ -467,37 +617,35 @@ class memberModel extends member /** * @brief Return user_id of the current logged-in user */ - function getLoggedUserID() + public static function getLoggedUserID() { - if(!$this->isLogged()) return; $logged_info = Context::get('logged_info'); + if (!$logged_info || !$logged_info->member_srl) return; return $logged_info->user_id; } /** * @brief Get a list of groups which the member_srl belongs to */ - function getMemberGroups($member_srl, $site_srl = 0, $force_reload = false) + public static function getMemberGroups($member_srl, $site_srl = 0, $force_reload = false) { - static $member_groups = array(); - // cache controll - $cache_key = sprintf('member:member_groups:%d:site:%d', $member_srl, $site_srl); + $member_srl = intval($member_srl); + $cache_key = sprintf('member:member_groups:%d', $member_srl); $group_list = Rhymix\Framework\Cache::get($cache_key); - if(!$member_groups[$member_srl][$site_srl] || $force_reload) + if(!isset($GLOBALS['__member_groups__'][$member_srl]) || $force_reload) { if(!$group_list) { $args = new stdClass(); $args->member_srl = $member_srl; - $args->site_srl = $site_srl; $output = executeQueryArray('member.getMemberGroups', $args); - $group_list = $output->data; + $group_list = $output->data ?: []; if (!count($group_list)) { - $default_group = $this->getDefaultGroup($site_srl); - getController('member')->addMemberToGroup($member_srl, $default_group->group_srl, $site_srl); + $default_group = self::getDefaultGroup(0); + MemberController::addMemberToGroup($member_srl, $default_group->group_srl); $group_list[$default_group->group_srl] = $default_group->title; } //insert in cache @@ -508,29 +656,35 @@ class memberModel extends member } if(!$group_list) return array(); - foreach($group_list as $group) + foreach($group_list as $group_srl => $group) { - $result[$group->group_srl] = $group->title; + if (is_object($group)) + { + $result[$group->group_srl] = $group->title; + } + else + { + $result[$group_srl] = $group; + } } - $member_groups[$member_srl][$site_srl] = $result; + $GLOBALS['__member_groups__'][$member_srl] = $result; } - return $member_groups[$member_srl][$site_srl]; + return $GLOBALS['__member_groups__'][$member_srl]; } /** * @brief Get a list of groups which member_srls belong to */ - function getMembersGroups($member_srls, $site_srl = 0) + public static function getMembersGroups($member_srls) { $args = new stdClass; $args->member_srls = implode(',',$member_srls); - $args->site_srl = $site_srl; $args->sort_index = 'list_order'; $output = executeQueryArray('member.getMembersGroups', $args); if(!$output->data) return array(); $result = array(); - foreach($output->data as $key=>$val) + foreach($output->data as $key => $val) { $result[$val->member_srl][] = $val->title; } @@ -540,15 +694,14 @@ class memberModel extends member /** * @brief Get a default group */ - function getDefaultGroup($site_srl = 0) + public static function getDefaultGroup() { - $cache_key = sprintf('member:default_group:site:%d', $site_srl); + $cache_key = sprintf('member:default_group'); $default_group = Rhymix\Framework\Cache::get($cache_key); if(!$default_group) { $args = new stdClass(); - $args->site_srl = $site_srl; $output = executeQuery('member.getDefaultGroup', $args); $default_group = $output->data; if($output->toBool()) @@ -563,7 +716,7 @@ class memberModel extends member /** * @brief Get an admin group */ - function getAdminGroup($columnList = array()) + public static function getAdminGroup($columnList = array()) { $args = new stdClass; $output = executeQuery('member.getAdminGroup', $args, $columnList); @@ -573,7 +726,7 @@ class memberModel extends member /** * @brief Get group info corresponding to group_srl */ - function getGroup($group_srl, $columnList = array()) + public static function getGroup($group_srl, $columnList = array()) { $args = new stdClass; $args->group_srl = $group_srl; @@ -584,30 +737,23 @@ class memberModel extends member /** * @brief Get a list of groups */ - function getGroups($site_srl = 0) + public static function getGroups() { - if(!$GLOBALS['__group_info__'][$site_srl]) + if(!isset($GLOBALS['__group_info__'][0])) { $result = array(); - - if(!isset($site_srl)) - { - $site_srl = 0; - } - - $group_list = Rhymix\Framework\Cache::get("member:member_groups:site:$site_srl"); + $group_list = Rhymix\Framework\Cache::get('member:member_groups'); if(!$group_list) { $args = new stdClass(); - $args->site_srl = $site_srl; $args->sort_index = 'list_order'; $args->order_type = 'asc'; $output = executeQueryArray('member.getGroups', $args); $group_list = $output->data; if($output->toBool()) { - Rhymix\Framework\Cache::set("member:member_groups:site:$site_srl", $group_list, 0, true); + Rhymix\Framework\Cache::set('member:member_groups', $group_list, 0, true); } } @@ -621,17 +767,19 @@ class memberModel extends member $result[$val->group_srl] = $val; } - $GLOBALS['__group_info__'][$site_srl] = $result; + $GLOBALS['__group_info__'][0] = $result; } - return $GLOBALS['__group_info__'][$site_srl]; + return $GLOBALS['__group_info__'][0]; } - public function getApiGroups() + /** + * @deprecated + */ + public static function getApiGroups() { $siteSrl = Context::get('siteSrl'); - $groupInfo = $this->getGroups($siteSrl); - - $this->add($groupInfo); + $groupInfo = self::getGroups($siteSrl); + //$this->add($groupInfo); } /** @@ -641,58 +789,65 @@ class memberModel extends member * To use as extend_filter, the argument should be boolean. * When the argument is true, it returns object result in type of filter. */ - function getJoinFormList($filter_response = false) + public static function getJoinFormList($filter_response = false) { global $lang; // Set to ignore if a super administrator. $logged_info = Context::get('logged_info'); - if(!$this->join_form_list) + if(!self::$_join_form_list) { // Argument setting to sort list_order column $args = new stdClass(); $args->sort_index = "list_order"; - $output = executeQuery('member.getJoinFormList', $args); + $output = executeQueryArray('member.getJoinFormList', $args); // NULL if output data deosn't exist $join_form_list = $output->data; if(!$join_form_list) return NULL; // Need to unserialize because serialized array is inserted into DB in case of default_value if(!is_array($join_form_list)) $join_form_list = array($join_form_list); - $join_form_count = count($join_form_list); - for($i=0;$i<$join_form_count;$i++) + foreach ($join_form_list as $i => $join_form) { - $join_form_list[$i]->column_name = strtolower($join_form_list[$i]->column_name); + $join_form->column_name = strtolower($join_form->column_name); - $member_join_form_srl = $join_form_list[$i]->member_join_form_srl; - $column_type = $join_form_list[$i]->column_type; - $column_name = $join_form_list[$i]->column_name; - $column_title = $join_form_list[$i]->column_title; - $default_value = $join_form_list[$i]->default_value; // Add language variable - if(!isset($lang->extend_vars)) $lang->extend_vars = array(); - $lang->extend_vars[$column_name] = $column_title; - // unserialize if the data type if checkbox, select and so on - if(in_array($column_type, array('checkbox','select','radio'))) + $column_name = $join_form->column_name; + $column_title = $join_form->column_title; + if (!isset($lang->extend_vars)) { - $join_form_list[$i]->default_value = unserialize($default_value); - if(!$join_form_list[$i]->default_value[0]) $join_form_list[$i]->default_value = ''; + $lang->extend_vars = array(); + } + $lang->extend_vars[$column_name] = $column_title; + + // unserialize if the data type if checkbox, select and so on + if (in_array($join_form->column_type, ['checkbox','select','radio'])) + { + if (isset($join_form->options) && $join_form->options !== '') + { + $join_form->options = json_decode($join_form->options, true); + } + elseif (preg_match('/^a:\d+:\{i:/', $join_form->default_value)) + { + $join_form->options = unserialize($join_form->default_value); + $join_form->default_value = ''; + } } else { - $join_form_list[$i]->default_value = ''; + $join_form->default_value = ''; } - $list[$member_join_form_srl] = $join_form_list[$i]; + $list[$join_form->member_join_form_srl] = $join_form; } - $this->join_form_list = $list; + self::$_join_form_list = $list; } // Get object style if the filter_response is true - if($filter_response && count($this->join_form_list)) + if($filter_response && count(self::$_join_form_list)) { - foreach($this->join_form_list as $key => $val) + foreach(self::$_join_form_list as $key => $val) { if($val->is_active != 'Y') continue; - unset($obj); + $obj = new stdClass; $obj->type = $val->column_type; $obj->name = $val->column_name; $obj->lang = $val->column_title; @@ -700,7 +855,7 @@ class memberModel extends member else $obj->required = false; $filter_output[] = $obj; - unset($open_obj); + $open_obj = new stdClass; $open_obj->name = 'open_'.$val->column_name; $open_obj->required = false; $filter_output[] = $open_obj; @@ -709,7 +864,7 @@ class memberModel extends member return $filter_output; } // Return the result - return $this->join_form_list; + return self::$_join_form_list; } /** @@ -717,7 +872,7 @@ class memberModel extends member * * @return array $joinFormList */ - function getUsedJoinFormList() + public static function getUsedJoinFormList() { $args = new stdClass(); $args->sort_index = "list_order"; @@ -745,9 +900,9 @@ class memberModel extends member /** * @brief Combine extend join form and member information (used to modify member information) */ - function getCombineJoinForm($member_info) + public static function getCombineJoinForm($member_info) { - $extend_form_list = $this->getJoinFormlist(); + $extend_form_list = self::getJoinFormlist(); if(!$extend_form_list) return; // Member info is open only to an administrator and him/herself when is_private is true. $logged_info = Context::get('logged_info'); @@ -755,7 +910,7 @@ class memberModel extends member foreach($extend_form_list as $srl => $item) { $column_name = $item->column_name; - $value = $member_info->{$column_name}; + $value = $member_info->{$column_name} ?? null; // Change values depening on the type of extend form switch($item->column_type) @@ -763,6 +918,18 @@ class memberModel extends member case 'checkbox' : if($value && !is_array($value)) $value = array($value); break; + /* + case 'country' : + $lang_type = Context::get('lang_type'); + $country_list = Rhymix\Framework\i18n::listCountries($lang_type === 'ko' ? Rhymix\Framework\i18n::SORT_NAME_KOREAN : Rhymix\Framework\i18n::SORT_NAME_ENGLISH); + $country_name = $lang_type === 'ko' ? $country_list[$value]->name_korean : $country_list[$value]->name_english; + $value = $country_name; + break; + case 'language' : + $supported_lang = Rhymix\Framework\Lang::getSupportedList(); + $value = $supported_lang[$value]['name']; + break; + */ case 'text' : case 'homepage' : case 'email_address' : @@ -775,8 +942,14 @@ class memberModel extends member $extend_form_list[$srl]->value = $value; - if($member_info->{'open_'.$column_name}=='Y') $extend_form_list[$srl]->is_opened = true; - else $extend_form_list[$srl]->is_opened = false; + if(isset($member_info->{'open_'.$column_name}) && $member_info->{'open_'.$column_name} === 'Y') + { + $extend_form_list[$srl]->is_opened = true; + } + else + { + $extend_form_list[$srl]->is_opened = false; + } } return $extend_form_list; } @@ -784,7 +957,7 @@ class memberModel extends member /** * @brief Get a join form */ - function getJoinForm($member_join_form_srl) + public static function getJoinForm($member_join_form_srl) { $args = new stdClass(); $args->member_join_form_srl = $member_join_form_srl; @@ -792,12 +965,17 @@ class memberModel extends member $join_form = $output->data; if(!$join_form) return NULL; - $column_type = $join_form->column_type; - $default_value = $join_form->default_value; - - if(in_array($column_type, array('checkbox','select','radio'))) + if (in_array($join_form->column_type, ['checkbox','select','radio'])) { - $join_form->default_value = unserialize($default_value); + if (isset($join_form->options) && $join_form->options !== '') + { + $join_form->options = json_decode($join_form->options, true); + } + elseif (preg_match('/^a:\d+:\{i:/', $join_form->default_value)) + { + $join_form->options = unserialize($join_form->default_value); + $join_form->default_value = ''; + } } else { @@ -810,9 +988,9 @@ class memberModel extends member /** * @brief Get a list of denied IDs */ - function getDeniedIDList() + public static function getDeniedIDList() { - if(!$this->denied_id_list) + if(!isset(self::$_denied_id_list)) { $args = new stdClass(); $args->sort_index = "list_order"; @@ -820,20 +998,20 @@ class memberModel extends member $args->list_count = 40; $args->page_count = 10; - $output = executeQuery('member.getDeniedIDList', $args); - $this->denied_id_list = $output; + $output = executeQueryArray('member.getDeniedIDList', $args); + self::$_denied_id_list = $output; } - return $this->denied_id_list; + return self::$_denied_id_list; } - function getDeniedIDs() + public static function getDeniedIDs() { $output = executeQueryArray('member.getDeniedIDs'); if(!$output->toBool()) return array(); return $output->data; } - function getDeniedNickNames() + public static function getDeniedNickNames() { $output = executeQueryArray('member.getDeniedNickNames'); if(!$output->toBool()) @@ -844,24 +1022,24 @@ class memberModel extends member return $output->data; } - function getManagedEmailHosts() + public static function getManagedEmailHosts() { - static $output; - if(isset($output->data)) return $output->data; + if(isset(self::$_managed_email_hosts)) { + return self::$_managed_email_hosts; + } $output = executeQueryArray('member.getManagedEmailHosts'); if(!$output->toBool()) { - $output->data = array(); - return array(); + return self::$_managed_email_hosts = array(); } - return $output->data; + return self::$_managed_email_hosts = $output->data; } /** * @brief Verify if ID is denied */ - function isDeniedID($user_id) + public static function isDeniedID($user_id) { $args = new stdClass(); $args->user_id = $user_id; @@ -873,7 +1051,7 @@ class memberModel extends member /** * @brief Verify if nick name is denied */ - function isDeniedNickName($nickName) + public static function isDeniedNickName($nickName) { $args = new stdClass(); $args->nick_name = $nickName; @@ -889,13 +1067,12 @@ class memberModel extends member /** * @brief Verify if email_host from email_address is denied */ - function isDeniedEmailHost($email_address) + public static function isDeniedEmailHost($email_address) { $email_address = trim($email_address); - $oMemberModel = &getModel('member'); - $config = $oMemberModel->getMemberConfig(); + $config = self::getMemberConfig(); $emailhost_check = $config->emailhost_check; - $managedHosts = $oMemberModel->getManagedEmailHosts(); + $managedHosts = self::getManagedEmailHosts(); if(count($managedHosts) < 1) return FALSE; static $return; @@ -931,22 +1108,29 @@ class memberModel extends member /** * @brief Get information of the profile image */ - function getProfileImage($member_srl) + public static function getProfileImage($member_srl) { - if(!isset($GLOBALS['__member_info__']['profile_image'][$member_srl])) + if(!isset($GLOBALS['__member_info__'])) + { + $GLOBALS['__member_info__'] = []; + } + if(!isset($GLOBALS['__member_info__']['profile_image'])) + { + $GLOBALS['__member_info__']['profile_image'] = []; + } + if(!array_key_exists($member_srl, $GLOBALS['__member_info__']['profile_image'])) { $GLOBALS['__member_info__']['profile_image'][$member_srl] = null; - $exts = array('gif','jpg','png'); - for($i=0;$i<3;$i++) + foreach(['jpg', 'jpeg', 'gif', 'png'] as $ext) { - $image_name_file = sprintf('files/member_extra_info/profile_image/%s%d.%s', getNumberingPath($member_srl), $member_srl, $exts[$i]); - if(file_exists($image_name_file)) + $image_name_file = sprintf('files/member_extra_info/profile_image/%s%d.%s', getNumberingPath($member_srl), $member_srl, $ext); + if(file_exists(\RX_BASEDIR . $image_name_file)) { - list($width, $height, $type, $attrs) = getimagesize($image_name_file); + list($width, $height, $type, $attrs) = getimagesize(\RX_BASEDIR . $image_name_file); $info = new stdClass(); $info->width = $width; $info->height = $height; - $info->src = Context::getRequestUri().$image_name_file . '?' . date('YmdHis', filemtime($image_name_file)); + $info->src = \RX_BASEURL . $image_name_file . '?t=' . filemtime(\RX_BASEDIR . $image_name_file); $info->file = './'.$image_name_file; $GLOBALS['__member_info__']['profile_image'][$member_srl] = $info; break; @@ -960,22 +1144,33 @@ class memberModel extends member /** * @brief Get the image name */ - function getImageName($member_srl) + public static function getImageName($member_srl) { + if(!isset($GLOBALS['__member_info__'])) + { + $GLOBALS['__member_info__'] = []; + } + if(!isset($GLOBALS['__member_info__']['image_name'])) + { + $GLOBALS['__member_info__']['image_name'] = []; + } if(!isset($GLOBALS['__member_info__']['image_name'][$member_srl])) { $image_name_file = sprintf('files/member_extra_info/image_name/%s%d.gif', getNumberingPath($member_srl), $member_srl); - if(file_exists($image_name_file)) + if(file_exists(\RX_BASEDIR . $image_name_file)) { - list($width, $height, $type, $attrs) = getimagesize($image_name_file); + list($width, $height, $type, $attrs) = getimagesize(\RX_BASEDIR . $image_name_file); $info = new stdClass; $info->width = $width; $info->height = $height; - $info->src = Context::getRequestUri().$image_name_file. '?' . date('YmdHis', filemtime($image_name_file)); + $info->src = \RX_BASEURL . $image_name_file. '?t=' . filemtime(\RX_BASEDIR . $image_name_file); $info->file = './'.$image_name_file; $GLOBALS['__member_info__']['image_name'][$member_srl] = $info; } - else $GLOBALS['__member_info__']['image_name'][$member_srl] = null; + else + { + $GLOBALS['__member_info__']['image_name'][$member_srl] = ''; + } } return $GLOBALS['__member_info__']['image_name'][$member_srl]; } @@ -983,22 +1178,33 @@ class memberModel extends member /** * @brief Get the image mark */ - function getImageMark($member_srl) + public static function getImageMark($member_srl) { + if(!isset($GLOBALS['__member_info__'])) + { + $GLOBALS['__member_info__'] = []; + } + if(!isset($GLOBALS['__member_info__']['image_mark'])) + { + $GLOBALS['__member_info__']['image_mark'] = []; + } if(!isset($GLOBALS['__member_info__']['image_mark'][$member_srl])) { $image_mark_file = sprintf('files/member_extra_info/image_mark/%s%d.gif', getNumberingPath($member_srl), $member_srl); - if(file_exists($image_mark_file)) + if(file_exists(\RX_BASEDIR . $image_mark_file)) { - list($width, $height, $type, $attrs) = getimagesize($image_mark_file); + list($width, $height, $type, $attrs) = getimagesize(\RX_BASEDIR . $image_mark_file); $info = new stdClass; $info->width = $width; $info->height = $height; - $info->src = Context::getRequestUri().$image_mark_file . '?' . date('YmdHis', filemtime($image_mark_file)); + $info->src = \RX_BASEURL . $image_mark_file . '?t=' . filemtime(\RX_BASEDIR . $image_mark_file); $info->file = './'.$image_mark_file; $GLOBALS['__member_info__']['image_mark'][$member_srl] = $info; } - else $GLOBALS['__member_info__']['image_mark'][$member_srl] = null; + else + { + $GLOBALS['__member_info__']['image_mark'][$member_srl] = ''; + } } return $GLOBALS['__member_info__']['image_mark'][$member_srl]; @@ -1008,23 +1214,31 @@ class memberModel extends member /** * @brief Get the image mark of the group */ - function getGroupImageMark($member_srl,$site_srl=0) + public static function getGroupImageMark($member_srl) { + if(!isset($GLOBALS['__member_info__'])) + { + $GLOBALS['__member_info__'] = []; + } + if(!isset($GLOBALS['__member_info__']['group_image_mark'])) + { + $GLOBALS['__member_info__']['group_image_mark'] = []; + } if(!isset($GLOBALS['__member_info__']['group_image_mark'][$member_srl])) { - $oModuleModel = getModel('module'); - $config = $oModuleModel->getModuleConfig('member'); + $config = ModuleModel::getModuleConfig('member'); if($config->group_image_mark!='Y') { return null; } - $member_group = $this->getMemberGroups($member_srl,$site_srl); - $groups_info = $this->getGroups($site_srl); + + $info = null; + $member_group = self::getMemberGroups($member_srl); + $groups_info = self::getGroups(); if(count($member_group) > 0 && is_array($member_group)) { $memberGroups = array_keys($member_group); - - foreach($groups_info as $group_srl=>$group_info) + foreach($groups_info as $group_srl => $group_info) { if(in_array($group_srl, $memberGroups)) { @@ -1039,7 +1253,7 @@ class memberModel extends member $localpath = str_replace('/./', '/', parse_url($info->src, PHP_URL_PATH)); if(file_exists($_SERVER['DOCUMENT_ROOT'] . $localpath)) { - $info->src = $localpath . '?' . date('YmdHis', filemtime($_SERVER['DOCUMENT_ROOT'] . $localpath)); + $info->src = $localpath . '?t=' . filemtime($_SERVER['DOCUMENT_ROOT'] . $localpath); } } $GLOBALS['__member_info__']['group_image_mark'][$member_srl] = $info; @@ -1048,9 +1262,11 @@ class memberModel extends member } } } - if (!$info) $GLOBALS['__member_info__']['group_image_mark'][$member_srl] == 'N'; + if (!$info) + { + $GLOBALS['__member_info__']['group_image_mark'][$member_srl] = ''; + } } - if ($GLOBALS['__member_info__']['group_image_mark'][$member_srl] == 'N') return null; return $GLOBALS['__member_info__']['group_image_mark'][$member_srl]; } @@ -1058,23 +1274,31 @@ class memberModel extends member /** * @brief Get user's signature */ - function getSignature($member_srl) + public static function getSignature($member_srl) { + if(!isset($GLOBALS['__member_info__'])) + { + $GLOBALS['__member_info__'] = []; + } + if(!isset($GLOBALS['__member_info__']['signature'])) + { + $GLOBALS['__member_info__']['signature'] = []; + } if(!isset($GLOBALS['__member_info__']['signature'][$member_srl])) { $filename = sprintf('files/member_extra_info/signature/%s%d.signature.php', getNumberingPath($member_srl), $member_srl); if(file_exists($filename)) { $signature = preg_replace('/<\?.*\?>/', '', FileHandler::readFile($filename)); - + // retroact - $config = getModel('member')->getMemberConfig(); + $config = self::getMemberConfig(); if($config->signature_html_retroact == 'Y' && $config->signature_html == 'N' && preg_match('/<[^br]+>/i', $signature)) { $signature = preg_replace('/(\r?\n)+/', "\n", $signature); - return getController('member')->putSignature($member_srl, $signature); + return MemberController::getInstance()->putSignature($member_srl, $signature); } - + $GLOBALS['__member_info__']['signature'][$member_srl] = $signature; } else @@ -1082,7 +1306,7 @@ class memberModel extends member $GLOBALS['__member_info__']['signature'][$member_srl] = ''; } } - + return $GLOBALS['__member_info__']['signature'][$member_srl]; } @@ -1093,14 +1317,14 @@ class memberModel extends member * @param int $member_srl Set this to member_srl when comparing a member's password (optional) * @return bool */ - function isValidPassword($hashed_password, $password_text, $member_srl=null) + public static function isValidPassword($hashed_password, $password_text, $member_srl=null) { // False if no password in entered - if(!$password_text) + if(!$hashed_password || !$password_text) { return false; } - + // Check the password $password_match = false; $current_algorithm = false; @@ -1118,9 +1342,9 @@ class memberModel extends member { return false; } - + // Update the encryption method if necessary - $config = $this->getMemberConfig(); + $config = self::getMemberConfig(); if($member_srl > 0 && $config->password_hashing_auto_upgrade != 'N') { $required_algorithm = Rhymix\Framework\Password::getDefaultAlgorithm(); @@ -1141,68 +1365,67 @@ class memberModel extends member $need_upgrade = false; } } - + if ($need_upgrade) { $args = new stdClass(); $args->member_srl = $member_srl; - $args->hashed_password = $this->hashPassword($password_text, $required_algorithm); - $oMemberController = getController('member'); - $oMemberController->updateMemberPassword($args); + $args->hashed_password = self::hashPassword($password_text, $required_algorithm); + MemberController::getInstance()->updateMemberPassword($args); } } - + return true; } - + /** * @brief Create a hash of plain text password * @param string $password_text The password to hash * @param string $algorithm The algorithm to use (optional, only set this when you want to use a non-default algorithm) * @return string */ - function hashPassword($password_text, $algorithm = null) + public static function hashPassword($password_text, $algorithm = null) { return Rhymix\Framework\Password::hashPassword($password_text, $algorithm); } - - function checkPasswordStrength($password, $strength) + + public static function checkPasswordStrength($password, $strength) { $logged_info = Context::get('logged_info'); if($logged_info->is_admin == 'Y') return true; - + if($strength == NULL) { - $config = $this->getMemberConfig(); + $config = self::getMemberConfig(); $strength = $config->password_strength?$config->password_strength:'normal'; } - + $length = strlen($password); - + switch ($strength) { case 'high': if($length < 8 || !preg_match('/[^a-zA-Z0-9]/', $password)) return false; /* no break */ - + case 'normal': if($length < 6 || !preg_match('/[a-zA-Z]/', $password) || !preg_match('/[0-9]/', $password)) return false; break; - + case 'low': if($length < 4) return false; break; } - + return true; } - - function getAdminGroupSrl($site_srl = 0) + + public static function getAdminGroupSrl() { $groupSrl = 0; - $output = $this->getGroups($site_srl); + $output = self::getGroups(); if(is_array($output)) { - foreach($output AS $key=>$value) + foreach($output AS $value) { if($value->is_admin == 'Y') { @@ -1213,14 +1436,48 @@ class memberModel extends member } return $groupSrl; } - - function getMemberModifyNicknameLog($page = 1, $member_srl = null) + + public static function getMemberModifyNicknameLog($page = 1, $member_srl = null) { + $search_keyword = Context::get('search_keyword'); + $search_target = Context::get('search_target'); + + // $this->user 에 재대로 된 회원 정보가 들어 가지 않음. + $logged_info = Context::get('logged_info'); + $args = new stdClass(); - $args->member_srl = $member_srl; $args->page = $page; - $output = executeQueryArray('member.getMemberModifyNickName', $args); - + if($logged_info->is_admin == 'Y') + { + if($search_target && $search_keyword) + { + switch ($search_target) + { + case "before": + $args->before_nick_name = $search_keyword; + break; + case "after": + $args->after_nick_name = $search_keyword; + break; + case "user_id": + if($search_keyword) $search_keyword = str_replace(' ','%',$search_keyword); + $args->user_id = $search_keyword; + break; + case "member_srl": + $args->member_srl = intval($search_keyword); + break; + default: + break; + } + $output = executeQuery('member.getMemberModifyNickName', $args); + + return $output; + } + } + + $args->member_srl = $member_srl; + $output = executeQuery('member.getMemberModifyNickName', $args); + return $output; } } diff --git a/modules/member/member.view.php b/modules/member/member.view.php index b0010ad7d..5534ea0b3 100644 --- a/modules/member/member.view.php +++ b/modules/member/member.view.php @@ -5,11 +5,10 @@ * @author NAVER (developers@xpressengine.com) * @brief View class of member module */ -class memberView extends member +class MemberView extends Member { - var $group_list = NULL; // /< Group list information - var $member_info = NULL; // /< Member information of the user - var $skin = 'default'; + public $member_config; + public $member_info; /** * @brief Initialization @@ -17,41 +16,134 @@ class memberView extends member function init() { // Get the member configuration - $oMemberModel = getModel('member'); - $this->member_config = $oMemberModel->getMemberConfig(); + $this->member_config = MemberModel::getMemberConfig(); Context::set('member_config', $this->member_config); $oSecurity = new Security(); $oSecurity->encodeHTML('member_config.signupForm..'); - $skin = $this->member_config->skin; - // Set the template path - if(!$skin) + // Set layout and skin paths + $this->setLayoutAndTemplatePaths('P', $this->member_config); + } + + /** + * Check the referer for login and signup pages. + */ + public function checkRefererUrl() + { + // Get the referer URL from Context var or HTTP header. + $referer_url = Context::get('referer_url') ?: ($_SERVER['HTTP_REFERER'] ?? ''); + + // Check if the referer is an internal URL. + $is_valid_referer = !empty($referer_url) && Rhymix\Framework\URL::isInternalURL($referer_url); + + // Check if the referer is the login or signup page, to prevent redirect loops. + if (preg_match('!\b(dispMemberLoginForm|dispMemberSignUpForm|dispMemberFindAccount|dispMemberResendAuthMail|procMember)!', $referer_url)) { - $skin = 'default'; - $template_path = sprintf('%sskins/%s', $this->module_path, $skin); + $is_valid_referer = false; + } + if (preg_match('!/(auth|login|signup)\b!', $referer_url)) + { + $is_valid_referer = false; + } + + // Store valid referer info in the session. + if ($is_valid_referer) + { + return $_SESSION['member_auth_referer'] = $referer_url; + } + elseif (isset($_SESSION['member_auth_referer'])) + { + return $_SESSION['member_auth_referer']; + } + elseif ($this->mid && !empty($this->member_config->mid) && $this->mid === $this->member_config->mid) + { + return getNotEncodedUrl(''); } else { - //check theme - $config_parse = explode('|@|', $skin); - if (count($config_parse) > 1) - { - $template_path = sprintf('./themes/%s/modules/member/', $config_parse[0]); - } - else - { - $template_path = sprintf('%sskins/%s', $this->module_path, $skin); - } + return getNotEncodedUrl('act', ''); } - // Template path - $this->setTemplatePath($template_path); + } - $oLayoutModel = getModel('layout'); - $layout_info = $oLayoutModel->getLayout($this->member_config->layout_srl); - if($layout_info) + /** + * Check redirect to member mid. + */ + public function checkMidAndRedirect() + { + if (!$this->member_config) { - $this->module_info->layout_srl = $this->member_config->layout_srl; - $this->setLayoutPath($layout_info->path); + $this->member_config = MemberModel::getMemberConfig(); + } + if (empty($this->member_config->mid) || empty($this->member_config->force_mid)) + { + return true; + } + if (ModuleModel::getModuleInfoByMid($this->member_config->mid)->module !== $this->module) + { + return true; + } + if (Context::get('mid') === $this->member_config->mid) + { + return true; + } + + $vars = get_object_vars(Context::getRequestVars()); + $vars['mid'] = $this->member_config->mid; + $this->setRedirectUrl(getNotEncodedUrl($vars)); + return false; + } + + /** + * Get the module_srl for the member mid. + * + * @return int + */ + public function getMemberModuleSrl(): int + { + if (!$this->member_config) + { + $this->member_config = MemberModel::getMemberConfig(); + } + if (!empty($this->member_config->mid)) + { + return ModuleModel::getModuleInfoByMid($this->member_config->mid)->module_srl ?? 0; + } + else + { + return 0; + } + } + + /** + * Set the browser title for a page belonging to the member menu. + * + * @param string $title + * @return void + */ + public static function setMemberPageBrowserTitle(string $title): void + { + $seo_title = config('seo.subpage_title') ?: '$SITE_TITLE - $SUBPAGE_TITLE'; + $seo_title = Context::replaceUserLang($seo_title); + Context::setBrowserTitle($seo_title, array( + 'site_title' => Context::getSiteTitle(), + 'site_subtitle' => Context::getSiteSubtitle(), + 'subpage_title' => $title, + 'page' => Context::get('page') ?: 1, + )); + } + + /** + * Module index + */ + public function dispMemberIndex() + { + if ($this->user->isMember()) + { + $this->setRedirectUrl(getNotEncodedUrl(['mid' => $this->mid, 'act' => 'dispMemberInfo'])); + } + else + { + $this->setRedirectUrl(getNotEncodedUrl(['mid' => $this->mid, 'act' => 'dispMemberLoginForm'])); } } @@ -60,24 +152,30 @@ class memberView extends member */ function dispMemberInfo() { - $oMemberModel = getModel('member'); - $logged_info = Context::get('logged_info'); + if (!$this->checkMidAndRedirect()) + { + return; + } + // Don't display member info to non-logged user - if(!$logged_info->member_srl) throw new Rhymix\Framework\Exceptions\MustLogin; - - $member_srl = Context::get('member_srl'); - if(!$member_srl && Context::get('is_logged')) + $logged_info = Context::get('logged_info'); + if(!$logged_info->member_srl) { - $member_srl = $logged_info->member_srl; - } - elseif(!$member_srl) - { - return $this->dispMemberSignUpForm(); + throw new Rhymix\Framework\Exceptions\MustLogin; + } + + $member_srl = Context::get('member_srl') ?: $logged_info->member_srl; + if(!$member_srl) + { + throw new Rhymix\Framework\Exceptions\MustLogin; + } + + $member_info = MemberModel::getMemberInfoByMemberSrl($member_srl); + if (!$member_info->member_srl) + { + throw new Rhymix\Framework\Exceptions\TargetNotFound; } - $site_module_info = Context::get('site_module_info'); - $columnList = array('member_srl', 'user_id', 'email_address', 'user_name', 'nick_name', 'homepage', 'blog', 'birthday', 'regdate', 'last_login', 'extra_vars'); - $member_info = $oMemberModel->getMemberInfoByMemberSrl($member_srl, $site_module_info->site_srl, $columnList); unset($member_info->password); unset($member_info->email_id); unset($member_info->email_host); @@ -85,15 +183,26 @@ class memberView extends member if($logged_info->is_admin != 'Y' && ($member_info->member_srl != $logged_info->member_srl)) { list($email_id, $email_host) = explode('@', $member_info->email_address); - $protect_id = substr($email_id, 0, 2) . str_repeat('*', strlen($email_id)-2); + if (strlen($email_id) <= 3) + { + $protect_id = '***'; + } + else + { + $protect_id = substr($email_id, 0, 2) . str_repeat('*', strlen($email_id) - 2); + } $member_info->email_address = sprintf('%s@%s', $protect_id, $email_host); } - if(!$member_info->member_srl) return $this->dispMemberSignUpForm(); + foreach ($member_info->group_list ?? [] as $key => $val) + { + $member_info->group_list[$key] = Context::replaceUserLang($val, true); + } + self::setMemberPageBrowserTitle(lang('cmd_view_member_info')); Context::set('memberInfo', get_object_vars($member_info)); - $extendForm = $oMemberModel->getCombineJoinForm($member_info); + $extendForm = MemberModel::getCombineJoinForm($member_info); unset($extendForm->find_member_account); unset($extendForm->find_member_answer); Context::set('extend_form_list', $extendForm); @@ -128,7 +237,7 @@ class memberView extends member if($formInfo->isDefaultForm) { - $item->title = lang($formInfo->name); + $item->title = $formInfo->title; $item->value = $memberInfo->{$formInfo->name}; if($formInfo->name == 'profile_image' && $memberInfo->profile_image) @@ -146,35 +255,33 @@ class memberView extends member $target = $memberInfo->image_mark; $item->value = '' . lang('member.image_mark') . ''; } - elseif($formInfo->name == 'birthday' && $memberInfo->birthday) + elseif($formInfo->name == 'birthday' && $memberInfo->birthday && preg_match('/^[0-9]{8}/', $item->value)) { - $item->value = zdate($item->value, 'Y-m-d'); + $item->value = sprintf('%s-%s-%s', substr($item->value, 0, 4), substr($item->value, 4, 2), substr($item->value, 6, 2)); + } + elseif($formInfo->name == 'phone_number' && $memberInfo->phone_number) + { + if($memberConfig->phone_number_hide_country !== 'Y') + { + $item->value = Rhymix\Framework\i18n::formatPhoneNumber($item->value, $memberInfo->phone_country); + } + elseif($memberConfig->phone_number_default_country === 'KOR' && ($memberInfo->phone_country === 'KOR' || $memberInfo->phone_country == '82')) + { + $item->value = Rhymix\Framework\Korea::formatPhoneNumber($item->value); + } } } else { - $item->title = $extendFormInfo[$formInfo->member_join_form_srl]->column_title; - $orgValue = $extendFormInfo[$formInfo->member_join_form_srl]->value; - if($formInfo->type=='tel' && is_array($orgValue)) - { - $item->value = implode('-', $orgValue); - } - elseif($formInfo->type=='kr_zip' && is_array($orgValue)) - { - $item->value = implode(' ', $orgValue); - } - elseif($formInfo->type=='checkbox' && is_array($orgValue)) - { - $item->value = implode(", ",$orgValue); - } - elseif($formInfo->type=='date') - { - $item->value = zdate($orgValue, "Y-m-d"); - } - else - { - $item->value = nl2br($orgValue); - } + $item->title = $extendFormInfo[$formInfo->member_join_form_srl]->column_title ?? null; + $extvalue = new Rhymix\Modules\Extravar\Models\Value(0, 1, '', $formInfo->type); + $extvalue->parent_type = 'member'; + $extvalue->input_name = $formInfo->name; + $extvalue->input_id = $formInfo->name; + $extvalue->value = $extendFormInfo[$formInfo->member_join_form_srl]->value ?? null; + $extvalue->default = $extendFormInfo[$formInfo->member_join_form_srl]->default_value ?? null; + $extvalue->options = $extendFormInfo[$formInfo->member_join_form_srl]->options ?? null; + $item->value = $extvalue->getValueHTML(); } $displayDatas[] = $item; @@ -191,34 +298,63 @@ class memberView extends member */ function dispMemberSignUpForm() { - //setcookie for redirect url in case of going to member sign up - setcookie("XE_REDIRECT_URL", $_SERVER['HTTP_REFERER'], 0, '/', null, !!config('session.use_ssl_cookies')); + // Check referer URL + $referer_url = $this->checkRefererUrl(); + // Redirect to member mid if necessary. + if (!$this->checkMidAndRedirect()) + { + return; + } + + // Return to previous screen if already logged in. + if($this->user->isMember()) + { + $this->setRedirectUrl($referer_url); + return; + } + + // call a trigger (before) $member_config = $this->member_config; - - $oMemberModel = getModel('member'); - // Get the member information if logged-in - if($oMemberModel->isLogged()) throw new Rhymix\Framework\Exception('msg_already_logged'); - // call a trigger (before) $trigger_output = ModuleHandler::triggerCall('member.dispMemberSignUpForm', 'before', $member_config); if(!$trigger_output->toBool()) return $trigger_output; + // Error appears if the member is not allowed to join - if($member_config->enable_join != 'Y') throw new Rhymix\Framework\Exceptions\FeatureDisabled('msg_signup_disabled'); - + if ($member_config->enable_join !== 'Y') + { + if (!empty($member_config->enable_join_key)) + { + if (strpos(escape(rawurldecode(\RX_REQUEST_URL)), $member_config->enable_join_key) !== false) + { + $_SESSION['signup_allowed'] = true; + } + else + { + $_SESSION['signup_allowed'] = false; + throw new Rhymix\Framework\Exceptions\FeatureDisabled('msg_signup_disabled'); + } + } + else + { + $_SESSION['signup_allowed'] = false; + throw new Rhymix\Framework\Exceptions\FeatureDisabled('msg_signup_disabled'); + } + } + $formTags = getAdminView('member')->_getMemberInputTag(); Context::set('formTags', $formTags); Context::set('email_confirmation_required', $member_config->enable_confirm); - + // Editor of the module set for signing by calling getEditor foreach($formTags as $formTag) { if($formTag->name == 'signature') { - $option = new stdClass; + $option = ModuleModel::getModuleConfig('editor') ?: new stdClass; $option->primary_key_name = 'member_srl'; $option->content_key_name = 'signature'; $option->allow_html = $member_config->signature_html !== 'N'; - $option->allow_fileupload = false; + $option->allow_fileupload = $member_config->member_allow_fileupload === 'Y'; $option->enable_autosave = false; $option->enable_default_component = true; $option->enable_component = false; @@ -229,27 +365,49 @@ class memberView extends member $option->editor_toolbar_hide = 'Y'; $option->editor_skin = $member_config->signature_editor_skin; $option->sel_editor_colorset = $member_config->sel_editor_colorset; - + if (!$option->allow_html) + { + $option->editor_skin = 'textarea'; + } + if ($option->allow_fileupload) + { + $option->module_srl = $this->getMemberModuleSrl(); + $option->upload_target_type = 'sig'; + } + if (!empty($member_config->member_max_filesize)) + { + $option->allowed_filesize = $member_config->member_max_filesize * 1024; + } + Context::set('editor', getModel('editor')->getEditor(0, $option)); } } - + + $identifier = array_first($member_config->identifiers); $identifierForm = new stdClass; - $identifierForm->title = lang($member_config->identifier); - $identifierForm->name = $member_config->identifier; + $identifierForm->title = lang($identifier); + $identifierForm->name = $identifier; Context::set('identifierForm', $identifierForm); - + $this->addExtraFormValidatorMessage(); - + + // Set a copy of the agreement for compatibility with old skins + $member_config->agreement = $member_config->agreements[1]->content ?? ''; + // Set a template file + self::setMemberPageBrowserTitle(lang('cmd_signup')); $this->setTemplateFile('signup_form'); } function dispMemberModifyInfoBefore() { + if (!$this->checkMidAndRedirect()) + { + return; + } + $logged_info = Context::get('logged_info'); - $oMemberModel = getModel('member'); - if(!$oMemberModel->isLogged() || empty($logged_info)) + if(!$logged_info->member_srl) { throw new Rhymix\Framework\Exceptions\MustLogin; } @@ -266,7 +424,7 @@ class memberView extends member if ($this->member_config->identifier == 'email_address') { Context::set('identifierTitle', lang('email_address')); - Context::set('identifierValue', $logged_info->email_address); + Context::set('identifierValue', $logged_info->email_address); } else { @@ -274,45 +432,50 @@ class memberView extends member Context::set('identifierValue', $logged_info->user_id); } + self::setMemberPageBrowserTitle(lang('cmd_modify_member_info')); $this->setTemplateFile('rechecked_password'); } /** * @brief Modify member information */ - function dispMemberModifyInfo() + function dispMemberModifyInfo() { - if($_SESSION['rechecked_password_step'] != 'VALIDATE_PASSWORD' && $_SESSION['rechecked_password_step'] != 'INPUT_DATA') + if (!isset($_SESSION['rechecked_password_step']) || !in_array($_SESSION['rechecked_password_step'], ['VALIDATE_PASSWORD', 'INPUT_DATA'])) { $this->dispMemberModifyInfoBefore(); return; } + if (!$this->checkMidAndRedirect()) + { + return; + } + $_SESSION['rechecked_password_step'] = 'INPUT_DATA'; $member_config = $this->member_config; - $oMemberModel = getModel('member'); // A message appears if the user is not logged-in - if(!$oMemberModel->isLogged()) throw new Rhymix\Framework\Exceptions\MustLogin; + if(!$this->user->member_srl) throw new Rhymix\Framework\Exceptions\MustLogin; $logged_info = Context::get('logged_info'); $member_srl = $logged_info->member_srl; $columnList = array('member_srl', 'user_id', 'user_name', 'nick_name', 'email_address', 'find_account_answer', 'homepage', 'blog', 'birthday', 'allow_mailing'); - $member_info = $oMemberModel->getMemberInfoByMemberSrl($member_srl, 0, $columnList); - $member_info->signature = $oMemberModel->getSignature($member_srl); + $member_info = MemberModel::getMemberInfoByMemberSrl($member_srl, 0, $columnList); + $member_info->signature = MemberModel::getSignature($member_srl); Context::set('member_info', $member_info); - + $formTags = getAdminView('member')->_getMemberInputTag($member_info); Context::set('formTags', $formTags); - + // Editor of the module set for signing by calling getEditor foreach($formTags as $formTag) { if($formTag->name == 'signature') { - $option = new stdClass; + $option = ModuleModel::getModuleConfig('editor') ?: new stdClass; $option->primary_key_name = 'member_srl'; $option->content_key_name = 'signature'; $option->allow_html = $member_config->signature_html !== 'N'; @@ -327,20 +490,35 @@ class memberView extends member $option->editor_toolbar_hide = 'Y'; $option->editor_skin = $member_config->signature_editor_skin; $option->sel_editor_colorset = $member_config->sel_editor_colorset; - + if (!$option->allow_html) + { + $option->editor_skin = 'textarea'; + } + if ($option->allow_fileupload) + { + $option->module_srl = $this->getMemberModuleSrl(); + $option->upload_target_type = 'sig'; + } + if (!empty($member_config->member_max_filesize)) + { + $option->allowed_filesize = $member_config->member_max_filesize * 1024; + } + Context::set('editor', getModel('editor')->getEditor($member_info->member_srl, $option)); } } - + + $identifier = array_first($member_config->identifiers); $identifierForm = new stdClass; - $identifierForm->title = lang($member_config->identifier); - $identifierForm->name = $member_config->identifier; - $identifierForm->value = $member_info->{$member_config->identifier}; + $identifierForm->title = lang($identifier); + $identifierForm->name = $identifier; + $identifierForm->value = $member_info->$identifier; Context::set('identifierForm', $identifierForm); - + $this->addExtraFormValidatorMessage(); - + // Set a template file + self::setMemberPageBrowserTitle(lang('cmd_modify_member_info')); $this->setTemplateFile('modify_info'); } @@ -354,27 +532,44 @@ class memberView extends member throw new Rhymix\Framework\Exceptions\FeatureDisabled; } + if (!$this->checkMidAndRedirect()) + { + return; + } + // A message appears if the user is not logged-in if(!Context::get('is_logged')) { throw new Rhymix\Framework\Exceptions\MustLogin; } - $logged_info = Context::get('logged_info'); - $member_srl = $logged_info->member_srl; + $args = new stdClass; + $args->list_count = 20; + $args->page_count = 5; + $args->page = intval(Context::get('page')) ?: 1; + if(in_array(Context::get('search_target'), array('title', 'title_content', 'content'))) + { + $args->search_target = Context::get('search_target'); + $args->search_keyword = escape(trim(utf8_normalize_spaces(Context::get('search_keyword')))); + } + $args->member_srl = array($this->user->member_srl, $this->user->member_srl * -1); + $args->module_srl = intval(Context::get('selected_module_srl')) ?: null; + $args->sort_index = 'list_order'; + $args->statusList = array('PUBLIC', 'SECRET'); + $args->use_division = false; - $module_srl = Context::get('module_srl'); - Context::set('module_srl',Context::get('selected_module_srl')); - Context::set('search_target','member_srl'); - Context::set('search_keyword',$member_srl); - - $oDocumentAdminView = getAdminView('document'); - $oDocumentAdminView->dispDocumentAdminList(); + $columnList = array('document_srl', 'module_srl', 'category_srl', 'member_srl', 'title', 'nick_name', 'comment_count', 'trackback_count', 'readed_count', 'voted_count', 'blamed_count', 'regdate', 'ipaddress', 'status'); + $output = DocumentModel::getDocumentList($args, false, false, $columnList); + Context::set('total_count', $output->total_count); + Context::set('total_page', $output->total_page); + Context::set('page', $output->page); + Context::set('page_navigation', $output->page_navigation); + Context::set('document_list', $output->data); $oSecurity = new Security(); $oSecurity->encodeHTML('document_list...title', 'search_target', 'search_keyword'); - Context::set('module_srl', $module_srl); + self::setMemberPageBrowserTitle(lang('cmd_view_own_document')); $this->setTemplateFile('document_list'); } @@ -388,25 +583,41 @@ class memberView extends member throw new Rhymix\Framework\Exceptions\FeatureDisabled; } - $oMemberModel = getModel('member'); + if (!$this->checkMidAndRedirect()) + { + return; + } + // A message appears if the user is not logged-in - if(!$oMemberModel->isLogged()) throw new Rhymix\Framework\Exceptions\MustLogin; + if(!Context::get('is_logged')) + { + throw new Rhymix\Framework\Exceptions\MustLogin; + } - $logged_info = Context::get('logged_info'); - $member_srl = $logged_info->member_srl; + $args = new stdClass; + $args->list_count = 20; + $args->page_count = 5; + $args->page = intval(Context::get('page')) ?: 1; + if(Context::get('search_keyword')) + { + $args->search_target = 'content'; + $args->search_keyword = escape(trim(utf8_normalize_spaces(Context::get('search_keyword')))); + } + $args->member_srl = array($this->user->member_srl, $this->user->member_srl * -1); + $args->module_srl = intval(Context::get('selected_module_srl')) ?: null; + $args->sort_index = 'list_order'; - $module_srl = Context::get('module_srl'); - Context::set('module_srl',Context::get('selected_module_srl')); - Context::set('search_target','member_srl'); - Context::set('search_keyword',$member_srl); - - $oCommentAdminView = getAdminView('comment'); - $oCommentAdminView->dispCommentAdminList(); + $output = CommentModel::getTotalCommentList($args); + Context::set('total_count', $output->total_count); + Context::set('total_page', $output->total_page); + Context::set('page', $output->page); + Context::set('page_navigation', $output->page_navigation); + Context::set('comment_list', $output->data); $oSecurity = new Security(); $oSecurity->encodeHTML('search_target', 'search_keyword'); - Context::set('module_srl', $module_srl); + self::setMemberPageBrowserTitle(lang('cmd_view_own_comment')); $this->setTemplateFile('comment_list'); } @@ -420,12 +631,19 @@ class memberView extends member throw new Rhymix\Framework\Exceptions\FeatureDisabled; } - $oMemberModel = getModel('member'); + if (!$this->checkMidAndRedirect()) + { + return; + } + // A message appears if the user is not logged-in - if(!$oMemberModel->isLogged()) throw new Rhymix\Framework\Exceptions\MustLogin; + if(!Context::get('is_logged')) + { + throw new Rhymix\Framework\Exceptions\MustLogin; + } $logged_info = Context::get('logged_info'); - + // Check folders $args = new stdClass; $args->member_srl = $logged_info->member_srl; @@ -433,16 +651,16 @@ class memberView extends member $folders = $output->data; if(!count($folders)) { - $output = getController('member')->migrateMemberScrappedDocuments($logged_info->member_srl); - if($output && !$output->toBool()) + $output = MemberController::getInstance()->migrateMemberScrappedDocuments($logged_info->member_srl); + if ($output instanceof BaseObject && !$output->toBool()) { return $output; } - + $output = executeQueryArray('member.getScrapFolderList', $args); $folders = $output->data; } - + // Get default folder if no folder is selected $folder_srl = (int)Context::get('folder_srl'); if($folder_srl && !array_filter($folders, function($folder) use($folder_srl) { return $folder->folder_srl == $folder_srl; })) @@ -453,7 +671,7 @@ class memberView extends member { $folder_srl = array_first($folders)->folder_srl; } - + // Get folder info $folder_info = new stdClass; foreach($folders as $folder) @@ -465,12 +683,38 @@ class memberView extends member } } + // If viewing default folder, check for additional scraps to migrate. + if (isset($folder_info->folder_srl) && $folder_info->name === '/DEFAULT/') + { + $output = executeQuery('member.updateScrapFolderFromNull', [ + 'folder_srl' => $folder_info->folder_srl, + 'member_srl' => $logged_info->member_srl, + ]); + } + // Get scrapped documents in selected folder $args = new stdClass(); $args->member_srl = $logged_info->member_srl; $args->folder_srl = $folder_srl; - $args->page = (int)Context::get('page'); + $args->page = Context::get('page'); + $search_keyword = str_replace(' ', '_', escape(trim(utf8_normalize_spaces(Context::get('search_keyword'))))); + switch (Context::get('search_target')) + { + case 'title': + $args->s_title = $search_keyword; + break; + case 'title_content': + $args->s_title = $search_keyword; + $args->s_content = $search_keyword; + break; + case 'content': + $args->s_content = $search_keyword; + break; + default: + break; + } $output = executeQueryArray('member.getScrapDocumentList', $args); + Context::set('total_count', $output->total_count); Context::set('total_page', $output->total_page); Context::set('page', $output->page); @@ -483,6 +727,7 @@ class memberView extends member $security = new Security($output->data); $security->encodeHTML('..nick_name'); + self::setMemberPageBrowserTitle(lang('cmd_view_scrapped_document')); $this->setTemplateFile('scrapped_list'); } @@ -496,24 +741,28 @@ class memberView extends member throw new Rhymix\Framework\Exceptions\FeatureDisabled; } - $oMemberModel = getModel('member'); + if (!$this->checkMidAndRedirect()) + { + return; + } + // A message appears if the user is not logged-in - if(!$oMemberModel->isLogged()) throw new Rhymix\Framework\Exceptions\MustLogin; - // Get the saved document(module_srl is set to member_srl instead) $logged_info = Context::get('logged_info'); + if(!$logged_info->member_srl) throw new Rhymix\Framework\Exceptions\MustLogin; + // Get the saved document(module_srl is set to member_srl instead) + $args = new stdClass(); $args->member_srl = $logged_info->member_srl; - $args->page = (int)Context::get('page'); + $args->page = Context::get('page'); $args->statusList = array('TEMP'); - - $oDocumentModel = getModel('document'); - $output = $oDocumentModel->getDocumentList($args, true); + $output = DocumentModel::getDocumentList($args, true); Context::set('total_count', $output->total_count); Context::set('total_page', $output->total_page); Context::set('page', $output->page); Context::set('document_list', $output->data); Context::set('page_navigation', $output->page_navigation); + self::setMemberPageBrowserTitle(lang('cmd_view_saved_document')); $this->setTemplateFile('saved_list'); } @@ -527,15 +776,20 @@ class memberView extends member throw new Rhymix\Framework\Exceptions\FeatureDisabled; } + if (!$this->checkMidAndRedirect()) + { + return; + } + $logged_info = Context::get('logged_info'); if (!$logged_info->member_srl) { throw new Rhymix\Framework\Exceptions\MustLogin; } - + $args = new stdClass(); $args->member_srl = $logged_info->member_srl; - $args->page = (int)Context::get('page'); + $args->page = Context::get('page'); $output = executeQueryArray('member.getAutologin', $args); Context::set('total_count', $output->total_count); Context::set('total_page', $output->total_page); @@ -543,36 +797,51 @@ class memberView extends member Context::set('active_logins', $output->data); Context::set('page_navigation', $output->page_navigation); + $args = new stdClass(); + $args->member_srl = $logged_info->member_srl; + $output = executeQueryArray('member.getMemberDevice', $args); + Context::set('registered_devices', $output->data); + + self::setMemberPageBrowserTitle(lang('cmd_view_active_logins')); $this->setTemplateFile('active_logins'); } - + /** - * @brief Display the login form + * @brief Display the login form */ function dispMemberLoginForm() { - if(Context::get('is_logged')) + // Check referer URL + $referer_url = $this->checkRefererUrl(); + Context::set('referer_url', $referer_url); + + // Redirect to member mid if necessary. + if (!$this->checkMidAndRedirect()) { - $this->setRedirectUrl(getNotEncodedUrl('act','')); + return; + } + + // Return to previous screen if already logged in. + if($this->user->isMember()) + { + $this->setRedirectUrl($referer_url); return; } // get member module configuration. - $oMemberModel = getModel('member'); $config = $this->member_config; Context::set('identifier', $config->identifier); + // Get validator status $XE_VALIDATOR_MESSAGE = Context::get('XE_VALIDATOR_MESSAGE'); $XE_VALIDATOR_ERROR = Context::get('XE_VALIDATOR_ERROR'); if($XE_VALIDATOR_ERROR == -11) + { Context::set('XE_VALIDATOR_MESSAGE', $XE_VALIDATOR_MESSAGE . $config->limit_day_description); - - if($XE_VALIDATOR_ERROR < -10 && $XE_VALIDATOR_ERROR > -21) - Context::set('referer_url', getUrl('')); - else - Context::set('referer_url', htmlspecialchars($_SERVER['HTTP_REFERER'], ENT_COMPAT | ENT_HTML401, 'UTF-8', false)); + } // Set a template file + self::setMemberPageBrowserTitle(lang('cmd_login')); $this->setTemplateFile('login_form'); } @@ -581,17 +850,20 @@ class memberView extends member */ function dispMemberModifyPassword() { - $oMemberModel = getModel('member'); // A message appears if the user is not logged-in - if(!$oMemberModel->isLogged()) throw new Rhymix\Framework\Exceptions\MustLogin; + if(!$this->user->member_srl) throw new Rhymix\Framework\Exceptions\MustLogin; + + if (!$this->checkMidAndRedirect()) + { + return; + } $memberConfig = $this->member_config; - $logged_info = Context::get('logged_info'); $member_srl = $logged_info->member_srl; $columnList = array('member_srl', 'user_id'); - $member_info = $oMemberModel->getMemberInfoByMemberSrl($member_srl, 0, $columnList); + $member_info = MemberModel::getMemberInfoByMemberSrl($member_srl, 0, $columnList); Context::set('member_info',$member_info); if($memberConfig->identifier == 'user_id') @@ -605,6 +877,7 @@ class memberView extends member Context::set('formValue', $member_info->email_address); } // Set a template file + self::setMemberPageBrowserTitle(lang('cmd_modify_member_password')); $this->setTemplateFile('modify_password'); } @@ -613,16 +886,19 @@ class memberView extends member */ function dispMemberLeave() { - $oMemberModel = getModel('member'); // A message appears if the user is not logged-in - if(!$oMemberModel->isLogged()) throw new Rhymix\Framework\Exceptions\MustLogin; + if(!$this->user->member_srl) throw new Rhymix\Framework\Exceptions\MustLogin; + + if (!$this->checkMidAndRedirect()) + { + return; + } $memberConfig = $this->member_config; - $logged_info = Context::get('logged_info'); $member_srl = $logged_info->member_srl; - $member_info = $oMemberModel->getMemberInfoByMemberSrl($member_srl); + $member_info = MemberModel::getMemberInfoByMemberSrl($member_srl); Context::set('member_info',$member_info); if($memberConfig->identifier == 'user_id') @@ -636,6 +912,7 @@ class memberView extends member Context::set('formValue', $member_info->email_address); } // Set a template file + self::setMemberPageBrowserTitle(lang('msg_leave_member')); $this->setTemplateFile('leave_form'); } @@ -644,14 +921,22 @@ class memberView extends member */ function dispMemberLogout() { - $oMemberController = getController('member'); - $output = $oMemberController->procMemberLogout(); - if(!$output->redirect_url) - $this->setRedirectUrl(getNotEncodedUrl('act', '')); - else - $this->setRedirectUrl($output->redirect_url); + // Redirect if not logged in. + if(!Context::get('is_logged')) + { + $this->setRedirectUrl(getNotEncodedUrl('act', '', 'redirect_url', '')); + return; + } - return; + $output = MemberController::getInstance()->procMemberLogout(); + if (!empty($output->redirect_url)) + { + $this->setRedirectUrl($output->redirect_url); + } + else + { + $this->setRedirectUrl(getNotEncodedUrl('act', '', 'redirect_url', '')); + } } /** @@ -668,58 +953,55 @@ class memberView extends member */ function dispMemberFindAccount() { - if(Context::get('is_logged')) + if (!$this->checkMidAndRedirect()) { - throw new Rhymix\Framework\Exception('already_logged'); + return; } - $config = $this->member_config; - - Context::set('identifier', $config->identifier); + Context::set('member_config', $this->member_config); + Context::set('identifier', $this->member_config->identifier); Context::set('enable_find_account_question', 'N'); + self::setMemberPageBrowserTitle(lang('cmd_find_member_account')); $this->setTemplateFile('find_member_account'); } /** * @brief Page of re-sending an authentication mail */ - function dispMemberResendAuthMail() + function dispMemberResendAuthMail() { - $authMemberSrl = $_SESSION['auth_member_srl']; - unset($_SESSION['auth_member_srl']); - - if(Context::get('is_logged')) + if(Context::get('is_logged')) { throw new Rhymix\Framework\Exception('already_logged'); } - if($authMemberSrl) + if (!$this->checkMidAndRedirect()) { - $oMemberModel = getModel('member'); - $memberInfo = $oMemberModel->getMemberInfoByMemberSrl($authMemberSrl); + return; + } - $_SESSION['auth_member_info'] = $memberInfo; - Context::set('memberInfo', $memberInfo); - $this->setTemplateFile('reset_mail'); - } - else - { - $this->setTemplateFile('resend_auth_mail'); - } + self::setMemberPageBrowserTitle(lang('cmd_resend_auth_mail')); + $this->setTemplateFile('resend_auth_mail'); } function dispMemberModifyEmailAddress() { - if($_SESSION['rechecked_password_step'] != 'VALIDATE_PASSWORD' && $_SESSION['rechecked_password_step'] != 'INPUT_DATA') + if (!isset($_SESSION['rechecked_password_step']) || !in_array($_SESSION['rechecked_password_step'], ['VALIDATE_PASSWORD', 'INPUT_DATA'])) { Context::set('success_return_url', getUrl('', 'mid', Context::get('mid'), 'act', 'dispMemberModifyEmailAddress')); $this->dispMemberModifyInfoBefore(); return; } + if (!$this->checkMidAndRedirect()) + { + return; + } + $_SESSION['rechecked_password_step'] = 'INPUT_DATA'; + self::setMemberPageBrowserTitle(lang('cmd_modify_member_email_address')); $this->setTemplateFile('modify_email_address'); } @@ -729,8 +1011,7 @@ class memberView extends member */ function addExtraFormValidatorMessage() { - $oMemberModel = getModel('member'); - $extraList = $oMemberModel->getUsedJoinFormList(); + $extraList = MemberModel::getUsedJoinFormList(); $js_code = array(); $js_code[] = ' + diff --git a/modules/member/skins/default/comment_list.html b/modules/member/skins/default/comment_list.html index 47a015d04..ebd6e707f 100644 --- a/modules/member/skins/default/comment_list.html +++ b/modules/member/skins/default/comment_list.html @@ -15,19 +15,28 @@ {$no} - {$comment->getSummary() ?: $lang->msg_no_text_comment} + + {$comment->getSummary(50, '...', $lang->msg_no_text_comment)} + {$comment->getRegdate("Y-m-d")} - +
                + +
                + + +

                {$lang->about_authmail_expires}

                +
                +
                {$lang->cmd_member_profile_view}
                @@ -28,6 +59,14 @@

                {$lang->about_member_profile_view}

                +
                +
                {$lang->cmd_modify_nickname_allow}
                +
                + + +

                {$lang->about_allow_nickname_change}

                +
                +
                {$lang->cmd_modify_nickname_log}
                @@ -36,6 +75,17 @@

                {$lang->about_update_nickname_log}

                +
                +
                {$lang->cmd_nickname_symbols}
                +
                + + + + +

                {$lang->about_nickname_symbols}

                + +
                +
                +
                + +
                + + +

                {$lang->about_password_reset_method}

                +
                +
                diff --git a/modules/member/tpl/group_list.html b/modules/member/tpl/group_list.html index e40197780..25fca0481 100644 --- a/modules/member/tpl/group_list.html +++ b/modules/member/tpl/group_list.html @@ -15,7 +15,7 @@

                {$XE_VALIDATOR_MESSAGE}

                -
                + @@ -23,7 +23,7 @@ {count($group_list)}{$lang->msg_groups_exist} - {$lang->use_group_image_mark}: + {$lang->use_group_image_mark}: @@ -55,7 +55,7 @@
                - + #{$group_srl} diff --git a/modules/member/tpl/insert_join_form.html b/modules/member/tpl/insert_join_form.html index 700d0cc6c..d59983f12 100644 --- a/modules/member/tpl/insert_join_form.html +++ b/modules/member/tpl/insert_join_form.html @@ -15,22 +15,28 @@
                - +
                -
                - +
                +
                - -

                {$lang->about_multi_type}

                + +
                +
                +
                + +
                + +

                {$lang->about_multi_type}

                @@ -51,7 +57,6 @@
                +

                {$lang->msg_update_member}

                {$lang->msg_new_member}

                +

                {$XE_VALIDATOR_MESSAGE}

                + @@ -20,40 +23,26 @@ -
                - -
                - - + + +
                + +
                + + + + + + + + {$editor|noescape} + + {$formTag->inputTag} + +
                -
                -
                - -
                - -
                -
                -
                - -
                - -
                -
                -
                - -
                - -
                -
                -
                - -
                {$formTag->inputTag}
                -
                {$editor|noescape}
                -
                - + +
                @@ -61,6 +50,7 @@
                +
                @@ -69,13 +59,16 @@
                -
                + +
                - - + + +
                +
                @@ -83,18 +76,7 @@ {$lang->about_refused_reason}
                -
                - -
                - -
                -
                -
                - -
                - -
                -
                +
                @@ -104,20 +86,23 @@ {$lang->about_limit_date}
                +
                - +
                - {$lang->about_refused_reason} + {$lang->about_limited_reason}
                +
                - - + +
                +
                @@ -125,20 +110,44 @@ {$lang->about_member_description}
                + +
                + +
                + + + + +
                +
                + +
                + +
                + + + + +
                +
                +
                +
                + + diff --git a/modules/member/tpl/js/default_config.js b/modules/member/tpl/js/default_config.js index a98b6d0a6..4ad6c3179 100644 --- a/modules/member/tpl/js/default_config.js +++ b/modules/member/tpl/js/default_config.js @@ -1,4 +1,13 @@ jQuery(function($){ + + $('input[name=enable_join]').on('change', function() { + if ($('#enable_join_only_with_key').is(':checked')) { + $('#enable_join_key').show(); + } else { + $('#enable_join_key').hide(); + } + }); + $('.__sync').click(function (){ exec_xml( 'importer', // module diff --git a/modules/member/tpl/js/signup_check.js b/modules/member/tpl/js/signup_check.js index e267c09da..7b0ff1585 100644 --- a/modules/member/tpl/js/signup_check.js +++ b/modules/member/tpl/js/signup_check.js @@ -19,34 +19,81 @@ function memberSetEvent() { // 실제 서버에 특정 필드의 value check를 요청하고 이상이 있으면 메세지를 뿌려주는 함수 function memberCheckValue(event) { var field = event.target; - var _name = field.name; - var _value = field.value; - if(!_name || !_value) return; + if(!field.name || !field.value) { + return; + } - var params = {name:_name, value:_value}; - var response_tags = ['error','message']; - - exec_xml('member','procMemberCheckValue', params, completeMemberCheckValue, response_tags, field); + exec_json('member.procMemberCheckValue', { + name: field.name, + value: field.value + }, function(data) { + completeMemberCheckValue(data, null, field); + }); } // 서버에서 응답이 올 경우 이상이 있으면 메세지를 출력 -function completeMemberCheckValue(ret_obj, response_tags, field) { +function completeMemberCheckValue(data, unused, field) { var _id = 'dummy_check'+field.name; var dummy = jQuery('#'+_id); - - if(ret_obj['message']=='success') { - dummy.html('').hide(); - return; - } - if (!dummy.length) { dummy = jQuery('

                ').attr('id', _id).appendTo(field.parentNode); } - dummy.html(ret_obj['message']).show(); + if(data.message == 'success') { + dummy.html('').hide(); + return; + } else { + dummy.html(data.message).show(); + } } // 결과 메세지를 정리하는 함수 function removeMemberCheckValueOutput(dummy, obj) { dummy.style.display = "none"; } + +// 문자 인증 처리 +(function($) { + $(function() { + $('input.phone_number').on('keydown change', function() { + $(this).siblings('button.verifySMS').show(); + }); + $('button.verifySMS').on('click', function(event) { + event.preventDefault(); + $(this).attr('disabled', 'disabled'); + var phone_country = $(this).siblings('.phone_country').val(); + var phone_number = $(this).siblings('.phone_number').val(); + var that = $(this); + exec_json('member.procMemberSendVerificationSMS', { phone_country: phone_country, phone_number: phone_number }, function(data) { + alert(data.message); + if (!data.error) { + var input_area = that.siblings('.verifySMS_input_area'); + input_area.show().find('input').focus(); + that.hide(); + } else { + that.removeAttr('disabled'); + } + }, function() { + that.removeAttr('disabled'); + }); + }); + $('button.verifySMS_input_button').on('click', function(event) { + event.preventDefault(); + $(this).attr('disabled', 'disabled'); + var code = $(this).siblings('.verifySMS_input_number').val(); + var that = $(this); + exec_json('member.procMemberConfirmVerificationSMS', { code: code }, function(data) { + alert(data.message); + if (!data.error) { + var input_area = that.parents('.verifySMS_input_area'); + input_area.hide(); + input_area.siblings('.phone_number,.phone_country').attr('readonly', 'readonly'); + } else { + that.removeAttr('disabled'); + } + }, function() { + that.removeAttr('disabled'); + }); + }); + }); +})(jQuery); diff --git a/modules/member/tpl/js/signup_config.js b/modules/member/tpl/js/signup_config.js index 7a5d2ffef..6e4ab1976 100644 --- a/modules/member/tpl/js/signup_config.js +++ b/modules/member/tpl/js/signup_config.js @@ -66,19 +66,18 @@ jQuery(function($){ .find(':radio, [type="number"]') .removeAttr('disabled') .end() - .find(':radio[value=option]').attr('checked', 'checked') + .find(':radio.item_optional').prop('checked', true) .end() .prev('td') - .find(':input[value=Y]').removeAttr('disabled').attr('checked', 'checked'); - + .find(':input[value=Y]').removeAttr('disabled').prop('checked', true); } else { $i.parent('td').next('td').next('td') .find('>._subItem').hide().end() - .find(':radio, [type="number"]').attr('disabled','disabled').removeAttr('checked') + .find(':radio.item_required, :radio.item_optional, [type="number"]').attr('disabled','disabled').prop('checked', false) .next('label').css('fontWeight','normal').end() .end() .prev('td') - .find(':input[value=Y]').removeAttr('checked').attr('disabled', 'disabled'); + .find(':input[value=Y]').attr('disabled', 'disabled').prop('checked', false); } } @@ -102,7 +101,7 @@ jQuery(function($){ {member_join_form_srl:memberFormSrl}, function(ret){ var tpl = ret.tpl.replace(/\|@\|/g, '\n'); - $('#extendForm').html(tpl); + $('#extendForm').html(tpl).find('.lang_code').xeApplyMultilingualUI(); if (checked)$('#extendForm #radio_'+checked).attr('checked', 'checked'); }, @@ -110,7 +109,7 @@ jQuery(function($){ ); }); - + $('a._extendFormDelete').click(function(event){ event.preventDefault(); if (!confirm(xe.lang.msg_delete_extend_form)) return; @@ -136,7 +135,7 @@ jQuery(function($){ $('#prohibited_id').focus(); return; } - + ids = ids.replace(/\n/g, ','); @@ -194,7 +193,7 @@ jQuery(function($){ $('#prohibited_nick_name').focus(); return; } - + ids = ids.replace(/\n/g, ','); @@ -219,35 +218,6 @@ jQuery(function($){ }); - $('input[name=identifier]').change(function(){ - var $checkedTR = $('input[name=identifier]:checked').closest('tr'); - var $notCheckedTR = $('input[name=identifier]:not(:checked)').closest('tr'); - var name, notName; - if (!$checkedTR.hasClass('sticky')){ - name = $checkedTR.find('input[name="list_order[]"]').val(); - if (!$checkedTR.find('input[type=hidden][name="usable_list[]"]').length) $('').insertBefore($checkedTR); - if (!$checkedTR.find('input[type=hidden][name='+name+']').length) $('').insertBefore($checkedTR); - $checkedTR.find('th').html(''+$checkedTR.find('th ._title').html()+''); - $checkedTR.find('input[type=checkbox][name="usable_list[]"]').attr('checked', 'checked').attr('disabled', 'disabled'); - $checkedTR.find('input[type=radio][name='+name+'][value=required]').attr('checked', 'checked').attr('disabled', 'disabled'); - $checkedTR.find('input[type=radio][name='+name+'][value=option]').removeAttr('checked').attr('disabled', 'disabled'); - $checkedTR.addClass('sticky'); - $checkedTR.parent().prepend($checkedTR); - - notName = $notCheckedTR.find('input[name="list_order[]"]').val(); - if (notName == 'user_id'){ - if ($notCheckedTR.find('input[type=hidden][name="usable_list[]"]').length) $notCheckedTR.find('input[type=hidden][name="usable_list[]"]').remove(); - if ($notCheckedTR.find('input[type=hidden][name='+name+']').length) $notCheckedTR.find('input[type=hidden][name='+name+']').remove(); - $notCheckedTR.find('input[type=checkbox][name="usable_list[]"]').removeAttr('disabled'); - $notCheckedTR.find('input[type=radio][name='+notName+']').removeAttr('disabled'); - } - $notCheckedTR.find('th').html('

                '+$notCheckedTR.find('th ._title').html()+'
                '); - $notCheckedTR.removeClass('sticky'); - - // add sticky class - } - }); - $('#userDefine').submit(function(e) { var id_list = $(this).find('input[name=join_form_id_list]').val(); var id_list_arr = id_list.split(','); diff --git a/modules/member/tpl/login_config.html b/modules/member/tpl/login_config.html index 7f0679d07..e83bb4b68 100644 --- a/modules/member/tpl/login_config.html +++ b/modules/member/tpl/login_config.html @@ -4,61 +4,68 @@ +
                +

                {$lang->identifier}

                +
                + + + +

                {$lang->about_identifier}

                +
                +
                {$lang->unit_day} -

                {$lang->about_change_password_date}

                +

                {$lang->about_change_password_date}

                {$lang->enable_login_fail_report}

                - - +

                + + +

                +

                + {$lang->unit_count} / + {$lang->unit_sec} +

                +

                {$lang->about_login_trial_limit}

                - - - - - - - - - - + @@ -26,6 +18,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/modules/member/tpl/member_list.html b/modules/member/tpl/member_list.html index 890b62abf..dfd63161a 100644 --- a/modules/member/tpl/member_list.html +++ b/modules/member/tpl/member_list.html @@ -16,11 +16,13 @@ - - + + + @@ -56,22 +61,41 @@ ? - - {@ $member_info['group_list'] = implode(', ', $member_info['group_list'])} - - - + + {@$used_values = ''} @@ -97,35 +121,33 @@ @@ -145,9 +167,9 @@ - + {$lang->cmd_cancel} diff --git a/modules/member/tpl/nick_name_log.html b/modules/member/tpl/nick_name_log.html index 5e76c71ef..b65c62bb6 100644 --- a/modules/member/tpl/nick_name_log.html +++ b/modules/member/tpl/nick_name_log.html @@ -27,6 +27,19 @@
                {$lang->signup_date}{zdate($memberInfo[regdate],"Y-m-d")}
                {$lang->last_login}{zdate($memberInfo[last_login],"Y-m-d H:i:s")}
                * {$item->title}{$item->title} * {$item->value}
                {$lang->allow_message} {$lang->allow_message_type[$memberInfo['allow_message']]}
                {$lang->status}{$memberInfo['denied'] === 'Y' ? $lang->denied : $lang->approval}
                {$lang->refused_reason}{$memberInfo['refused_reason']|autoescape|nl2br}
                {$lang->signup_date}{zdate($memberInfo['regdate'], 'Y-m-d H:i:s')}
                {$lang->last_login}{zdate($memberInfo['last_login'], 'Y-m-d H:i:s')}
                {$lang->limit_date}{zdate($memberInfo['limit_date'], 'Y-m-d')}
                {$lang->is_admin} {$lang->cmd_yes}
                {$lang->cmd_show_all_member}({$total_count}) | - {$lang->cmd_show_super_admin_member}({$total_count}) + {$lang->cmd_show_super_admin_member}({$total_count}) | - {$lang->approval}({$total_count}) + {$lang->approval}({$total_count}) | - {$lang->denied}({$total_count}) + {$lang->denied}({$total_count}) + | + {$lang->member_unauthenticated}({$total_count})
                {$lang->profile_image}{$lang->email}{$title} + {$title} + {$lang->status} {$lang->signup_date} {$lang->last_login} - {getEncodeEmailAddress($member_info['email_address'])} + {@ $member_info['group_list'] = Context::replaceUserLang(implode(', ', $member_info['group_list']), true)} + + + {getEncodeEmailAddress($member_info['email_address'])} + + + {\Rhymix\Framework\i18n::formatPhoneNumber($member_info['phone_number'], $member_info['phone_country'])} + + {\Rhymix\Framework\Korea::formatPhoneNumber($member_info['phone_number'])} + + {$member_info['phone_number']} + + + {$member_info[$name]} + {$member_info[$name]} - + + {$lang->approval} + {$lang->denied} + + {$lang->member_unauthenticated} {$lang->member_limited} - {$lang->approval} + {$member_info['status']} {zdate($member_info['regdate'], 'Y-m-d')}{zdate($member_info['last_login'], 'Y-m-d')} + {zdate($member_info['regdate'], 'Y-m-d')} + + {zdate($member_info['last_login'], 'Y-m-d')} + {$member_info['group_list']}  {$lang->inquiry}/{$lang->cmd_modify}
                + + +
                  diff --git a/modules/member/tpl/signup_config.html b/modules/member/tpl/signup_config.html index 73f94c1d5..f6d490b51 100644 --- a/modules/member/tpl/signup_config.html +++ b/modules/member/tpl/signup_config.html @@ -21,26 +21,23 @@
                  {$lang->unit_day} +

                  {$lang->about_limit_day}

                  -
                - +
                -
                -

                {$lang->about_emailhost_check}

                -
                -

                {@ if($config->emailhost_check =='prohibited') $emailhost_check = $lang->cmd_prohibited; else $emailhost_check = $lang->cmd_allowed;}{sprintf($lang->count_manage_email_host, count($managedEmailHost), $emailhost_check)}

                • {$emailInfo->email_host}

                {$lang->multi_line_input}

                +

                {$lang->about_emailhost_check}

                @@ -67,12 +64,27 @@

                {$lang->multi_line_input}

                +
                + +
                + + +

                {$lang->about_special_phone_number}

                +
                +
                +
                +

                {$lang->cmd_max_auth_sms_count}

                +
                + {$lang->unit_count} / + {$lang->unit_sec} +

                {$lang->about_max_auth_sms_count}

                +
                +
                - - -

                {$lang->about_redirect_url}

                + +

                {$lang->about_redirect_url}

                @@ -82,12 +94,6 @@ {$lang->target} - {$lang->identifier} - [?] -
                -

                {$lang->about_identifier}

                -
                - {$lang->use} {$lang->public} [?] @@ -102,49 +108,84 @@ {@ $disabled_list = array('find_account_question')} - {@ $fixed_public_list = array('nick_name', 'password', 'email_address')} + {@ $fixed_public_list = array('nick_name')} + {@ $fixed_private_list = array('email_address', 'phone_number', 'password')} - - - - - - - {$item->title} - - - - - - - - -   -   - - + - - + +
                - {$item->title} + + + + {$item->title} +
                - - - + + + + + + + + - - + + + + + +
                - + + + {$lang->install_other_menu_types} +
              • + +
                + +

                {$lang->about_menu_icon}

                +
                + +
              • +
              • + +
                + +

                {$lang->about_menu_class}

                +
                + +
              • - +
              • - +

                {$lang->about_menu_id} {$lang->about_new_menu_id}

                @@ -220,10 +269,26 @@
              • +
              • + +
                + +

                {$lang->about_menu_icon}

                +
                + +
              • +
              • + +
                + +

                {$lang->about_menu_class}

                +
                + +
              • - +
              • @@ -285,7 +350,9 @@

                {$lang->menu_img_btn}

                {$lang->about_imgbtn}

                -
                + + + @@ -303,7 +370,9 @@
                -
                + + + @@ -321,7 +390,9 @@
                -
                + + + @@ -366,14 +437,14 @@
                - +
                - +
                @@ -433,7 +504,6 @@ @@ -458,16 +528,6 @@
                - - - + \ No newline at end of file + diff --git a/modules/message/message.admin.controller.php b/modules/message/message.admin.controller.php index 169da556c..9a54a6a53 100644 --- a/modules/message/message.admin.controller.php +++ b/modules/message/message.admin.controller.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * @brief admin controller class of message module */ -class messageAdminController extends message +class MessageAdminController extends Message { /** * @brief Initialization diff --git a/modules/message/message.admin.model.php b/modules/message/message.admin.model.php index 3d53a1160..a64c1541c 100644 --- a/modules/message/message.admin.model.php +++ b/modules/message/message.admin.model.php @@ -1,6 +1,7 @@ */ -class messageAdminModel extends message{ +class MessageAdminModel extends Message +{ public function getMessageAdminColorset() { $skin = Context::get('skin'); diff --git a/modules/message/message.admin.view.php b/modules/message/message.admin.view.php index f4713e39a..04150712e 100644 --- a/modules/message/message.admin.view.php +++ b/modules/message/message.admin.view.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * @brief admin view class of the message module */ -class messageAdminView extends message +class MessageAdminView extends Message { /** * @brief Initialization diff --git a/modules/message/message.class.php b/modules/message/message.class.php index a1387435a..6142a31cd 100644 --- a/modules/message/message.class.php +++ b/modules/message/message.class.php @@ -5,14 +5,14 @@ * @author NAVER (developers@xpressengine.com) * @brief high class of message module */ -class message extends ModuleObject +class Message extends ModuleObject { /** * @brief Implement if additional tasks are necessary when installing */ function moduleInstall() { - + } /** @@ -20,19 +20,7 @@ class message extends ModuleObject */ function checkUpdate() { - $oModuleModel = getModel('module'); - $config = $oModuleModel->getModuleConfig('message'); - if($config->skin) - { - $config_parse = explode('.', $config->skin); - if (count($config_parse) > 1) - { - $template_path = sprintf('./themes/%s/modules/message/', $config_parse[0]); - if(is_dir($template_path)) return true; - } - } - return false; } /** @@ -40,23 +28,7 @@ class message extends ModuleObject */ function moduleUpdate() { - $oModuleModel = getModel('module'); - $config = $oModuleModel->getModuleConfig('message'); - if($config->skin) - { - $config_parse = explode('.', $config->skin); - if (count($config_parse) > 1) - { - $template_path = sprintf('./themes/%s/modules/message/', $config_parse[0]); - if(is_dir($template_path)) - { - $config->skin = implode('|@|', $config_parse); - $oModuleController = getController('module'); - $oModuleController->updateModuleConfig('message', $config); - } - } - } } /** @@ -64,6 +36,7 @@ class message extends ModuleObject */ function recompileCache() { + } } /* End of file message.class.php */ diff --git a/modules/message/message.mobile.php b/modules/message/message.mobile.php index a7ab17610..99e8a9034 100644 --- a/modules/message/message.mobile.php +++ b/modules/message/message.mobile.php @@ -1,71 +1,9 @@ */ -class messageMobile extends messageView +class MessageMobile extends MessageView { - /** - * @brief Initialization - **/ - function init() - { - } - /** - * @brief Message output - **/ - function dispMessage($detail = null) - { - // Get configurations (using module model object) - $oModuleModel = getModel('module'); - $config = $oModuleModel->getModuleConfig('message'); - if(!is_object($config)) $config = new stdClass; - if(!$config->mskin) $config->mskin = 'default'; - - // Set the template path - if($config->mskin === '/USE_RESPONSIVE/') - { - $template_path = sprintf('%sskins/%s/', $this->module_path, $config->skin); - if(!is_dir($template_path) || !$config->skin) - { - $template_path = sprintf('%sskins/%s/', $this->module_path, 'default'); - } - } - else - { - $template_path = sprintf('%sm.skins/%s/', $this->module_path, $config->mskin); - if(!is_dir($template_path) || !$config->mskin) - { - $template_path = sprintf('%sm.skins/%s/', $this->module_path, 'default'); - } - } - - // Get the member configuration - $oModuleModel = getModel('module'); - $member_config = $oModuleModel->getModuleConfig('member'); - Context::set('member_config', $member_config); - - // Set a flag to check if the https connection is made when using SSL and create https url - $ssl_mode = false; - if($member_config->enable_ssl == 'Y') - { - if(strncasecmp('https://', Context::getRequestUri(), 8) === 0) $ssl_mode = true; - } - Context::set('ssl_mode',$ssl_mode); - Context::set('system_message', nl2br($this->getMessage())); - Context::set('system_message_detail', nl2br($detail)); - - Context::set('act', 'procMemberLogin'); - Context::set('mid', ''); - - $this->setTemplatePath($template_path); - $this->setTemplateFile('system_message'); - - // Default 403 Error - if($this->getHttpStatusCode() === 200) - { - $this->setHttpStatusCode(403); - } - } } /* End of file message.mobile.php */ /* Location: ./modules/message/message.mobile.php */ diff --git a/modules/message/message.view.php b/modules/message/message.view.php index fd3169eec..cdc3af68f 100644 --- a/modules/message/message.view.php +++ b/modules/message/message.view.php @@ -5,61 +5,123 @@ * @author NAVER (developers@xpressengine.com) * @brief view class of the message module */ -class messageView extends message +class MessageView extends Message { - /** - * @brief Initialization - */ - function init() - { - } - /** * @brief Display messages */ - function dispMessage($detail = null) + public function dispMessage($detail = null, $location = null) { - // Get configurations (using module model object) - $oModuleModel = getModel('module'); - $this->module_config = $config = $oModuleModel->getModuleConfig('message', $this->module_info->site_srl); - - if(!$config) - { - $config = new stdClass(); - } - - if(!$config->skin) + // Get skin configuration + $config = ModuleModel::getModuleConfig('message') ?: new stdClass; + if(empty($config->skin)) { $config->skin = 'xedition'; } - $template_path = sprintf('%sskins/%s', $this->module_path, $config->skin); - - // Template path + if(empty($config->mskin)) + { + $config->mskin = 'default'; + } + + // Set the template path + if (contains('mobile', get_class($this), false)) + { + if($config->mskin === '/USE_RESPONSIVE/') + { + $template_path = sprintf('%sskins/%s/', $this->module_path, $config->skin); + if(!is_dir($template_path)) + { + $template_path = sprintf('%sskins/%s/', $this->module_path, 'default'); + } + } + else + { + $template_path = sprintf('%sm.skins/%s/', $this->module_path, $config->mskin); + if(!is_dir($template_path)) + { + $template_path = sprintf('%sm.skins/%s/', $this->module_path, 'default'); + } + } + } + else + { + $template_path = sprintf('%sskins/%s', $this->module_path, $config->skin); + if(!is_dir($template_path)) + { + $template_path = sprintf('%sskins/%s/', $this->module_path, 'default'); + } + } $this->setTemplatePath($template_path); // Get the member configuration - $member_config = $oModuleModel->getModuleConfig('member'); + $member_config = ModuleModel::getModuleConfig('member'); Context::set('member_config', $member_config); - - // Set a flag to check if the https connection is made when using SSL and create https url - $ssl_mode = false; - if($member_config->enable_ssl == 'Y') + + // Disable location if debug not available + if (!Rhymix\Framework\Debug::isEnabledForCurrentUser()) { - if(strncasecmp('https://', Context::getRequestUri(), 8) === 0) $ssl_mode = true; + $location = null; } - Context::set('ssl_mode', $ssl_mode); + // Remove basedir from location (if any) + if ($location && starts_with(\RX_BASEDIR, $location)) + { + $location = substr($location, strlen(\RX_BASEDIR)); + } + + Context::set('ssl_mode', \RX_SSL); Context::set('system_message', nl2br($this->getMessage())); - Context::set('system_message_detail', nl2br($detail)); + Context::set('system_message_detail', nl2br($detail ?? '')); + Context::set('system_message_help', self::getErrorHelp(strval($detail ?? ''))); + Context::set('system_message_location', escape($location ?? '')); + + if ($this->getError()) + { + if ($detail) + { + $this->add('errorDetail', $detail); + } + if ($location) + { + $this->add('errorLocation', $location); + } + } $this->setTemplateFile('system_message'); - + // Default 403 Error if($this->getHttpStatusCode() === 200) { $this->setHttpStatusCode(403); } } + + /** + * Get friendly help message for common types of errors. + * + * @param string $error_message + * @return string + */ + public static function getErrorHelp(string $error_message): string + { + $regexp_list = [ + '/Class [\'"]Object[\'"] not found/' => 'baseobject', + '/Undefined constant [\'"][^\'"]+?[\'"]/' => 'undef_constant', + '/Attempt to assign property [\'"][^\'"]+?[\'"] on null/' => 'undef_object', + '/Argument #\d+ \(\$\w+\) must be of type (Countable\|)?array, \w+ given ?/' => 'not_array', + '/Syntax error, unexpected end of file\b/i' => 'unexpected_eof', + ]; + + foreach ($regexp_list as $regexp => $key) + { + if (preg_match($regexp, $error_message)) + { + return lang('message.error_help.' . $key); + } + } + + return ''; + } } /* End of file message.view.php */ /* Location: ./modules/message/message.view.php */ diff --git a/modules/message/skins/default/message.css b/modules/message/skins/default/message.css index 1e30d1154..26d7f2e07 100644 --- a/modules/message/skins/default/message.css +++ b/modules/message/skins/default/message.css @@ -15,7 +15,10 @@ body, #access table, #access input, #access textarea, #access select, #access bu #access>.login-footer:before, #access>.login-footer:after{display:table;line-height:0;content:"";clear:both} #access .control-group{position:relative;padding:0 14px 0 0;margin:0;clear:both} +#access .control-group.captcha { margin-bottom:10px} #access .control-group:before{content:"";display:block;clear:both} +#access .message{padding:0.8em 1em} +#access .message.location{word-break:break-all} #access form{margin:0} #access fieldset{margin:0} #access label{cursor:pointer;display:inline-block} diff --git a/modules/message/skins/default/system_message.html b/modules/message/skins/default/system_message.html index bd63106d3..4deba92d0 100644 --- a/modules/message/skins/default/system_message.html +++ b/modules/message/skins/default/system_message.html @@ -1,5 +1,5 @@ - + @@ -8,13 +8,21 @@
                -
                @@ -44,14 +58,20 @@
                diff --git a/modules/module/tpl/module_list.html b/modules/module/tpl/module_list.html index 3ff9f9409..1f4ad3cc2 100644 --- a/modules/module/tpl/module_list.html +++ b/modules/module/tpl/module_list.html @@ -17,7 +17,6 @@ {$lang->version} {$lang->author} {$lang->path} - {$lang->cmd_delete} @@ -32,15 +31,19 @@ {$val->title}

                {$val->description}

                -

                - {$lang->msg_blacklisted_module}
                - {$lang->get('admin.msg_blacklisted_reason.'.$val->module)} +

                + {$lang->msg_blacklisted_module}

                {$lang->msg_avail_install}

                {$lang->msg_avail_update}

                -

                {$lang->msg_avail_easy_update} {$lang->msg_do_you_like_update}

                - {$val->version} + + + Rhymix Core + + {$val->version} + + @@ -51,9 +54,6 @@ {$val->path} - - {$lang->cmd_delete} - diff --git a/modules/module/tpl/module_setup.html b/modules/module/tpl/module_setup.html index d99fe0501..82a96ac8a 100644 --- a/modules/module/tpl/module_setup.html +++ b/modules/module/tpl/module_setup.html @@ -46,14 +46,14 @@
                - +

                {$lang->about_header_text}

                - +

                {$lang->about_footer_text}

                @@ -68,5 +68,5 @@ - + diff --git a/modules/module/tpl/multilingual_v17.html b/modules/module/tpl/multilingual_v17.html index 0fa7a68c5..23754d4d4 100644 --- a/modules/module/tpl/multilingual_v17.html +++ b/modules/module/tpl/multilingual_v17.html @@ -41,11 +41,11 @@
                diff --git a/modules/module/tpl/skin_config.html b/modules/module/tpl/skin_config.html index 26171074b..576bc11e3 100644 --- a/modules/module/tpl/skin_config.html +++ b/modules/module/tpl/skin_config.html @@ -25,7 +25,7 @@ {$lang->skin_author}
                - + {$author->name} ({$author->homepage} @@ -41,22 +41,22 @@
                -
                {zdate($skin_info->date, 'Y-m-d')}
                +
                {zdate($skin_info->date ?? '', 'Y-m-d')}
                -
                +
                {nl2br(trim($skin_info->license))}

                {$skin_info->license_link}

                -
                +
                {nl2br(trim($skin_info->description))}
                - +

                {$lang->extra_vars}

                @@ -79,7 +79,7 @@
                - + {@$group = $val->group}
                @@ -90,10 +90,10 @@
                - + - + - + @@ -130,6 +130,6 @@
                - + diff --git a/modules/ncenterlite/conf/info.xml b/modules/ncenterlite/conf/info.xml index 76cc4c118..79d35c8cf 100644 --- a/modules/ncenterlite/conf/info.xml +++ b/modules/ncenterlite/conf/info.xml @@ -1,14 +1,14 @@ - 알림센터 Lite - Notification Center Lite + 알림 센터 + Notification Center 사이트 사용자간의 커뮤니케이션에 대한 정보를 알려주는 모듈입니다. This module notify users of information about new documents, comments and/or messages that call them. This module will enhance communication beween site users. - 3.1 - 2016-09-10 - content - - XE Public - XE Public + RX_VERSION + RX_CORE + service + + Rhymix + Rhymix diff --git a/modules/ncenterlite/conf/module.xml b/modules/ncenterlite/conf/module.xml index f56580f36..a4e64461d 100644 --- a/modules/ncenterlite/conf/module.xml +++ b/modules/ncenterlite/conf/module.xml @@ -2,16 +2,19 @@ - - - + + + + + - + - + + @@ -20,18 +23,39 @@ - + + + + + + + + + + + + + + + + + + + + + + - 알림센터 Lite - Notification Center Lite - 通知センター Lite + 알림 센터 + Notification Center + 通知センター diff --git a/modules/ncenterlite/lang/en.php b/modules/ncenterlite/lang/en.php index 70f9fbb19..c04754896 100644 --- a/modules/ncenterlite/lang/en.php +++ b/modules/ncenterlite/lang/en.php @@ -1,33 +1,60 @@ ncenterlite = 'Notification Center Lite'; -$lang->ncenterlite_install_version = 'Installed version'; +$lang->ncenterlite = 'Notification Center'; +$lang->ncenterlite_notify = 'notification'; +$lang->ncenterlite_install_version = 'Installed Version'; $lang->ncenterlite_advenced_config = 'Advenced Setting'; -$lang->ncenterlite_document = 'document'; +$lang->ncenterlite_document = 'post'; $lang->ncenterlite_comment = 'comment'; -$lang->ncenterlite_type_message = 'message'; +$lang->ncenterlite_type_document = 'New Post'; +$lang->ncenterlite_type_comment = 'New Comment'; +$lang->ncenterlite_type_comment_comment = 'Reply on Comments'; +$lang->ncenterlite_type_mention = 'Mention'; +$lang->ncenterlite_type_vote = 'Vote'; +$lang->ncenterlite_type_scrap = 'Scrap'; +$lang->ncenterlite_type_message = 'Direct Message'; +$lang->ncenterlite_type_test = 'Dummy Notification'; +$lang->ncenterlite_type_admin_content = 'Admin Notification'; +$lang->ncenterlite_type_custom = 'Other'; +$lang->ncenterlite_comment_noti = $lang->ncenterlite_type_comment; +$lang->ncenterlite_comment_comment_noti = $lang->ncenterlite_type_comment_comment; +$lang->ncenterlite_mention_noti = $lang->ncenterlite_type_mention; +$lang->ncenterlite_vote_noti = $lang->ncenterlite_type_vote; +$lang->ncenterlite_scrap_noti = $lang->ncenterlite_type_scrap; +$lang->ncenterlite_message_noti = $lang->ncenterlite_type_message; $lang->ncenterlite_sender = 'Sender'; $lang->ncenterlite_addressee = 'Recipient'; $lang->ncenterlite_noti_contents = 'Contents'; -$lang->ncenterlite_read = 'Have read'; +$lang->ncenterlite_notify_setting = 'Turn Off Notifications at:'; +$lang->ncenterlite_about_notify_setting = 'You can turn off notifications in the selected pages and modules. Please select the pages and modules to turn off notifications in the following list.'; +$lang->ncenterlite_notify_mid_all = 'Forwarding Notifications to the Administrators'; +$lang->ncenterlite_about_mid_all = "You can listen to all the notifications from the selected pages and modules. Please select the pages and modules to receive forwarded notifications in the following list. (Caution: This setting may let you know the other members' activities in the selected pages.)"; +$lang->ncenterlite_read = 'Have Read'; $lang->ncenterlite_read_y = 'Read'; -$lang->ncenterlite_read_n = 'Not read'; +$lang->ncenterlite_read_n = 'Not Read'; $lang->ncenterlite_no_target = 'no target'; -$lang->ncenterlite_my_list = 'My notification list'; -$lang->ncenterlite_my_settings = 'My notification settings'; -$lang->ncenterlite_user_settings = 'User notification settings'; +$lang->ncenterlite_my_list = 'Received Notifications'; +$lang->ncenterlite_my_settings = 'My Notification Settings'; +$lang->ncenterlite_user_settings = 'User Notification Settings'; +$lang->ncenterlite_notify_settings = 'Notification Settings'; $lang->ncenterlite_userconfig_title = 'Notification Center Settings of %s'; -$lang->ncenterlite_userconfig_about = 'Personalized settings of notification center can be controlled by you.'; -$lang->ncenterlite_comment_noti = 'Comment notice'; -$lang->ncenterlite_comment_noti_about = 'Get notice of a comment after someone replies to my documents or comments.'; -$lang->ncenterlite_mention_noti = 'New Direct Message'; -$lang->ncenterlite_mention_noti_about = 'Get notice of a mention after someone mention me on documents and/or comments. ( @Nickname to mention )'; -$lang->ncenterlite_message_noti = 'New Message'; -$lang->ncenterlite_message_noti_about = 'Get notice of a message after someone send the message to me.'; -$lang->ncenterlite_comment_comment_noti = 'New Comment'; -$lang->ncenterlite_vote_noti = 'New Upvote'; +$lang->ncenterlite_userconfig_about = 'Please manage your notification settings here.'; +$lang->ncenterlite_comment_noti_about = 'Receive notifications for new comments on my posts.'; +$lang->ncenterlite_comment_comment_noti_about = 'Receive notifications for replies to my comments.'; +$lang->ncenterlite_mention_noti_about = 'Receive notifications for mentions (@%s).'; +$lang->ncenterlite_message_noti_about = 'Receive notifications for direct messages.'; +$lang->ncenterlite_vote_noti_about = 'Receive notifications when someone votes on my post or comment.'; +$lang->ncenterlite_scrap_noti_about = 'Receive notifications when someone scraps my post.'; +$lang->ncenterlite_admin_content_noti_about = 'Receive administrator notifications.'; +$lang->ncenterlite_custom_noti_about = 'Receive all other notifications.'; +$lang->ncneterlite_block_individual = 'Turn off notifications from each post/comment'; +$lang->ncneterlite_block_individual_about = 'You can turn off notifications from a particular post or comment.'; $lang->ncenterlite_activate = 'Activate'; $lang->ncenterlite_inactivate = 'Inactivate'; $lang->ncenterlite_userconfig_about_warning = 'Watch out! You are controlling other user\'s settings via this page.'; +$lang->ncenterlite_comment_all = 'Let every commenter know'; +$lang->ncenterlite_comment_all_setting = 'Notification settings for comment authors'; +$lang->ncenterlite_comment_all_select_mid = 'Modules with notifications for commenters'; +$lang->ncenterlite_about_comment_all_select_mid = 'Select pages and modules to turn on the notifications for commenters. If there are no selected modules, this function will be inactive.'; $lang->ncenterlite_article = '%1$s wrote "%2$s".'; $lang->ncenterlite_board = '%1$s wrote "%3$s" on %2$s.'; $lang->ncenterlite_commented = '%1$s commented "%3$s" on your %2$s.'; @@ -36,13 +63,15 @@ $lang->ncenterlite_mentioned = '%1$s mentioned you in a %4$s, " $lang->ncenterlite_message_string = 'You have %d unread message.'; $lang->ncenterlite_message_string_plural = 'You have %d unread messages.'; $lang->ncenterlite_message_mention = '%1$s sent you a message, "%2$s".'; -$lang->ncenterlite_test_noti = 'Hello, %s! This is a test notification.'; -$lang->ncenterlite_vote = '%1$s upvoted your %3$s, "%2$s".'; -$lang->ncenterlite_vote_anonymous = 'upvoted your %2$s, "%1$s".'; +$lang->ncenterlite_test_noti = 'Hi %s! This is a dummy notification.'; +$lang->ncenterlite_vote = '%1$s liked your %3$s, "%2$s".'; +$lang->ncenterlite_vote_anonymous = 'Your %2$s, "%1$s" was liked.'; +$lang->ncenterlite_scrap = '%1$s scrapped your %3$s, "%2$s".'; +$lang->ncenterlite_scrap_anonymous = 'Your %2$s, "%1$s" was scrapped.'; $lang->ncenterlite_admin_content_message = '%1$s wrote "%3$s" on %2$s.'; -$lang->ncenterlite_insert_member_message = '%s! Welcome to the membership!!'; +$lang->ncenterlite_insert_member_message = '%s! Welcome to join us!'; $lang->ncenterlite_content_image = '(Image)'; -$lang->ncenterlite_content_empty = '(No Content)'; +$lang->ncenterlite_content_empty = '(No content to display)'; $lang->ncenterlite_ago = 'ago'; $lang->ncenterlite_date['0'] = 'year'; $lang->ncenterlite_date['1'] = 'month'; @@ -53,13 +82,13 @@ $lang->ncenterlite_date['5'] = 'second'; $lang->ncenterlite_sir = ' '; $lang->ncenterlite_message = 'You have %s new notification.'; $lang->ncenterlite_messages = 'You have %s new notifications.'; -$lang->ncenterlite_not_have_message = 'You have no new notifications.'; -$lang->ncenterlite_thisistest = '[*] This is a test notice.'; -$lang->ncenterlite_delete_all = 'delete all'; +$lang->ncenterlite_not_have_message = 'There is no new notification for you.'; +$lang->ncenterlite_thisistest = '[*] This is a dummy notification.'; +$lang->ncenterlite_delete_all = 'Delete All'; $lang->ncenterlite_more = 'More'; -$lang->ncenterlite_stop_login_required = 'Sign in to control your own notification settings.'; $lang->ncenterlite_stop_no_permission_other_user = 'You don\'t have the authority to read settings of other members.'; $lang->ncenterlite_stop_no_permission_other_user_settings = 'You don\'t have the authority to control settings of other members.'; +$lang->ncenterlite_stop_no_permission_other_user_block_settings = 'You are not allowed to change the other member\'s notification settings.'; $lang->ncenterlite_message_delete_notification_before = 'Notifications before %s are deleted.'; $lang->ncenterlite_message_delete_notification_all = 'Every notification is deleted.'; $lang->ncenterlite_click_to_open = 'Click here to open'; @@ -69,6 +98,7 @@ $lang->ncenterlite_warning = 'Watch out!'; $lang->ncenterlite_io = 'Activate Notifications'; $lang->ncenterlite_io_about = 'You can activate or inactivate every function of Notification Center Lite module.'; $lang->ncenterlite_on = 'Active'; +$lang->ncenterlite_only_message = 'For direct messages only'; $lang->ncenterlite_off = 'Inactive'; $lang->ncenterlite_display = 'Display Notifications'; $lang->ncenterlite_display_all = 'All display'; @@ -89,24 +119,82 @@ $lang->ncenterlite_test_mention = 'Web page notification test'; $lang->ncenterlite_test_mention_about = 'Create dummy data for module and/or skin test.'; $lang->ncenterlite_test_push = 'Push notification test'; $lang->ncenterlite_test_push_about = 'Create dummy data for mobile push notification test.'; -$lang->ncenterlite_document_event_settings = 'Document event notification'; -$lang->ncenterlite_document_event_vote = 'Recommendation'; -$lang->ncenterlite_document_event_vote_about = 'When someone recommend document, the author of it can get notifying.'; +$lang->ncenterlite_document_event_settings = 'Post Event Notification'; +$lang->ncenterlite_document_event_vote = 'Vote'; +$lang->ncenterlite_document_event_vote_about = 'When someone vote up or down a post, its author can get notifying.'; $lang->ncenterlite_document_event_read = 'Delete notifying after read the article'; -$lang->ncenterlite_document_event_read_preserve = 'Preserve notification'; -$lang->ncenterlite_document_event_read_delete = 'Delete notification'; +$lang->ncenterlite_document_event_read_preserve = 'Preserve Notification'; +$lang->ncenterlite_document_event_read_delete = 'Delete Notification'; $lang->ncenterlite_document_event_read_about = 'Delete every notification after read the article. Default is do not delete (preserve).'; $lang->ncenterlite_commnet_event = 'Comments'; $lang->ncenterlite_commnet_event_noti_all = 'Notice every comments to the author'; $lang->ncenterlite_commnet_event_noti_some = 'Notice only direct replies to the author'; -$lang->ncenterlite_message_event = 'Notify message'; -$lang->ncenterlite_message_event_about = 'Do not notify message (Use XE Core message notification system).'; +$lang->ncenterlite_message_event = 'Notify direct message'; +$lang->ncenterlite_message_event_about = 'Do not notify direct messages via this module (Use notifications via the direct message module only).'; +$lang->ncenterlite_mid_use = 'Module specific settings'; +$lang->ncenterlite_to_unsubscribe = 'Disable notification'; +$lang->ncenterlite_subscribe = 'Activate notification'; +$lang->ncenterlite_cmd_unsubscribe_settings = 'Notification settings'; +$lang->ncenterlite_notify_count = 'Notification count'; +$lang->ncenterlite_notify_count_about = 'Set the number of notifications per page.'; +$lang->this_message_unsubscribe = 'Unsubscribe notifications from this post'; +$lang->about_this_message_unsubscribe = 'Stop receiving notifications about this post.'; +$lang->unsubscribe_list = 'Unsubscribe List'; +$lang->unsubscribe = 'Unsubscribe individually'; +$lang->about_unsubscribe = 'Allow users to unsubscribe notifications from individual posts.'; +$lang->fcm_push_format = 'Push notification format'; +$lang->fcm_push_format_notification = 'Use notification'; +$lang->fcm_push_format_data = 'Use data'; +$lang->about_fcm_push_format = 'Select where to place the notification data when sending push notifications with Google FCM.
                This setting should match the requirements of your client application or frontend code.'; +$lang->member_menu_view = 'Display personalized notification panel'; +$lang->member_menu_on = 'Display'; +$lang->member_menu_off = 'Hide'; +$lang->about_member_menu_view = 'Each member may have their own settings panel for their notification center. They can manipulate the notification settings in their panel. Please select if you will allow them to have their panel or not.'; +$lang->user_notify_setting = 'User notification settings'; +$lang->about_user_notify_setting = 'Each member can set a notify settings. Warning! If a member setting not use notifications, they will not be notified regardless of their default settings.'; +$lang->ncenterlite_push_before_sms = 'Try a push notification before sending an SMS'; +$lang->ncenterlite_push_before_sms_about = 'Don\'t send an SMS if the recipient has at least one device to which a push notification has been successfully sent.'; +$lang->ncenterlite_no_notify = 'There is no notification to present.'; $lang->ncenterlite_all_delete = 'Delete all'; $lang->ncenterlite_month_before_delete = 'Delete older than 1 month'; $lang->dont_check_notify_delete = 'Unread notifications will be deleted, too.'; -$lang->user_notify_setting = 'User notify setting.'; -$lang->about_user_notify_setting = 'Each member can set a notify settings. Warning! If a member setting not use notifications, they will not be notified regardless of their default settings.'; -$lang->msg_not_use_user_setting = 'user setting\'s not use. Please contact your administrator.'; +$lang->anonymous_nick_name_setting = 'Alternative nickname for anonymous'; +$lang->about_anonymous_nick_name = 'Defining an alternative nickname for notifications from anonymous.'; +$lang->mention_suffixes = 'Common name suffixes'; +$lang->about_mention_suffixes = 'Please define common suffixes for names. This notification system will identify the name with and without the defined suffixes. For instance, if " Doe" is defined as a suffix, both "@John Doe" and "@John" will point to "@John" in this system. Please separate multiple suffixes with a comma (,) between each.'; +$lang->mention_suffix_always_cut = 'Handling nicknames with the defined suffixes'; +$lang->mention_suffix_always_cut_y = 'Prioritize members\' nicknames withOUT the suffixes'; +$lang->mention_suffix_always_cut_n = 'Prioritize members\' nicknames WITH the suffixes'; +$lang->about_mention_suffix_always_cut = 'Decide which one should receive a notification for "@John Doe" when both John Doe and John are in your database.'; +$lang->mention_limit = 'Maximum numbers of notifications'; +$lang->about_mention_limit = 'To prevent a server-side overload, you can limit the maximum numbers of notifications in a single post or comment.'; +$lang->ncenterlite_msg_setting_error = 'There are errors in your settings. Please check your inputs.'; +$lang->ncenterlite_use_help = 'Select which kinds of notifications can be sent to the members via selected methods. Your members can personalize their settings in their own settings panel among your selections here.'; +$lang->ncenterlite_use_othercomment_help = 'Send notifications to every commenter when the author of the original post leaves a comment.'; +$lang->member_phone_variable = 'Variable of member\'s phone number'; +$lang->member_phone_variable_about = 'Define where the phone number will be referred. Either member\'s account information or the extended variable of the post can be selected.
                If there is only one extended variable in the form of a phone number, the settings are automatically saved on installation.'; +$lang->member_phone_builtin_field = 'Phone number in the member\'s account information'; $lang->anonymous_voter = 'anonymous voter'; $lang->about_anonymous_voter = 'anonymize voter in vote notification'; -$lang->msg_denger_rhymix_user = 'Warning! Rhymix includes notification center by default.
                Please remove this XE-only module and reinstall the Rhymix native version.'; \ No newline at end of file +$lang->anonymous_scrap = 'anonymous scrap'; +$lang->about_anonymous_scrap = 'anonymize scrapper in scrap notification'; +$lang->highlight_effect = 'highlight effect'; +$lang->about_highlight_effect = 'it gives highlight effect to the comment when access the comment URL.'; +$lang->fail_module_install = 'Failed to install module.'; +$lang->cmd_web_notify = 'Web'; +$lang->cmd_mail_notify = 'Email'; +$lang->cmd_sms_notify = 'SMS'; +$lang->cmd_push_notify = 'Push'; +$lang->ncenterlite_content_type = 'Type of the contents'; +$lang->ncenterlite_type_id = 'Notification Type ID'; +$lang->ncenterlite_type_objects = 'Value of Notification Type Variable'; +$lang->ncenterlite_type_content = 'Content of Notification Type'; +$lang->ncenterlite_notify_type = 'List of Notification Type'; +$lang->msg_do_not_notify_type = 'There is no matched type for generating a notification.'; +$lang->ncenterlite_custom_list = 'Tailored list'; +$lang->msg_not_use_user_setting = 'You cannot change your notification settings. Please contact your administrator.'; +$lang->msg_denger_rhymix_user = 'Warning! Rhymix already includes the notification center as a default module.
                Please remove this XE-only module and reinstall the Rhymix bundled version.'; +$lang->msg_test_notifycation_success = 'A dummy notification was successfully generated.'; +$lang->msg_unsubscribe_block_not_support = 'You are not allowed to turn off your notification. Please refer to the website administrator.'; +$lang->msg_unsubscribe_not_permission = 'You are not allowed to see the other members\' subscription lists.'; +$lang->msg_unsubscribe_not_in_list = 'This content is not on the unsubscribed list.'; diff --git a/modules/ncenterlite/lang/ko.php b/modules/ncenterlite/lang/ko.php index 1379fe242..80b6a69b5 100644 --- a/modules/ncenterlite/lang/ko.php +++ b/modules/ncenterlite/lang/ko.php @@ -1,22 +1,33 @@ ncenterlite = '알림센터 Lite'; +$lang->ncenterlite = '알림 센터'; +$lang->ncenterlite_notify = '알림'; $lang->ncenterlite_install_version = '설치된 버전'; $lang->ncenterlite_advenced_config = '고급 설정'; $lang->ncenterlite_document = '글'; $lang->ncenterlite_comment = '댓글'; -$lang->ncenterlite_mention = '멘션'; -$lang->ncenterlite_cmd_vote = '추천'; -$lang->ncenterlite_comment_comment = '대댓글'; -$lang->ncenterlite_type_message = '쪽지'; -$lang->ncenterlite_type_test = '테스트'; +$lang->ncenterlite_type_document = '글 알림'; +$lang->ncenterlite_type_comment = '댓글 알림'; +$lang->ncenterlite_type_comment_comment = '대댓글 알림'; +$lang->ncenterlite_type_mention = '멘션 알림'; +$lang->ncenterlite_type_vote = '추천 알림'; +$lang->ncenterlite_type_scrap = '스크랩 알림'; +$lang->ncenterlite_type_message = '쪽지 알림'; +$lang->ncenterlite_type_test = '테스트 알림'; +$lang->ncenterlite_type_admin_content = '관리자 알림'; +$lang->ncenterlite_type_custom = '기타 알림'; +$lang->ncenterlite_comment_noti = $lang->ncenterlite_type_comment; +$lang->ncenterlite_comment_comment_noti = $lang->ncenterlite_type_comment_comment; +$lang->ncenterlite_mention_noti = $lang->ncenterlite_type_mention; +$lang->ncenterlite_vote_noti = $lang->ncenterlite_type_vote; +$lang->ncenterlite_scrap_noti = $lang->ncenterlite_type_scrap; +$lang->ncenterlite_message_noti = $lang->ncenterlite_type_message; $lang->ncenterlite_sender = '보낸 사람'; $lang->ncenterlite_addressee = '받는 사람'; $lang->ncenterlite_noti_contents = '내용'; $lang->ncenterlite_notify_setting = '알림 미표시 페이지'; -$lang->ncenterlite_about_notify_setting = '선택한 모듈에서는 알림을 표시하지 않습니다.'; +$lang->ncenterlite_about_notify_setting = '선택한 모듈에서는 알림을 표시하지 않습니다. 그리고 알림을 전송하지도 않습니다.'; $lang->ncenterlite_notify_mid_all = '관리자 알림 페이지'; $lang->ncenterlite_about_mid_all = '선택한 모듈에서는 모든 알림이 관리자에게도 전달됩니다.'; -$lang->ncenterlite_admin_content = '관리자 알림'; $lang->ncenterlite_read = '읽음 확인'; $lang->ncenterlite_read_y = '읽음'; $lang->ncenterlite_read_n = '읽지 않음'; @@ -27,14 +38,16 @@ $lang->ncenterlite_user_settings = '사용자 알림 설정'; $lang->ncenterlite_notify_settings = '알림 설정'; $lang->ncenterlite_userconfig_title = '%s님의 알림센터 설정'; $lang->ncenterlite_userconfig_about = '알림센터의 개인의 설정을 저장하도록 합니다.'; -$lang->ncenterlite_comment_noti = '댓글 알림'; -$lang->ncenterlite_comment_noti_about = '내 게시물의 혹은 내 댓글에 댓글이 달릴경우 알림을 받습니다.'; -$lang->ncenterlite_mention_noti = '멘션 알림'; -$lang->ncenterlite_mention_noti_about = '누군가 글, 혹은 댓글을 통해서 나를 멘션 했을 경우 알려줍니다. (멘션 방법 @닉네임 )'; -$lang->ncenterlite_message_noti = '쪽지 알림'; -$lang->ncenterlite_message_noti_about = '누군가에게 받은 쪽지를 알림을 받습니다.'; -$lang->ncenterlite_comment_comment_noti = '대댓글 알림'; -$lang->ncenterlite_vote_noti = '추천 알림'; +$lang->ncenterlite_comment_noti_about = '내 글에 댓글이 달리면 알림을 받습니다.'; +$lang->ncenterlite_comment_comment_noti_about = '내 댓글에 대댓글이 달리면 알림을 받습니다.'; +$lang->ncenterlite_mention_noti_about = '누군가 글이나 댓글에서 나를 멘션(@%s)하면 알림을 받습니다.'; +$lang->ncenterlite_message_noti_about = '새 쪽지가 도착하면 알림을 받습니다.'; +$lang->ncenterlite_vote_noti_about = '내 글이나 댓글이 추천을 받으면 알림을 받습니다.'; +$lang->ncenterlite_scrap_noti_about = '누군가 내 글을 스크랩하면 알림을 받습니다.'; +$lang->ncenterlite_admin_content_noti_about = '관리자 알림을 받습니다.'; +$lang->ncenterlite_custom_noti_about = '기타 알림을 받습니다.'; +$lang->ncneterlite_block_individual = '개별 문서/댓글 알림 차단'; +$lang->ncneterlite_block_individual_about = '개별적으로 문서/댓글을 차단할 수 있습니다.'; $lang->ncenterlite_activate = '사용'; $lang->ncenterlite_inactivate = '사용 안함'; $lang->ncenterlite_userconfig_about_warning = '주의! 당신은 관리자 권한으로 다른 사용자의 설정창에 접속하였습니다.'; @@ -53,6 +66,8 @@ $lang->ncenterlite_message_mention = '%s님이 "%s"ncenterlite_test_noti = '%s님! 테스트 알림입니다.'; $lang->ncenterlite_vote = '%s님이 회원님의 "%s" %s을 추천하였습니다.'; $lang->ncenterlite_vote_anonymous = '회원님의 "%s" %s이 추천되었습니다.'; +$lang->ncenterlite_scrap = '%s님이 회원님의 "%s" %s을 스크랩하였습니다.'; +$lang->ncenterlite_scrap_anonymous = '회원님의 "%s" %s이 스크랩되었습니다.'; $lang->ncenterlite_admin_content_message = '%1$s님이 "%2$s" 게시판에 "%3$s"라고 글을 남겼습니다.'; $lang->ncenterlite_insert_member_message = '%s회원가입을 환영합니다!!'; $lang->ncenterlite_content_image = '(이미지)'; @@ -71,9 +86,9 @@ $lang->ncenterlite_not_have_message = '새 알림이 없습니다.'; $lang->ncenterlite_thisistest = '[*] 시험용 알림입니다'; $lang->ncenterlite_delete_all = '모두 삭제'; $lang->ncenterlite_more = '더보기'; -$lang->ncenterlite_stop_login_required = '알림센터 설정을 하시려면 로그인 해주세요.'; $lang->ncenterlite_stop_no_permission_other_user = '다른 회원의 설정을 볼 권한이 없습니다.'; $lang->ncenterlite_stop_no_permission_other_user_settings = '다른 회원의 설정을 변경할 권한이 없습니다.'; +$lang->ncenterlite_stop_no_permission_other_user_block_settings = '다른 회원의 설정을 변경할 권한이 없습니다.'; $lang->ncenterlite_message_delete_notification_before = '%s까지 알림 정보를 삭제했습니다.'; $lang->ncenterlite_message_delete_notification_all = '모든 알림을 삭제했습니다.'; $lang->ncenterlite_notice_list = '알림 목록'; @@ -116,12 +131,28 @@ $lang->ncenterlite_commnet_event_noti_some = '대댓글은 알리지 않음'; $lang->ncenterlite_message_event = '쪽지 알림 설정'; $lang->ncenterlite_message_event_about = '쪽지 알림을 사용하지 않음(XE의 기본 쪽지 알림을 사용)'; $lang->ncenterlite_mid_use = '모듈별 사용 설정'; +$lang->ncenterlite_to_unsubscribe = '수신 거부'; +$lang->ncenterlite_subscribe = '수신 거부 안함'; +$lang->ncenterlite_cmd_unsubscribe_settings = '알림 수신 설정'; +$lang->ncenterlite_notify_count = '알림 갯수'; +$lang->ncenterlite_notify_count_about = '알림 리스트의 알림 갯수를 설정합니다.'; +$lang->this_message_unsubscribe = '이 게시글/댓글의 알림 수신 거부'; +$lang->about_this_message_unsubscribe = '이 게시글/댓글의 알림을 수신 거부합니다.'; +$lang->unsubscribe_list = '알림 수신 거부'; +$lang->unsubscribe = '개별 문서/댓글 알림 수신 거부'; +$lang->about_unsubscribe = '개별 문서/댓글 알림 수신을 거부하는 기능을 사용합니다.'; +$lang->fcm_push_format = '푸시 알림 포맷'; +$lang->fcm_push_format_notification = 'notification 사용'; +$lang->fcm_push_format_data = 'data 사용'; +$lang->about_fcm_push_format = 'Google FCM 푸시 알림 발송시 알림 데이터를 어느 항목에 담을지 선택합니다.
                알림을 받아 처리하는 모바일 어플리케이션이나 프론트엔드 코드의 요구사항에 맞추어야 합니다.'; $lang->member_menu_view = '회원 메뉴 표시'; $lang->member_menu_on = '표시'; $lang->member_menu_off = '표시하지 않음'; $lang->about_member_menu_view = '각 회원이 알림 설정을 변경할 수 있는 메뉴를 추가합니다.'; -$lang->user_notify_setting = '회원알림설정'; +$lang->user_notify_setting = '회원 알림 설정'; $lang->about_user_notify_setting = '각 맴버회원들의 알림을 설정할 수 있습니다. 회원들이 알림을 받지 않도록 설정되어 있다면 해당 회원이 알림센터를 통해서 알림을 못받을 수 있습니다.'; +$lang->ncenterlite_push_before_sms = '푸시 성공시 문자 미발송'; +$lang->ncenterlite_push_before_sms_about = '1개 이상의 기기로 푸시 알림을 발송하는 데 성공한 경우, 해당 회원에게는 문자를 발송하지 않습니다.'; $lang->ncenterlite_no_notify = '알림 내역이 없습니다.'; $lang->ncenterlite_all_delete = '전체 삭제'; $lang->ncenterlite_month_before_delete = '한 달 이전의 알림 삭제'; @@ -137,17 +168,23 @@ $lang->about_mention_suffix_always_cut = '\'알림센터님\'이라는 회원과 $lang->mention_limit = '멘션 갯수 제한'; $lang->about_mention_limit = '서버 과부하와 스팸을 방지하기 위해 한 글에서 지나치게 많은 회원들을 호출하지 못하도록 합니다.'; $lang->ncenterlite_msg_setting_error = '설정에 오류가 있습니다. 다시 설정해 주세요.'; -$lang->ncenterlite_use_help = '회원들에게 전송할 알림을 선택할 수 있습니다.
                모든 댓글 작성자에게 알림 기능은 게시글의 작성자가 댓글을 남길경우 게시글을 작성한 작성자를 제외하고 해당 글의 모든 댓글 작성자들에게 알림을 전송합니다.'; -$lang->ncenterlite_dont_use_push = '푸시 알림은 현재 지원중이 아닙니다.'; -$lang->member_phone_variable = '회원전화번호 변수'; -$lang->member_phone_variable_about = '회원전화번호 변수를 선택합니다. 회원전화번호 변수가 1개일 경우 설치시 자동으로 설정이 저장됩니다. 이 경우 설정을 할 필요가 없어서 설정을 선택할 수 없습니다.'; +$lang->ncenterlite_use_help = '회원들에게 전송할 알림의 종류와 전달 방식을 선택할 수 있습니다.'; +$lang->ncenterlite_use_othercomment_help = '글 작성자가 댓글을 남기면 해당 글에 댓글을 작성한 모든 사람에게 알림을 전송합니다.'; +$lang->member_phone_variable = '회원 전화번호 변수'; +$lang->member_phone_variable_about = '문자 알림 사용시 회원의 전화번호를 어디에서 불러올지 선택합니다. 회원정보의 전화번호 또는 확장변수를 선택할 수 있습니다.
                전화번호 형태의 확장변수가 1개뿐인 경우 설치시 자동으로 설정이 저장됩니다.'; +$lang->member_phone_builtin_field = '회원정보의 전화정보'; $lang->anonymous_voter = '추천인 익명'; $lang->about_anonymous_voter = '추천 알림시 추천인을 익명으로 처리합니다.'; -$lang->fail_module_install = '모듈설치에 실패하였습니다.'; +$lang->anonymous_scrap = '스크랩 익명'; +$lang->about_anonymous_scrap = '스크랩 알림시 스크랩한 사람을 익명으로 처리합니다.'; +$lang->highlight_effect = '하이라이트 효과'; +$lang->about_highlight_effect = '댓글 URL로 접근시 해당 댓글에 하이라이트 효과를 줍니다.'; +$lang->fail_module_install = '모듈 설치에 실패하였습니다.'; $lang->cmd_web_notify = '웹 알림'; $lang->cmd_mail_notify = '메일 알림'; $lang->cmd_sms_notify = '문자 알림'; $lang->cmd_push_notify = '푸시 알림'; +$lang->ncenterlite_content_type = '컨텐츠 종류'; $lang->ncenterlite_type_id = '알림 타입 아이디'; $lang->ncenterlite_type_objects = '알림 타입 변수값'; $lang->ncenterlite_type_content = '알림 타입 내용'; @@ -157,3 +194,6 @@ $lang->ncenterlite_custom_list = '커스텀 리스트'; $lang->msg_not_use_user_setting = '유저 세팅을 제공하지 않습니다. 관리자에게 문의하세요.'; $lang->msg_denger_rhymix_user = '경고! 라이믹스에서는 코어에 포함된 순정 알림센터를 사용해야 합니다.
                XE용 알림센터를 삭제하고, 라이믹스 알림센터를 다시 설치해 주시기 바랍니다.'; $lang->msg_test_notifycation_success = '테스트알림더미를 정상적으로 생성하였습니다.'; +$lang->msg_unsubscribe_block_not_support = '개별 수신 거부 기능을 제공하지 않습니다. 관리자에게 문의하세요.'; +$lang->msg_unsubscribe_not_permission = '다른 회원의 구독리스트를 조회할 권한이 없습니다.'; +$lang->msg_unsubscribe_not_in_list = '수신 거부 목록에 없는 컨텐츠입니다.'; diff --git a/modules/ncenterlite/m.skins/default/userconfig.html b/modules/ncenterlite/m.skins/default/userconfig.html index fae62dae5..140b4e787 100644 --- a/modules/ncenterlite/m.skins/default/userconfig.html +++ b/modules/ncenterlite/m.skins/default/userconfig.html @@ -1,66 +1,43 @@
                - -
                -

                {$XE_VALIDATOR_MESSAGE}

                -
                - - -
                - - - - -
                -

                {@$user_str = $member_info->nick_name} - {@$user_str = $logged_info->nick_name} - {@$title_str = Context::getLang('ncenterlite_userconfig_title')} - {sprintf($title_str, $user_str)} -

                - -

                {$lang->ncenterlite_userconfig_about} ({$lang->ncenterlite_userconfig_about_warning})

                -
                - -
                - - -

                {$lang->ncenterlite_comment_noti_about}

                -
                -
                -
                - -
                - - -

                {$lang->ncenterlite_mention_noti_about}

                -
                -
                -
                - -
                - - -

                {$lang->ncenterlite_message_noti_about}

                -
                -
                - -
                -
                -
                - -
                +
                +

                {$XE_VALIDATOR_MESSAGE}

                - -
                \ No newline at end of file + +
                + + + + +
                +

                {@$user_str = $member_info->nick_name} + {@$user_str = $logged_info->nick_name} + {@$title_str = Context::getLang('ncenterlite_userconfig_title')} + {sprintf($title_str, $user_str)} +

                + +

                {$lang->ncenterlite_userconfig_about} ({$lang->ncenterlite_userconfig_about_warning})

                + +
                + +
                + + + + + + + + +

                {sprintf($lang->get('ncenterlite_' . $notify_type . '_noti_about'), $logged_info->nick_name)}

                +
                +
                + +
                +
                +
                + +
                +
                +
                +
                diff --git a/modules/ncenterlite/ncenterlite.admin.controller.php b/modules/ncenterlite/ncenterlite.admin.controller.php index 60c608a90..792a147ec 100644 --- a/modules/ncenterlite/ncenterlite.admin.controller.php +++ b/modules/ncenterlite/ncenterlite.admin.controller.php @@ -1,5 +1,5 @@ disp_act == 'dispNcenterliteAdminSkinsetting') + { + if(intval($obj->notify_count) !== intval($config->notify_count)) + { + Rhymix\Framework\Cache::clearGroup($this->module); + } + } + foreach($config_vars as $val) { if($obj->{$val}) @@ -43,12 +57,9 @@ class ncenterliteAdminController extends ncenterlite } } - if ($obj->disp_act == 'dispNcenterliteAdminNotifyConfig') + if ($obj->disp_act === 'dispNcenterliteAdminConfig') { - if (!$obj->use) - { - $config->use = array(); - } + $config->{'use'} = $obj->{'use'} ?? []; } if ($obj->disp_act == 'dispNcenterliteAdminAdvancedconfig') @@ -62,9 +73,13 @@ class ncenterliteAdminController extends ncenterlite $config->mention_suffixes = array_map('trim', explode(',', $config->mention_suffixes)); } - if($obj->variable_name === '0') + if($obj->variable_name === '-1') { - $config->variable_name = null; + $config->variable_name = '#'; + } + elseif($obj->variable_name === '0') + { + $config->variable_name = ''; } } @@ -79,7 +94,7 @@ class ncenterliteAdminController extends ncenterlite $config->admin_notify_module_srls = array(); } } - + if($obj->disp_act == 'dispNcenterliteAdminOtherComment') { if(!$obj->comment_all) @@ -91,7 +106,15 @@ class ncenterliteAdminController extends ncenterlite $config->comment_all_notify_module_srls = array(); } } - + + if($obj->disp_act == 'dispNcenterliteAdminSkinsetting') + { + if(!$obj->notify_count) + { + $config->notify_count = 0; + } + } + $output = $oModuleController->insertModuleConfig('ncenterlite', $config); if(!$output->toBool()) { @@ -124,6 +147,7 @@ class ncenterliteAdminController extends ncenterlite $args->member_srl = $logged_info->member_srl; $args->srl = 1; $args->target_srl = 1; + $args->target_p_srl = 1; $args->type = $this->_TYPE_TEST; $args->target_type = $this->_TYPE_TEST; $args->target_url = getUrl(''); @@ -162,6 +186,7 @@ class ncenterliteAdminController extends ncenterlite $args->member_srl = $logged_info->member_srl; $args->srl = 1; $args->target_srl = 1; + $args->target_p_srl = 1; $args->type = $this->_TYPE_DOCUMENT; $args->target_type = $this->_TYPE_COMMENT; $args->target_url = getUrl(''); @@ -176,7 +201,7 @@ class ncenterliteAdminController extends ncenterlite } $this->setMessage('msg_test_notifycation_success'); - + if (Context::get('success_return_url')) { $this->setRedirectUrl(Context::get('success_return_url')); diff --git a/modules/ncenterlite/ncenterlite.admin.model.php b/modules/ncenterlite/ncenterlite.admin.model.php index fda8a5043..cc7da6a64 100644 --- a/modules/ncenterlite/ncenterlite.admin.model.php +++ b/modules/ncenterlite/ncenterlite.admin.model.php @@ -1,6 +1,6 @@ getName() === 'Dummy') - { - $sms_available = false; - } - else - { - $sms_available = true; - } - - $push_avaliable = false; - $config = $oNcenterliteModel->getConfig(); Context::set('config', $config); - Context::set('sms_available', $sms_available); - Context::set('push_available', $push_avaliable); + Context::set('notify_types', NcenterliteModel::getNotifyTypes()); + Context::set('sms_available', Rhymix\Framework\SMS::getDefaultDriver()->getName() !== 'Dummy'); + Context::set('push_available', count(Rhymix\Framework\Config::get('push.types') ?? []) > 0); } function dispNcenterliteAdminSeletedmid() @@ -41,7 +29,7 @@ class ncenterliteAdminView extends ncenterlite Context::set('mid_list', $mid_list); Context::set('config', $config); } - + function dispNcenterliteAdminOtherComment() { $oModuleModel = getModel('module'); diff --git a/modules/ncenterlite/ncenterlite.class.php b/modules/ncenterlite/ncenterlite.class.php index a0e23e66d..6e874de84 100644 --- a/modules/ncenterlite/ncenterlite.class.php +++ b/modules/ncenterlite/ncenterlite.class.php @@ -1,5 +1,5 @@ triggers as $trigger) + foreach(['notify_type', 'readed', 'target_body', 'target_browser', 'target_p_srl'] as $column_name) { - if(!$oModuleModel->getTrigger($trigger[0], $trigger[1], $trigger[2], $trigger[3], $trigger[4])) return true; + if(!$oDB->isColumnExists('ncenterlite_notify', $column_name)) + { + return true; + } } - - foreach($this->delete_triggers as $trigger) + foreach(['idx_srl', 'idx_member_srl', 'idx_regdate', 'idx_readed', 'idx_target_srl', 'idx_target_p_srl', 'idx_target_member_srl', 'idx_member_srl_and_readed'] as $index_name) { - if($oModuleModel->getTrigger($trigger[0], $trigger[1], $trigger[2], $trigger[3], $trigger[4])) + if(!$oDB->isIndexExists('ncenterlite_notify', $index_name)) { return true; } } - if(!$oDB->isColumnExists('ncenterlite_notify', 'readed')) + foreach(NcenterliteModel::getUserSetNotifyTypes() as $type => $srl) { - return true; + if(!$oDB->isColumnExists('ncenterlite_user_set', $type . '_notify')) + { + return true; + } + else + { + $column_info = $oDB->getColumnInfo('ncenterlite_user_set', $type . '_notify'); + if (strtolower($column_info->dbtype) !== 'varchar' || $column_info->size < 40) + { + return true; + } + } } - if(!$oDB->isColumnExists('ncenterlite_notify', 'target_body')) + // #1903 #1906 + foreach(['target_browser', 'target_summary'] as $column_name) { - return true; - } - - if(!$oDB->isColumnExists('ncenterlite_notify', 'notify_type')) - { - return true; - } - - if(!$oDB->isColumnExists('ncenterlite_notify', 'target_browser')) - { - return true; - } - - if(!$oDB->isColumnExists('ncenterlite_notify', 'target_p_srl')) - { - return true; - } - - if(!$oDB->isIndexExists('ncenterlite_notify', 'idx_srl')) - { - return true; - } - - if(!$oDB->isIndexExists('ncenterlite_notify', 'idx_target_srl')) - { - return true; - } - - if(!$oDB->isIndexExists('ncenterlite_notify', 'idx_target_p_srl')) - { - return true; - } - - if(!$oDB->isIndexExists('ncenterlite_notify', 'idx_target_member_srl')) - { - return true; - } - - // Composite index to speed up getNotifyList - if(!$oDB->isIndexExists('ncenterlite_notify', 'idx_member_srl_and_readed')) - { - return true; + $column_info = $oDB->getColumnInfo('ncenterlite_notify', $column_name); + if ($column_info->size < 80) + { + return true; + } } // PK duplicate @@ -114,13 +71,29 @@ class ncenterlite extends ModuleObject return true; } + if($oDB->isColumnExists('ncenterlite_user_set','admin_content_notify')) + { + return true; + } + + if($oDB->isColumnExists('ncenterlite_user_set','custom_notify')) + { + return true; + } + + // Extra data column + if (!$oDB->isColumnExists('ncenterlite_notify', 'data')) + { + return true; + } + $config = getModel('ncenterlite')->getConfig(); $member_config = getModel('member')->getMemberConfig(); $variable_name = array(); foreach($member_config->signupForm as $value) { - if($value->type == 'tel') + if(isset($value->type) && $value->type == 'tel') { $variable_name[] = $value->name; } @@ -138,70 +111,64 @@ class ncenterlite extends ModuleObject { $oModuleModel = getModel('module'); $oModuleController = getController('module'); - $oDB = &DB::getInstance(); - - foreach($this->triggers as $trigger) - { - if(!$oModuleModel->getTrigger($trigger[0], $trigger[1], $trigger[2], $trigger[3], $trigger[4])) - { - $oModuleController->insertTrigger($trigger[0], $trigger[1], $trigger[2], $trigger[3], $trigger[4]); - } - } - - foreach($this->delete_triggers as $trigger) - { - if($oModuleModel->getTrigger($trigger[0], $trigger[1], $trigger[2], $trigger[3], $trigger[4])) - { - $oModuleController->deleteTrigger($trigger[0], $trigger[1], $trigger[2], $trigger[3], $trigger[4]); - } - } - - if(!$oDB->isColumnExists('ncenterlite_notify','readed')) - { - $oDB->addColumn('ncenterlite_notify', 'readed', 'char', 1, 'N', true); - $oDB->addIndex('ncenterlite_notify', 'idx_readed', array('readed')); - $oDB->addIndex('ncenterlite_notify', 'idx_member_srl', array('member_srl')); - $oDB->addIndex('ncenterlite_notify', 'idx_regdate', array('regdate')); - } - - if(!$oDB->isColumnExists('ncenterlite_notify','target_browser')) - { - $oDB->addColumn('ncenterlite_notify', 'target_browser', 'varchar', 50, true); - } - - if(!$oDB->isColumnExists('ncenterlite_notify','target_body')) - { - $oDB->addColumn('ncenterlite_notify', 'target_body', 'varchar', 255, true); - } + $oDB = DB::getInstance(); if(!$oDB->isColumnExists('ncenterlite_notify','notify_type')) { $oDB->addColumn('ncenterlite_notify', 'notify_type', 'number', 11, 0); } - + if(!$oDB->isColumnExists('ncenterlite_notify','readed')) + { + $oDB->addColumn('ncenterlite_notify', 'readed', 'char', 1, 'N', true); + } + if(!$oDB->isColumnExists('ncenterlite_notify','target_body')) + { + $oDB->addColumn('ncenterlite_notify', 'target_body', 'varchar', 255, true); + } + if(!$oDB->isColumnExists('ncenterlite_notify','target_browser')) + { + $oDB->addColumn('ncenterlite_notify', 'target_browser', 'varchar', 80, true); + } if(!$oDB->isColumnExists('ncenterlite_notify','target_p_srl')) { $oDB->addColumn('ncenterlite_notify', 'target_p_srl', 'number', 10, true); } - if(!$oDB->isIndexExists('ncenterlite_notify', 'idx_srl')) + foreach(['idx_srl', 'idx_member_srl', 'idx_regdate', 'idx_readed', 'idx_target_srl', 'idx_target_p_srl', 'idx_target_member_srl'] as $index_name) { - $oDB->addIndex('ncenterlite_notify', 'idx_srl', array('srl')); + if(!$oDB->isIndexExists('ncenterlite_notify', $index_name)) + { + $oDB->addIndex('ncenterlite_notify', $index_name, array(substr($index_name, 4))); + } } - if(!$oDB->isIndexExists('ncenterlite_notify', 'idx_target_srl')) + $prev_type = ''; + foreach(NcenterliteModel::getUserSetNotifyTypes() as $type => $srl) { - $oDB->addIndex('ncenterlite_notify', 'idx_target_srl', array('target_srl')); + if(!$oDB->isColumnExists('ncenterlite_user_set', $type . '_notify')) + { + $oDB->addColumn('ncenterlite_user_set', $type . '_notify', 'varchar', 40, null, true, $prev_type ? ($prev_type . '_notify') : 'member_srl'); + } + else + { + $column_info = $oDB->getColumnInfo('ncenterlite_user_set', $type . '_notify'); + if (strtolower($column_info->dbtype) !== 'varchar' || $column_info->size < 40) + { + $oDB->modifyColumn('ncenterlite_user_set', $type . '_notify', 'varchar', 40, null, true, $prev_type ? ($prev_type . '_notify') : 'member_srl'); + } + } + $prev_type = $type; } - if(!$oDB->isIndexExists('ncenterlite_notify', 'idx_target_p_srl')) + // Not use the column + if($oDB->isColumnExists('ncenterlite_user_set','admin_content_notify')) { - $oDB->addIndex('ncenterlite_notify', 'idx_target_p_srl', array('target_p_srl')); + $oDB->dropColumn('ncenterlite_user_set', 'admin_content_notify'); } - if(!$oDB->isIndexExists('ncenterlite_notify', 'idx_target_member_srl')) + if($oDB->isColumnExists('ncenterlite_user_set','custom_notify')) { - $oDB->addIndex('ncenterlite_notify', 'idx_target_member_srl', array('target_member_srl')); + $oDB->dropColumn('ncenterlite_user_set', 'custom_notify'); } // Composite index to speed up getNotifyList @@ -210,12 +177,27 @@ class ncenterlite extends ModuleObject $oDB->addIndex('ncenterlite_notify', 'idx_member_srl_and_readed', array('member_srl', 'readed')); } + // #1903 #1906 + foreach(['target_browser', 'target_summary'] as $column_name) + { + $column_info = $oDB->getColumnInfo('ncenterlite_notify', $column_name); + if ($column_info->size < 80) + { + $oDB->modifyColumn('ncenterlite_notify', $column_name, 'varchar', 80, null, false); + } + } + // PK duplicate if($oDB->isIndexExists('ncenterlite_notify', 'idx_notify')) { $oDB->dropIndex('ncenterlite_notify', 'idx_notify'); } + // Extra data column + if (!$oDB->isColumnExists('ncenterlite_notify', 'data')) + { + $oDB->addColumn('ncenterlite_notify', 'data', 'longtext', null, null, false, 'target_url'); + } $config = getModel('ncenterlite')->getConfig(); if(!$config) @@ -256,18 +238,13 @@ class ncenterlite extends ModuleObject function moduleUninstall() { - $oModuleController = getController('module'); - - foreach($this->triggers as $trigger) - { - $oModuleController->deleteTrigger($trigger[0], $trigger[1], $trigger[2], $trigger[3], $trigger[4]); - } return new BaseObject(); } public static function getSmsHandler() { static $oSmsHandler = null; + $config = getModel('ncenterlite')->getConfig(); if($oSmsHandler === null) { @@ -279,6 +256,11 @@ class ncenterlite extends ModuleObject return $oSmsHandler; } + if($config->variable_name === '#') + { + return $oSmsHandler; + } + $variable_name = array(); $member_config = getModel('member')->getMemberConfig(); foreach($member_config->signupForm as $value) diff --git a/modules/ncenterlite/ncenterlite.controller.php b/modules/ncenterlite/ncenterlite.controller.php index fef6b5751..cc8d81cb1 100644 --- a/modules/ncenterlite/ncenterlite.controller.php +++ b/modules/ncenterlite/ncenterlite.controller.php @@ -1,40 +1,131 @@ true, + 'dispEditorFrame' => true, + 'dispEditorPopup' => true + ); + + /** + * Send any message to a member. + * + * @param int $from_member_srl Sender + * @param int $to_member_srl Recipient + * @param string|object $message Message content + * @param string $url The URL to redirect to when the recipient clicks the notification + * @param int $target_srl The sequence number associated with this notification + * @return BaseObject + */ + public function sendNotification($from_member_srl, $to_member_srl, $message, $url = '', $target_srl = 0) + { + $args = new stdClass(); + $args->config_type = 'custom'; + $args->module_srl = 0; + $args->member_srl = intval($to_member_srl); + $args->type = 'X'; + $args->srl = 0; + $args->target_p_srl = 0; + $args->target_srl = intval($target_srl); + $args->target_member_srl = intval($from_member_srl ?: $to_member_srl); + $args->target_type = $this->_TYPE_CUSTOM; + $args->target_url = $url; + $args->target_browser = ''; + $args->target_summary = ''; + $args->target_content = null; + + if (is_object($message)) + { + $args->target_summary = $message->summary ?? ''; + $args->target_body = $message->subject ?? ''; + $args->target_url = empty($message->url) ? $args->target_url : $message->url; + $args->extra_content = $message->content ?? ''; + if (isset($message->data)) + { + $args->extra_data = is_object($message->data) ? get_object_vars($message->data) : (array)($message->data); + } + } + else + { + $args->target_body = $message; + } + + $output = $this->_insertNotify($args); + if(!$output->toBool()) + { + return $output; + } + else + { + return new BaseObject; + } + } + function procNcenterliteUserConfig() { - $logged_info = Context::get('logged_info'); - $oNcenterliteModel = getModel('ncenterlite'); - $config = $oNcenterliteModel->getConfig(); + $config = NcenterliteModel::getConfig(); + if(!Rhymix\Framework\Session::isMember()) + { + throw new Rhymix\Framework\Exceptions\MustLogin; + } if($config->user_notify_setting != 'Y') { throw new Rhymix\Framework\Exception('msg_not_use_user_setting'); } - $member_srl = Context::get('member_srl'); - - if(!$member_srl) - { - $member_srl = $logged_info->member_srl; - } - - if($logged_info->member_srl != $member_srl && $logged_info->is_admin != 'Y') + // Disable modifying other user's config #1925 #2148 + $member_srl = intval(Context::get('member_srl')) ?: $this->user->member_srl; + if ($this->user->member_srl !== $member_srl) { throw new Rhymix\Framework\Exception('ncenterlite_stop_no_permission_other_user_settings'); } - $user_config = $oNcenterliteModel->getUserConfig($member_srl); - - $obj = Context::getRequestVars(); + $vars = Context::getRequestVars(); + $notify_types = NcenterliteModel::getUserSetNotifyTypes(); + $is_old_skin = false; + foreach ($notify_types as $type => $srl) + { + if (isset($vars->{$type . '_notify'})) + { + $is_old_skin = true; + break; + } + } $args = new stdClass(); $args->member_srl = $member_srl; - $args->comment_notify = $obj->comment_notify; - $args->mention_notify = $obj->mention_notify; - $args->message_notify = $obj->message_notify; + foreach ($notify_types as $type => $srl) + { + $disabled_list = array(); + if ($is_old_skin) + { + if (isset($vars->{$type . '_notify'}) && $vars->{$type . '_notify'} === 'N') + { + $disabled_list = ['!web', '!mail', '!sms', '!push']; + } + } + else + { + foreach (['web', 'mail', 'sms', 'push'] as $method) + { + if (isset($config->use[$type][$method]) && $config->use[$type][$method]) + { + if (!isset($vars->use[$type][$method]) || !$vars->use[$type][$method]) + { + $disabled_list[] = '!' . $method; + } + } + } + } + $args->{$type . '_notify'} = implode(',', $disabled_list); + } - if(!$user_config->data) + $user_config = NcenterliteModel::getUserConfig($member_srl); + if(!$user_config) { $insert_output = executeQuery('ncenterlite.insertUserConfig', $args); if(!$insert_output->toBool()) @@ -50,9 +141,9 @@ class ncenterliteController extends ncenterlite return $update_output; } } + Rhymix\Framework\Cache::delete('ncenterlite:user_config:' . $member_srl); $this->setMessage('success_updated'); - if (Context::get('success_return_url')) { $this->setRedirectUrl(Context::get('success_return_url')); @@ -63,6 +154,163 @@ class ncenterliteController extends ncenterlite } } + function procNcenterliteInsertUnsubscribe() + { + $config = NcenterliteModel::getConfig(); + if(!Rhymix\Framework\Session::isMember()) + { + throw new Rhymix\Framework\Exceptions\MustLogin; + } + if($config->unsubscribe !== 'Y') + { + throw new Rhymix\Framework\Exception('msg_unsubscribe_block_not_support'); + } + + $obj = Context::getRequestVars(); + + if(!$this->user->member_srl || (!intval($obj->unsubscribe_srl) && !intval($obj->target_srl))) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + + if($obj->target_srl) + { + $oNcenterliteModel = getModel('ncenterlite'); + $userBlockData = $oNcenterliteModel->getUserUnsubscribeConfigByTargetSrl($obj->target_srl, $this->user->member_srl); + + // If there was a record directed by unsubscribe_srl, the record should be used to validate the input data. + if($userBlockData) + { + if (!intval($obj->unsubscribe_srl)) + { + $obj->unsubscribe_srl = $userBlockData->unsubscribe_srl; + } + + if (intval($obj->unsubscribe_srl) != intval($userBlockData->unsubscribe_srl)) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + } + } + + if(!$userBlockData && $obj->unsubscribe_srl) + { + $userBlockData = $oNcenterliteModel->getUserUnsubscribeConfigByUnsubscribeSrl($obj->unsubscribe_srl); + + // The input member_srl from the POST or GET might not equal to the member_srl from the record of unsubscribe_srl. + if(intval($this->user->member_srl) != intval($userBlockData->member_srl)) + { + throw new Rhymix\Framework\Exception('ncenterlite_stop_no_permission_other_user_block_settings'); + } + + // If there was a record directed by unsubscribe_srl, the record should be used to validate the input data. + if($userBlockData) + { + if (!intval($obj->target_srl)) + { + $obj->target_srl = $userBlockData->target_srl; + } + + if (intval($obj->target_srl) != intval($userBlockData->target_srl)) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + } + } + + if($userBlockData) + { + $obj->unsubscribe_srl = $userBlockData->unsubscribe_srl; + } + + // Content type can be document and comment, now. However, the default type cannot be specified, as the type can be another in the future. + if($obj->unsubscribe_type == 'document') + { + $text = self::_createSummary(getModel('document')->getDocument($obj->target_srl)->get('title')); + } + elseif($obj->unsubscribe_type == 'comment') + { + $comment = getModel('comment')->getComment($obj->target_srl); + $contentString = $comment->getContentText(30); + $text = strlen($contentString) ? $contentString : lang('comment.no_text_comment'); + } + else + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + + $args = new stdClass(); + $args->member_srl = $this->user->member_srl; + $args->target_srl = $obj->target_srl; + if($obj->unsubscribe_type == 'document') + { + $args->document_srl = $obj->target_srl; + } + else + { + $args->document_srl = $comment->get('document_srl'); + } + $args->unsubscribe_type = $obj->unsubscribe_type; + $args->text = $text; + + if($obj->value == 'Y') + { + // 데이터가 있으면 차단, 데이터가 없으면 차단하지 않기 때문에 따로 업데이트를 하지 않는다. + if(!$userBlockData) + { + $args->unsubscribe_srl = getNextSequence(); + $output = executeQuery('ncenterlite.insertUnsubscribe', $args); + if(!$output->toBool()) + { + return $output; + } + } + else + { + $args->unsubscribe_srl = $userBlockData->unsubscribe_srl; + } + } + else + { + if(!$obj->unsubscribe_srl && !$userBlockData) + { + throw new Rhymix\Framework\Exception('msg_unsubscribe_not_in_list'); + } + + $args->unsubscribe_srl = $obj->unsubscribe_srl; + $output = executeQuery('ncenterlite.deleteUnsubscribe', $args); + if(!$output->toBool()) + { + return $output; + } + } + + $this->setMessage('success_updated'); + + if(Context::get('is_popup') != 'Y') + { + if (Context::get('success_return_url')) + { + $this->setRedirectUrl(Context::get('success_return_url')); + } + else + { + $this->setRedirectUrl(getNotEncodedUrl('act', 'dispNcenterliteUnsubscribeList', 'member_srl', $this->user->member_srl)); + } + } + else + { + if (Context::get('success_return_url')) + { + $this->setRedirectUrl(Context::get('success_return_url')); + } + else + { + $this->setRedirectUrl(getNotEncodedUrl('act', 'dispNcenterliteUnsubscribeList', 'target_srl', $obj->target_srl, 'unsubscribe_type', $obj->unsubscribe_type)); + } + } + } + function triggerAfterDeleteMember($obj) { $member_srl = $obj->member_srl; @@ -82,7 +330,7 @@ class ncenterliteController extends ncenterlite { $this->removeFlagFile($args->member_srl); } - + // Delete to user setting. $userSetOutput = executeQuery('ncenterlite.deleteNcenterliteUserSettingData', $args); if(!$userSetOutput->toBool()) @@ -93,7 +341,7 @@ class ncenterliteController extends ncenterlite function triggerAfterInsertDocument(&$obj) { - if ($obj->disable_triggers[$this->module] === true) + if (isset($obj->disable_triggers[$this->module]) && $obj->disable_triggers[$this->module] === true) { return; } @@ -101,7 +349,7 @@ class ncenterliteController extends ncenterlite $oModuleModel = getModel('module'); $oNcenterliteModel = getModel('ncenterlite'); - $config = $oNcenterliteModel->getConfig(); + $config = NcenterliteModel::getConfig(); $mention_targets = $this->_getMentionTarget($obj->title . ' ' . $obj->content); @@ -140,10 +388,12 @@ class ncenterliteController extends ncenterlite $args->target_srl = $obj->document_srl; $args->type = $this->_TYPE_DOCUMENT; $args->target_type = $this->_TYPE_ADMIN_DOCUMENT; - $args->target_url = getNotEncodedUrl('', 'document_srl', $obj->document_srl); - $args->target_summary = cut_str(strip_tags($obj->title), 50); + $args->target_url = getNotEncodedUrl('', 'mid', $module_info->mid, 'document_srl', $obj->document_srl); + $args->target_summary = self::_createSummary($obj->title); + $args->target_content = self::_createContent($obj->content) ?: (strpos($obj->content, 'regdate = date('YmdHis'); $args->target_browser = $module_info->browser_title; + $args->module_srl = $obj->module_srl; $args->notify = $this->_getNotifyId($args); $output = $this->_insertNotify($args, $is_anonymous); if(!$output->toBool()) @@ -156,8 +406,9 @@ class ncenterliteController extends ncenterlite function triggerAfterInsertComment($obj) { + /** @var ncenterliteModel $oNcenterliteModel */ $oNcenterliteModel = getModel('ncenterlite'); - $config = $oNcenterliteModel->getConfig(); + $config = NcenterliteModel::getConfig(); $logged_info = Context::get('logged_info'); @@ -166,37 +417,43 @@ class ncenterliteController extends ncenterlite $module_info = $oModuleModel->getModuleInfoByDocumentSrl($document_srl); $comment_srl = $obj->comment_srl; $parent_srl = $obj->parent_srl; - $content = $obj->content; - $regdate = $obj->regdate; + $content = self::_createSummary($obj->content) ?: (strpos($obj->content, '_isAnonymous($this->_TYPE_COMMENT, $obj); $oDocumentModel = getModel('document'); $oDocument = $oDocumentModel->getDocument($document_srl); - - if($config->comment_all == 'Y' && $obj->member_srl == $oDocument->get('member_srl') && !$obj->parent_srl && (is_array($config->comment_all_notify_module_srls) && in_array($module_info->module_srl, $config->comment_all_notify_module_srls))) + // 댓글을 남긴 이력이 있는 회원들에게만 알림을 전송 + if($config->comment_all == 'Y' && abs($obj->member_srl) == abs($oDocument->get('member_srl')) && !$obj->parent_srl && (is_array($config->comment_all_notify_module_srls) && in_array($module_info->module_srl, $config->comment_all_notify_module_srls))) { $comment_args = new stdClass(); - $comment_args->member_srl = $obj->member_srl; + $comment_args->member_srl = [$obj->member_srl, abs($obj->member_srl)]; $comment_args->document_srl = $obj->document_srl; - $other_comment = executeQuery('ncenterlite.getOtherCommentByMemberSrl', $comment_args); + $other_comment = executeQueryArray('ncenterlite.getOtherCommentByMemberSrl', $comment_args); foreach ($other_comment->data as $value) { + if($config->user_notify_setting == 'Y' && $value->comment_notify === 'N') + { + continue; + } + $args = new stdClass(); $args->config_type = 'comment_all'; - $args->member_srl = $value->member_srl; + $args->member_srl = abs($value->member_srl); $args->target_p_srl = $obj->comment_srl; $args->srl = $obj->document_srl; $args->target_srl = $obj->comment_srl; $args->type = $this->_TYPE_COMMENT; $args->target_type = $this->_TYPE_COMMENT_ALL; - $args->target_url = getNotEncodedUrl('', 'document_srl', $document_srl, '_comment_srl', $comment_srl) . '#comment_' . $comment_srl; - $args->target_summary = cut_str(trim(utf8_normalize_spaces(strip_tags($oDocument->get('title')))), 50) ?: (strpos($content, 'target_url = getNotEncodedUrl('', 'mid', $module_info->mid, 'document_srl', $document_srl, 'comment_srl', $comment_srl) . '#comment_' . $comment_srl; + $args->target_summary = $content; + $args->target_content = null; $args->target_nick_name = $obj->nick_name; $args->target_email_address = $obj->email_address; $args->regdate = date('YmdHis'); $args->target_browser = $module_info->browser_title; + $args->module_srl = $module_info->module_srl; $args->notify = $this->_getNotifyId($args); $output = $this->_insertNotify($args, $is_anonymous); if(!$output->toBool()) @@ -205,10 +462,11 @@ class ncenterliteController extends ncenterlite } } } - + $obj->admin_comment_notify = false; $admin_list = $oNcenterliteModel->getMemberAdmins(); + // 관리자에게 알림을 전송 if(isset($config->use['admin_content']) && is_array($config->admin_notify_module_srls) && in_array($module_info->module_srl, $config->admin_notify_module_srls)) { foreach($admin_list as $admins) @@ -225,12 +483,14 @@ class ncenterliteController extends ncenterlite $args->target_srl = $obj->comment_srl; $args->type = $this->_TYPE_COMMENT; $args->target_type = $this->_TYPE_ADMIN_COMMENT; - $args->target_url = getNotEncodedUrl('', 'document_srl', $document_srl, '_comment_srl', $comment_srl) . '#comment_' . $comment_srl; - $args->target_summary = cut_str(trim(utf8_normalize_spaces(strip_tags($content))), 50) ?: (strpos($content, 'target_url = getNotEncodedUrl('', 'mid', $module_info->mid, 'document_srl', $document_srl, 'comment_srl', $comment_srl) . '#comment_' . $comment_srl; + $args->target_summary = $content; + $args->target_content = null; $args->target_nick_name = $obj->nick_name; $args->target_email_address = $obj->email_address; $args->regdate = date('YmdHis'); $args->target_browser = $module_info->browser_title; + $args->module_srl = $module_info->module_srl; $args->notify = $this->_getNotifyId($args); $output = $this->_insertNotify($args, $is_anonymous); if($output->toBool()) @@ -244,7 +504,6 @@ class ncenterliteController extends ncenterlite } } - // check use the mention option. $notify_member_srls = array(); if(isset($config->use['mention'])) { @@ -267,83 +526,100 @@ class ncenterliteController extends ncenterlite { $oCommentModel = getModel('comment'); $oComment = $oCommentModel->getComment($parent_srl); - $member_srl = $oComment->member_srl; + $abs_member_srl = abs($oComment->member_srl); if($config->user_notify_setting == 'Y') { - $comment_member_config = $oNcenterliteModel->getUserConfig($member_srl); - $parent_member_config = $comment_member_config->data; - if($parent_member_config->comment_notify == 'N') + $parent_member_config = NcenterliteModel::getUserConfig($abs_member_srl); + if($parent_member_config && !$parent_member_config->comment_comment) { return; } } - if(is_array($admin_list) && in_array(abs($member_srl), $admin_list) && isset($config->use['admin_content']) && $obj->admin_comment_notify == true) + if(is_array($admin_list) && in_array($abs_member_srl, $admin_list) && isset($config->use['admin_content']) && $obj->admin_comment_notify == true) { return; } - if(!in_array(abs($member_srl), $notify_member_srls) && (!Context::get('is_logged') || ($member_srl != 0 && abs($member_srl) != $logged_info->member_srl))) + if(!in_array($abs_member_srl, $notify_member_srls) && (!Context::get('is_logged') || ($abs_member_srl != 0 && $abs_member_srl != $logged_info->member_srl))) { + // 받는 사람이 문서를 차단하고 있을 경우 + if($oNcenterliteModel->getUserUnsubscribeConfigByTargetSrl($document_srl, $abs_member_srl)) + { + return; + } + + if($oNcenterliteModel->getUserUnsubscribeConfigByTargetSrl($parent_srl, $abs_member_srl)) + { + return; + } + $args = new stdClass(); $args->config_type = 'comment_comment'; - $args->member_srl = abs($member_srl); + $args->member_srl = $abs_member_srl; $args->srl = $obj->document_srl; $args->target_p_srl = $parent_srl; $args->target_srl = $obj->comment_srl; $args->type = $this->_TYPE_COMMENT; $args->target_type = $this->_TYPE_COMMENT; - $args->target_url = getNotEncodedUrl('', 'document_srl', $document_srl, '_comment_srl', $comment_srl) . '#comment_' . $comment_srl; - $args->target_summary = cut_str(trim(utf8_normalize_spaces(strip_tags($content))), 50) ?: (strpos($content, 'target_url = getNotEncodedUrl('', 'mid', $module_info->mid, 'document_srl', $document_srl, 'comment_srl', $comment_srl) . '#comment_' . $comment_srl; + $args->target_summary = $content; + $args->target_content = null; $args->target_nick_name = $obj->nick_name; $args->target_email_address = $obj->email_address; - $args->regdate = $regdate; + $args->regdate = $obj->regdate; $args->target_browser = $module_info->browser_title; + $args->module_srl = $module_info->module_srl; $args->notify = $this->_getNotifyId($args); $output = $this->_insertNotify($args, $is_anonymous); if(!$output->toBool()) { return $output; } - $notify_member_srls[] = abs($member_srl); + $notify_member_srls[] = $abs_member_srl; } } // 대댓글이 아니고, 게시글의 댓글을 남길 경우 if(!$parent_srl || ($parent_srl && isset($config->use['comment_comment']))) { - $member_srl = $oDocument->get('member_srl'); + $abs_member_srl = abs($oDocument->get('member_srl')); - if(is_array($admin_list) && in_array(abs($member_srl), $admin_list) && isset($config->use['admin_content']) && $obj->admin_comment_notify == true) + if(is_array($admin_list) && in_array($abs_member_srl, $admin_list) && isset($config->use['admin_content']) && $obj->admin_comment_notify == true) + { + return; + } + + if($oNcenterliteModel->getUserUnsubscribeConfigByTargetSrl($document_srl, $abs_member_srl)) { return; } if($config->user_notify_setting == 'Y') { - $comment_member_config = $oNcenterliteModel->getUserConfig($member_srl); - $document_comment_member_config = $comment_member_config->data; - if($document_comment_member_config->comment_notify == 'N') + $document_comment_member_config = NcenterliteModel::getUserConfig($abs_member_srl); + if($document_comment_member_config && !$document_comment_member_config->comment) { return; } } - - if(!in_array(abs($member_srl), $notify_member_srls) && (!$logged_info || ($member_srl != 0 && abs($member_srl) != $logged_info->member_srl))) + if(!in_array($abs_member_srl, $notify_member_srls) && (!$logged_info || ($abs_member_srl != 0 && $abs_member_srl != $logged_info->member_srl))) { $args = new stdClass(); $args->config_type = 'comment'; - $args->member_srl = abs($member_srl); + $args->member_srl = $abs_member_srl; $args->srl = $document_srl; $args->target_p_srl = $comment_srl; $args->target_srl = $comment_srl; $args->type = $this->_TYPE_DOCUMENT; $args->target_type = $this->_TYPE_COMMENT; - $args->target_url = getNotEncodedUrl('', 'document_srl', $document_srl, '_comment_srl', $comment_srl) . '#comment_' . $comment_srl; - $args->target_summary = cut_str(trim(utf8_normalize_spaces(strip_tags($content))), 50) ?: (strpos($content, 'target_url = getNotEncodedUrl('', 'mid', $module_info->mid, 'document_srl', $document_srl, 'comment_srl', $comment_srl) . '#comment_' . $comment_srl; + $args->target_summary = $content; + $args->target_content = null; $args->target_nick_name = $obj->nick_name; $args->target_email_address = $obj->email_address; - $args->regdate = $regdate; + $args->regdate = $obj->regdate; $args->target_browser = $module_info->browser_title; + $args->module_srl = $module_info->module_srl; $args->notify = $this->_getNotifyId($args); $output = $this->_insertNotify($args, $is_anonymous); if(!$output->toBool()) @@ -356,8 +632,7 @@ class ncenterliteController extends ncenterlite function triggerAfterSendMessage($obj) { - $oNcenterliteModel = getModel('ncenterlite'); - $config = $oNcenterliteModel->getConfig(); + $config = NcenterliteModel::getConfig(); $communication_config = getModel('communication')->getConfig(); if($communication_config->enable_message != 'Y') @@ -372,9 +647,8 @@ class ncenterliteController extends ncenterlite if($config->user_notify_setting == 'Y') { - $messages_member_config = $oNcenterliteModel->getUserConfig($obj->receiver_srl); - $message_member_config = $messages_member_config->data; - if($message_member_config->message_notify == 'N') + $target_member_config = NcenterliteModel::getUserConfig($obj->receiver_srl); + if($target_member_config && !$target_member_config->message) { return; } @@ -390,6 +664,7 @@ class ncenterliteController extends ncenterlite $args->type = $this->_TYPE_MESSAGE; $args->target_type = $this->_TYPE_MESSAGE; $args->target_summary = $obj->title; + $args->target_content = mb_substr(trim(utf8_normalize_spaces(strip_tags($obj->content))), 0, 200, 'UTF-8'); $args->regdate = date('YmdHis'); $args->notify = $this->_getNotifyId($args); $args->target_url = getNotEncodedUrl('', 'act', 'dispCommunicationMessages', 'message_srl', $obj->related_srl); @@ -400,122 +675,206 @@ class ncenterliteController extends ncenterlite } } - function triggerAfterVotedupdate(&$obj) + function triggerAfterScrap($obj) { - $oNcenterliteModel = getModel('ncenterlite'); - $config = $oNcenterliteModel->getConfig(); + $config = NcenterliteModel::getConfig(); + if(!isset($config->use['scrap'])) + { + return; + } + + if($config->user_notify_setting === 'Y') + { + $target_member_config = NcenterliteModel::getUserConfig(abs($obj->target_member_srl)); + if($target_member_config && !$target_member_config->scrap) + { + return; + } + } + + $oModuleModel = getModel('module'); + $module_info = $oModuleModel->getModuleInfoByDocumentSrl($obj->document_srl); + + $args = new stdClass(); + $args->config_type = 'scrap'; + $args->target_member_srl = abs($obj->member_srl); + $args->member_srl = $obj->target_member_srl; + $args->srl = $obj->document_srl; + $args->target_p_srl = '1'; + $args->target_srl = $obj->document_srl; + $args->type = $this->_TYPE_DOCUMENT; + $args->target_type = $this->_TYPE_SCRAPPED; + $args->target_summary = $obj->title; + $args->target_content = null; + $args->regdate = date('YmdHis'); + $args->notify = $this->_getNotifyId($args); + $args->target_url = getNotEncodedUrl('', 'mid', $module_info->mid, 'document_srl', $obj->document_srl); + $output = $this->_insertNotify($args, $config->anonymous_scrap !== 'N'); + if(!$output->toBool()) + { + return $output; + } + } + + function triggerAfterDocumentVotedUpdate(&$obj) + { + $config = NcenterliteModel::getConfig(); if(!isset($config->use['vote'])) { return; } - if($obj->point < 0) + if($obj->update_target !== 'voted_count') { return; } + if($config->user_notify_setting == 'Y') + { + $target_member_config = NcenterliteModel::getUserConfig(abs($obj->member_srl)); + if ($target_member_config && !$target_member_config->vote) + { + return; + } + } $oDocumentModel = getModel('document'); $oDocument = $oDocumentModel->getDocument($obj->document_srl, false, false); + $module_info = getModel('module')->getModuleInfoByDocumentSrl($obj->document_srl); $args = new stdClass(); $args->config_type = 'vote'; - $args->member_srl = $obj->member_srl; + $args->member_srl = abs($obj->member_srl); $args->srl = $obj->document_srl; $args->target_p_srl = '1'; $args->target_srl = $obj->document_srl; $args->type = $this->_TYPE_DOCUMENT; $args->target_type = $this->_TYPE_VOTED; $args->target_summary = $oDocument->get('title'); + $args->target_content = null; $args->regdate = date('YmdHis'); $args->notify = $this->_getNotifyId($args); - $args->target_url = getNotEncodedUrl('', 'document_srl', $obj->document_srl); - $output = $this->_insertNotify($args); - if(!$output->toBool()) + $args->target_url = getNotEncodedUrl('', 'mid', $module_info->mid, 'document_srl', $obj->document_srl); + $args->module_srl = $obj->module_srl; + $this->_insertNotify($args, $config->anonymous_voter !== 'N'); + } + + function triggerAfterDocumentVotedCancel($obj) + { + $config = NcenterliteModel::getConfig(); + if(empty($config->use)) { - return $output; + return; + } + if($obj->update_target !== 'voted_count') + { + return; + } + if(!$this->user->member_srl) + { + return; + } + + if($config->anonymous_voter === 'Y') + { + $member_srl = -1 * $this->user->member_srl; + } + else + { + $member_srl = $this->user->member_srl; + } + + $args = new stdClass(); + $args->type = $this->_TYPE_DOCUMENT; + $args->target_type = $this->_TYPE_VOTED; + $args->target_srl = $obj->document_srl; + $args->target_member_srl = $member_srl; + $output = executeQuery('ncenterlite.deleteNotifyByTargetType', $args); + if($output->toBool()) + { + $this->removeFlagFile(abs($obj->member_srl)); } } function triggerAfterCommentVotedCount($obj) { - $oNcenterliteModel = getModel('ncenterlite'); - $config = $oNcenterliteModel->getConfig(); + $config = NcenterliteModel::getConfig(); if(!isset($config->use['vote'])) { return; } - if($obj->point < 0) + if($obj->update_target !== 'voted_count') { return; } + if($config->user_notify_setting == 'Y') + { + $target_member_config = NcenterliteModel::getUserConfig(abs($obj->member_srl)); + if ($target_member_config && !$target_member_config->vote) + { + return; + } + } - $oCommentModel = new commentModel(); + $oCommentModel = getModel('comment'); $oComment = $oCommentModel->getComment($obj->comment_srl); $content = $oComment->get('content'); $document_srl = $oComment->get('document_srl'); + $module_info = getModel('module')->getModuleInfoByDocumentSrl($document_srl); $args = new stdClass(); $args->config_type = 'vote'; - $args->member_srl = $obj->member_srl; + $args->member_srl = abs($obj->member_srl); $args->srl = $document_srl; $args->target_p_srl = $obj->comment_srl; $args->target_srl = $obj->comment_srl; $args->type = $this->_TYPE_COMMENT; $args->target_type = $this->_TYPE_VOTED; - $args->target_summary = cut_str(trim(utf8_normalize_spaces(strip_tags($content))), 50); + $args->target_summary = self::_createSummary($content) ?: (strpos($content, 'target_content = null; $args->regdate = date('YmdHis'); + $args->module_srl = $obj->module_srl; $args->notify = $this->_getNotifyId($args); - $args->target_url = getNotEncodedUrl('', 'document_srl', $document_srl, '_comment_srl', $obj->comment_srl) . '#comment_' . $obj->comment_srl; - $output = $this->_insertNotify($args); - if(!$output->toBool()) - { - return $output; - } + $args->target_url = getNotEncodedUrl('', 'mid', $module_info->mid, 'document_srl', $document_srl, 'comment_srl', $obj->comment_srl) . '#comment_' . $obj->comment_srl; + $this->_insertNotify($args, $config->anonymous_voter !== 'N'); } function triggerAfterCommentVotedCancel($obj) { - $oCommentModel = new commentModel(); - $oComment = $oCommentModel->getComment($obj->comment_srl); - - $document_srl = $oComment->get('document_srl'); + $config = NcenterliteModel::getConfig(); + if(empty($config->use)) + { + return; + } + if($obj->update_target !== 'voted_count') + { + return; + } + if(!$this->user->member_srl) + { + return; + } $args = new stdClass(); $args->type = $this->_TYPE_COMMENT; $args->target_type = $this->_TYPE_VOTED; $args->target_srl = $obj->comment_srl; - $args->srl = $document_srl; + $args->target_member_srl = $this->user->member_srl; $output = executeQuery('ncenterlite.deleteNotifyByTargetType', $args); if($output->toBool()) { - $this->removeFlagFile($obj->member_srl); - } - else - { - return $output; + $this->removeFlagFile(abs($obj->member_srl)); } } function triggerAfterDeleteComment(&$obj) { - $oNcenterliteModel = getModel('ncenterlite'); - $config = $oNcenterliteModel->getConfig(); + $config = NcenterliteModel::getConfig(); if(empty($config->use)) { return; } - $notify_list = $oNcenterliteModel->getNotifyMemberSrlByCommentSrl($obj->comment_srl); - - // 대댓글의 대댓글일 경우 혹은 중복적으로 받는 경우 comment_srl 당 2개이상 notify가 생성될 수 있다. - $member_srls = array(); - foreach($notify_list as $value) - { - if(!in_array($value->member_srl, $member_srls)) - { - $member_srls[] = $value->member_srl; - } - } + $member_srls = ncenterliteModel::getInstance()->getNotifyMemberSrlBySrl($obj->comment_srl); $args = new stdClass(); $args->srl = $obj->comment_srl; @@ -531,26 +890,29 @@ class ncenterliteController extends ncenterlite function triggerAfterDeleteDocument(&$obj) { - $oNcenterliteModel = getModel('ncenterlite'); - $config = $oNcenterliteModel->getConfig(); + $config = NcenterliteModel::getConfig(); if(empty($config->use)) { return; } + $member_srls = ncenterliteModel::getInstance()->getNotifyMemberSrlBySrl($obj->document_srl); + $args = new stdClass(); $args->srl = $obj->document_srl; $output = executeQuery('ncenterlite.deleteNotifyBySrl', $args); - if(!$output->toBool()) + if($output->toBool()) { - return $output; + foreach($member_srls as $member_srl) + { + $this->removeFlagFile($member_srl); + } } } function triggerAfterMoveToTrash(&$obj) { - $oNcenterliteModel = getModel('ncenterlite'); - $notify_list = $oNcenterliteModel->getNotifyListByDocumentSrl($obj->document_srl); + $notify_list = ncenterliteModel::getInstance()->getNotifyListByDocumentSrl($obj->document_srl); $member_srls = array(); foreach($notify_list as $value) @@ -561,7 +923,7 @@ class ncenterliteController extends ncenterlite } } - $config = $oNcenterliteModel->getConfig(); + $config = NcenterliteModel::getConfig(); if(empty($config->use)) { @@ -581,10 +943,40 @@ class ncenterliteController extends ncenterlite } } + function triggerAfterMoveToTrashComment($obj) + { + $notify_list = ncenterliteModel::getInstance()->getNotifyListByCommentSrl($obj->document_srl, $obj->comment_srl); + + $member_srls = array(); + foreach($notify_list as $value) + { + if(!in_array($value->member_srl, $member_srls)) + { + $member_srls[] = $value->member_srl; + } + } + + $config = NcenterliteModel::getConfig(); + if(empty($config->use)) + { + return; + } + + $args = new stdClass(); + $args->srl = $obj->comment_srl; + $output = executeQuery('ncenterlite.deleteNotifyBySrl', $args); + if($output->toBool()) + { + foreach($member_srls as $member_srl) + { + //Remove flag files + $this->removeFlagFile($member_srl); + } + } + } + function triggerAfterModuleHandlerProc(&$oModule) { - $vars = Context::getRequestVars(); - $logged_info = Context::get('logged_info'); $args = new stdClass(); if($oModule->getLayoutFile() == 'popup_layout.html') @@ -592,19 +984,22 @@ class ncenterliteController extends ncenterlite Context::set('ncenterlite_is_popup', TRUE); } - $oNcenterliteModel = getModel('ncenterlite'); - $config = $oNcenterliteModel->getConfig(); + $config = NcenterliteModel::getConfig(); // if the array is empty, lets return. if(empty($config->use)) { return; } + if(empty($oModule->act)) + { + return; + } if($oModule->act == 'dispBoardReplyComment') { $comment_srl = Context::get('comment_srl'); $logged_info = Context::get('logged_info'); - if($comment_srl && $logged_info) + if($comment_srl && $logged_info && $logged_info->member_srl) { $args->target_srl = $comment_srl; $args->member_srl = $logged_info->member_srl; @@ -616,14 +1011,12 @@ class ncenterliteController extends ncenterlite } } } - else if($oModule->act == 'dispBoardContent') + elseif(preg_match('/^disp[A-Z][a-z0-9_]+Content$/', $oModule->act)) { - $comment_srl = Context::get('_comment_srl'); $document_srl = Context::get('document_srl'); - $oDocument = Context::get('oDocument'); $logged_info = Context::get('logged_info'); - if($document_srl && $config->document_read == 'Y' && $logged_info->member_srl) + if($document_srl && $config->document_read == 'Y' && $logged_info && $logged_info->member_srl) { $args->srl = $document_srl; $args->member_srl = $logged_info->member_srl; @@ -634,35 +1027,6 @@ class ncenterliteController extends ncenterlite $this->removeFlagFile($args->member_srl); } } - - if($comment_srl && $document_srl && $oDocument) - { - $_comment_list = $oDocument->getComments(); - if($_comment_list) - { - if(array_key_exists($comment_srl, $_comment_list)) - { - $url = getNotEncodedUrl('_comment_srl', '') . '#comment_' . $comment_srl; - } - else - { - $cpage = $oDocument->comment_page_navigation->cur_page; - if($cpage > 1) - { - $url = getNotEncodedUrl('cpage', $cpage - 1) . '#comment_' . $comment_srl; - } - else - { - $url = getNotEncodedUrl('_comment_srl', '', 'cpage', '') . '#comment_' . $comment_srl; - } - } - - $url = str_replace('&', '&', $url); - header('location: ' . $url); - Context::close(); - exit; - } - } } elseif($oModule->act == 'dispCommunicationMessages') { @@ -680,121 +1044,59 @@ class ncenterliteController extends ncenterlite } } } + } - // 지식인 모듈의 의견 - // TODO: 지식인 모듈을 사용하는지 안하는지 현재로써는 모르기 때문에 일단은 이 코드를 유지 하였다가 나중에 라이믹스용 지식인이 나온다면 변경하기 - if($oModule->act == 'procKinInsertComment') + public function triggerAfterGetComments($comment_list) + { + if (Context::get('act') === 'dispBoardCommentPage' && $comment_list) { - // 글, 댓글 구분 - $parent_type = ($vars->document_srl == $vars->parent_srl) ? 'DOCUMENT' : 'COMMENT'; - if($parent_type == 'DOCUMENT') - { - $oDocumentModel = getModel('document'); - $oDocument = $oDocumentModel->getDocument($vars->document_srl); - $member_srl = $oDocument->get('member_srl'); - $type = $this->_TYPE_DOCUMENT; - } - else - { - $oCommentModel = getModel('comment'); - $oComment = $oCommentModel->getComment($vars->parent_srl); - $member_srl = $oComment->get('member_srl'); - $type = $this->_TYPE_COMMENT; - } + $config = NcenterliteModel::getConfig(); + $document_srl = Context::get('document_srl'); + $logged_info = Context::get('logged_info'); - if($logged_info->member_srl != $member_srl) - { - $args = new stdClass(); - $args->member_srl = abs($member_srl); - $args->srl = ($parent_type == 'DOCUMENT') ? $vars->document_srl : $vars->parent_srl; - $args->type = $type; - $args->target_type = $this->_TYPE_COMMENT; - $args->target_srl = $vars->parent_srl; - $args->target_p_srl = '1'; - $args->target_url = getNotEncodedUrl('', 'document_srl', $vars->document_srl, '_comment_srl', $vars->parent_srl) . '#comment_' . $vars->parent_srl; - $args->target_summary = cut_str(strip_tags($vars->content), 50); - $args->target_nick_name = $logged_info->nick_name; - $args->target_email_address = $logged_info->email_address; - $args->regdate = date('YmdHis'); - $args->notify = $this->_getNotifyId($args); - $output = $this->_insertNotify($args); - if(!$output->toBool()) - { - return $output; - } - } - } - else if($oModule->act == 'dispKinView' || $oModule->act == 'dispKinIndex') - { - // 글을 볼 때 알림 제거 - $oDocumentModel = getModel('document'); - $oDocument = $oDocumentModel->getDocument($vars->document_srl); - $member_srl = $oDocument->get('member_srl'); - - if($logged_info->member_srl == $member_srl) + if ($document_srl && $config->document_read == 'Y' && $logged_info && $logged_info->member_srl) { $args = new stdClass; $args->member_srl = $logged_info->member_srl; - $args->srl = $vars->document_srl; - $args->type = $this->_TYPE_DOCUMENT; - $output = executeQuery('ncenterlite.updateNotifyReadedBySrl', $args); - if($output->toBool()) + $args->target_srl = array_values(array_map(function($comment) { + return $comment->comment_srl; + }, $comment_list)); + + $output = executeQuery('ncenterlite.updateNotifyReadedByTargetSrl', $args); + if ($output->toBool() && DB::getInstance()->getAffectedRows()) { - //Remove flag files $this->removeFlagFile($args->member_srl); } } } - else if($oModule->act == 'getKinComments') - { - // 의견을 펼칠 때 알림 제거 - $args = new stdClass; - $args->member_srl = $logged_info->member_srl; - $args->target_srl = $vars->parent_srl; - $output = executeQuery('ncenterlite.updateNotifyReadedByTargetSrl', $args); - if($output->toBool()) - { - //Remove flag files - $this->removeFlagFile($args->member_srl); - } - } } function triggerBeforeDisplay(&$output_display) { - // 팝업창이면 중지 + // Don't show notification panel in popups, iframes, admin dashboard, etc. if(Context::get('ncenterlite_is_popup')) { return; } - - // 자신의 알림목록을 보고 있을 경우엔 알림센터창을 띄우지 않는다. - if(Context::get('act') == 'dispNcenterliteNotifyList') - { - return; - } - if(Context::isLocked()) { return; } - - // HTML 모드가 아니면 중지 + admin 모듈이면 중지 + if(isset(self::$_skip_acts[strval(Context::get('act'))])) + { + return; + } if(Context::getResponseMethod() != 'HTML' || Context::get('module') == 'admin') { return; } - - // 로그인 상태가 아니면 중지 if(!Context::get('is_logged')) { return; } - $module_info = Context::get('module_info'); - $oNcenterliteModel = getModel('ncenterlite'); - $config = $oNcenterliteModel->getConfig(); + $config = NcenterliteModel::getConfig(); // if the array is empty, dose not output the notification. if(empty($config->use)) @@ -808,18 +1110,29 @@ class ncenterliteController extends ncenterlite } // 노티바 제외 페이지이면 중지 - if(is_array($config->hide_module_srls) && in_array($module_info->module_srl, $config->hide_module_srls)) + $module_info = Context::get('module_info'); + if(is_array($config->hide_module_srls) && in_array($module_info->module_srl ?? 0, $config->hide_module_srls)) + { + return; + } + + // 레이아웃에서 알림센터 사용중이라면 중지 + $layout_info = Context::get('layout_info'); + if($layout_info && isset($layout_info->use_ncenter_widget) && $layout_info->use_ncenter_widget == 'Y') { return; } Context::set('ncenterlite_config', $config); - - Context::loadFile(array('./modules/ncenterlite/tpl/js/ncenterlite.js', 'body', '', 100000)); + + if($config->highlight_effect === 'Y') + { + Context::loadFile(array('./modules/ncenterlite/tpl/js/ncenterlite.js', 'body', '', 100000)); + } $logged_info = Context::get('logged_info'); $_output = $oNcenterliteModel->getMyNotifyList($logged_info->member_srl); - + if($config->always_display !== 'Y') { if(!$_output->data) @@ -829,14 +1142,17 @@ class ncenterliteController extends ncenterlite } $_latest_notify_id = array_slice($_output->data, 0, 1); - $_latest_notify_id = $_latest_notify_id[0]->notify; + $_latest_notify_id = count($_latest_notify_id) > 0 ? $_latest_notify_id[0]->notify : ""; Context::set('ncenterlite_latest_notify_id', $_latest_notify_id); - if($_COOKIE['_ncenterlite_hide_id'] && $_COOKIE['_ncenterlite_hide_id'] == $_latest_notify_id) + if(!empty($_COOKIE['_ncenterlite_hide_id']) && $_COOKIE['_ncenterlite_hide_id'] == $_latest_notify_id) { return; } - setcookie('_ncenterlite_hide_id', '', 0, '/'); + if(!empty($_COOKIE['_ncenterlite_hide_id'])) + { + Rhymix\Framework\Cookie::remove('_ncenterlite_hide_id'); + } $oMemberModel = getModel('member'); $memberConfig = $oMemberModel->getMemberConfig(); @@ -854,11 +1170,21 @@ class ncenterliteController extends ncenterlite if(Mobile::isFromMobilePhone()) { $this->template_path = sprintf('%sm.skins/%s/', $this->module_path, $config->mskin); - if(!is_dir($this->template_path) || !$config->mskin) + if(!$config->mskin) { $config->mskin = 'default'; $this->template_path = sprintf('%sm.skins/%s/', $this->module_path, $config->mskin); } + // If use to same PC skin set. + else if ($config->mskin === '/USE_RESPONSIVE/') + { + $this->template_path = sprintf('%sskins/%s/', $this->module_path, $config->skin); + if(!$config->skin) + { + $config->skin = 'default'; + $this->template_path = sprintf('%sskins/%s/', $this->module_path, $config->skin); + } + } } else { @@ -870,39 +1196,34 @@ class ncenterliteController extends ncenterlite } } + if($config->zindex) + { + Context::set('ncenterlite_zindex', ' style="z-index:' . $config->zindex . ';" '); + } + + $result = TemplateHandler::getInstance()->compile($this->template_path, 'ncenterlite.html'); $this->_addFile(); - $html = $this->_getTemplate(); - $output_display = $html . $output_display; + $output_display = $result . $output_display; } function triggerAddMemberMenu() { - $oNcenterliteModel = getModel('ncenterlite'); $oMemberController = getController('member'); - $config = $oNcenterliteModel->getConfig(); + $config = NcenterliteModel::getConfig(); - if($config->user_config_list == 'Y') + if($config->user_config_list == 'Y' && Context::get('is_logged')) { - $logged_info = Context::get('logged_info'); - if(!Context::get('is_logged')) - { - return; - } - $target_srl = Context::get('target_srl'); - $oMemberController->addMemberMenu('dispNcenterliteNotifyList', 'ncenterlite_my_list'); - } - if($config->user_notify_setting == 'Y') - { - $oMemberController->addMemberMenu('dispNcenterliteUserConfig', 'ncenterlite_my_settings'); - - if($logged_info->is_admin == 'Y') + if($config->user_notify_setting == 'Y') { - $url = getUrl('', 'act', 'dispNcenterliteUserConfig', 'member_srl', $target_srl); - $str = Context::getLang('ncenterlite_user_settings'); - $oMemberController->addMemberPopupMenu($url, $str, ''); + $oMemberController->addMemberMenu('dispNcenterliteUserConfig', 'ncenterlite_my_settings'); + } + + if($config->unsubscribe == 'Y') + { + $oMemberController->addMemberMenu('dispNcenterliteUnsubscribeList', 'unsubscribe_list'); } } } @@ -914,16 +1235,9 @@ class ncenterliteController extends ncenterlite Context::loadFile(array($this->template_path . 'ncenterlite.css', '', '', 100)); } - $oNcenterliteModel = getModel('ncenterlite'); - $config = $oNcenterliteModel->getConfig(); - if(!Mobile::isFromMobilePhone()) - { - if($config->colorset && file_exists(FileHandler::getRealPath($this->template_path . 'ncenterlite.' . $config->colorset . '.css'))) - { - Context::loadFile(array($this->template_path . 'ncenterlite.' . $config->colorset . '.css', '', '', 100)); - } - } - elseif(Mobile::isFromMobilePhone()) + $config = NcenterliteModel::getConfig(); + + if(Mobile::isFromMobilePhone() && $config->mskin !== '/USE_RESPONSIVE/') { if($config->mcolorset && file_exists(FileHandler::getRealPath($this->template_path . 'ncenterlite.' . $config->mcolorset . '.css'))) { @@ -934,30 +1248,13 @@ class ncenterliteController extends ncenterlite Context::loadFile(array('./common/js/xe.min.js', 'head', '', -100000)); Context::loadFile(array($this->template_path . 'ncenterlite.mobile.css', '', '', 100)); } - if($config->zindex) - { - Context::set('ncenterlite_zindex', ' style="z-index:' . $config->zindex . ';" '); - } - } - - function _getTemplate() - { - $oNcenterModel = getModel('ncenterlite'); - $config = $oNcenterModel->getConfig(); - - $oTemplateHandler = TemplateHandler::getInstance(); - - if(Mobile::isFromMobilePhone()) - { - $path = sprintf('%sm.skins/%s/', $this->module_path, $config->mskin); - } else { - $path = sprintf('%sskins/%s/', $this->module_path, $config->skin); + if($config->colorset && file_exists(FileHandler::getRealPath($this->template_path . 'ncenterlite.' . $config->colorset . '.css'))) + { + Context::loadFile(array($this->template_path . 'ncenterlite.' . $config->colorset . '.css', '', '', 100)); + } } - $result = $oTemplateHandler->compile($path, 'ncenterlite.html'); - - return $result; } function updateNotifyRead($notify, $member_srl) @@ -1051,6 +1348,10 @@ class ncenterliteController extends ncenterlite } $module_info = Context::get('module_info'); + if (!$module_info || !$module_info->module) + { + return false; + } // DX 익명 체크박스 if($module_info->module == 'beluxe' && $triggerObj->anonymous == 'Y') @@ -1081,20 +1382,27 @@ class ncenterliteController extends ncenterlite function _insertNotify($args, $anonymous = FALSE) { + $config = NcenterliteModel::getConfig(); + + if(is_array($config->hide_module_srls) && in_array($args->module_srl, $config->hide_module_srls)) + { + return new BaseObject(); + } + // 비회원 노티 제거 - if($args->member_srl <= 0) + if(!isset($args->member_srl) || $args->member_srl <= 0) { return new BaseObject(); } // 노티 ID가 없는 경우 자동 생성 - if (!$args->notify) + if (empty($args->notify)) { $args->notify = $this->_getNotifyId($args); } - + // 날짜가 없는 경우 자동 생성 - if (!$args->regdate) + if (empty($args->regdate)) { $args->regdate = date('YmdHis'); } @@ -1102,15 +1410,15 @@ class ncenterliteController extends ncenterlite // 익명인 경우 발신자 정보를 제거 if($anonymous == TRUE) { - $args->target_member_srl = 0; + $args->target_member_srl = -1 * $this->user->member_srl; $args->target_nick_name = strval($args->target_nick_name); - $args->target_user_id = strval($args->target_nick_name); - $args->target_email_address = strval($args->target_nick_name); + $args->target_user_id = $args->target_nick_name; + $args->target_email_address = $args->target_nick_name; } // 발신자 회원번호(target_member_srl)가 지정된 경우 그대로 사용 elseif($args->target_member_srl) { - $member_info = getModel('member')->getMemberInfoByMemberSrl($args->target_member_srl); + $member_info = getModel('member')->getMemberInfoByMemberSrl(abs($args->target_member_srl)); $args->target_member_srl = intval($member_info->member_srl); $args->target_nick_name = strval($member_info->nick_name); $args->target_user_id = strval($member_info->user_id); @@ -1134,17 +1442,36 @@ class ncenterliteController extends ncenterlite $args->target_email_address = ''; } + // 수신자가 웹 알림을 거부한 경우 이미 읽은 것으로 처리 + if($config->user_notify_setting == 'Y') + { + $target_member_config = NcenterliteModel::getUserConfig($args->member_srl); + if($target_member_config && isset($target_member_config->{$args->config_type}) && !in_array('web', $target_member_config->{$args->config_type})) + { + $args->readed = 'Y'; + } + } + $trigger_output = ModuleHandler::triggerCall('ncenterlite._insertNotify', 'before', $args); if(!$trigger_output->toBool() || $trigger_output->getMessage() === 'cancel') { return $trigger_output; } - + + if (isset($args->extra_data) && $args->extra_data) + { + $args->data = serialize($args->extra_data); + } + $output = executeQuery('ncenterlite.insertNotify', $args); if($output->toBool()) { ModuleHandler::triggerCall('ncenterlite._insertNotify', 'after', $args); - $this->sendSmsMessage($args); + $push_success = $this->sendPushMessage($args); + if (!$push_success || !isset($config->push_before_sms) || $config->push_before_sms !== 'Y') + { + $this->sendSmsMessage($args); + } $this->sendMailMessage($args); $this->removeFlagFile($args->member_srl); } @@ -1161,7 +1488,7 @@ class ncenterliteController extends ncenterlite $cache_key = sprintf('ncenterlite:notify_list:%d', $member_srl); Rhymix\Framework\Cache::set($cache_key, $output); - + $flag_path = \RX_BASEDIR . 'files/cache/ncenterlite/new_notify/' . getNumberingPath($member_srl) . $member_srl . '.php'; if (Rhymix\Framework\Cache::getDriverName() !== 'dummy') { @@ -1184,7 +1511,7 @@ class ncenterliteController extends ncenterlite $cache_key = sprintf('ncenterlite:notify_list:%d', $member_srl); Rhymix\Framework\Cache::delete($cache_key); - + $flag_path = \RX_BASEDIR . 'files/cache/ncenterlite/new_notify/' . getNumberingPath($member_srl) . $member_srl . '.php'; if(file_exists($flag_path)) { @@ -1206,17 +1533,16 @@ class ncenterliteController extends ncenterlite **/ function _getMentionTarget($content) { - $oNcenterliteModel = getModel('ncenterlite'); $oMemberModel = getModel('member'); - $config = $oNcenterliteModel->getConfig(); + $config = NcenterliteModel::getConfig(); $logged_info = Context::get('logged_info'); - + // Extract mentions. $content = html_entity_decode(strip_tags($content)); preg_match_all('/(?:^|\s)@([^\pC\pM\pP\pS\pZ]+)/u', $content, $matches); $mentions = array_unique($matches[1]); $members = array(); - + // Find members. foreach ($mentions as $mention) { @@ -1224,12 +1550,12 @@ class ncenterliteController extends ncenterlite { continue; } - + if (count($members) >= $config->mention_limit) { break; } - + if ($config->mention_suffix_always_cut != 'Y') { if ($config->mention_names === 'id') @@ -1245,7 +1571,7 @@ class ncenterliteController extends ncenterlite { $member_srl = null; } - + if (!$member_srl) { foreach ($config->mention_suffixes as $suffix) @@ -1255,7 +1581,7 @@ class ncenterliteController extends ncenterlite $mention = substr($mention, 0, $pos); } } - + if (isset($members[$mention])) { continue; @@ -1273,31 +1599,98 @@ class ncenterliteController extends ncenterlite { continue; } - + $members[$mention] = $member_srl; } - + return array_values($members); } + function sendPushMessage($args) + { + $config = NcenterliteModel::getConfig(); + if(!isset($config->use[$args->config_type]['push'])) + { + return false; + } + if($config->user_notify_setting == 'Y') + { + $target_member_config = NcenterliteModel::getUserConfig($args->member_srl); + if($target_member_config && isset($target_member_config->{$args->config_type}) && !in_array('push', $target_member_config->{$args->config_type})) + { + return; + } + } + if($this->user->member_srl == $args->member_srl && $args->target_type != $this->_TYPE_CUSTOM) + { + return false; + } + + $oNcenterliteModel = getModel('ncenterlite'); + $content = $oNcenterliteModel->getNotificationText($args); + $content = htmlspecialchars_decode(preg_replace('/<\/?(strong|)[^>]*>/', '', $content)); + + $target_url = strval($args->target_url); + if (!preg_match('!^https?://!', $target_url)) + { + $target_url = Rhymix\Framework\URL::getCurrentDomainUrl($target_url); + } + + if (!isset($args->extra_data) || !$args->extra_data) + { + $args->extra_data = []; + $args->extra_data['sender'] = strval($args->target_nick_name); + $args->extra_data['profile_image'] = ''; + if ($args->target_member_srl > 0) + { + $profile_image = MemberModel::getProfileImage($args->target_member_srl); + if ($profile_image && $profile_image->src) + { + $args->extra_data['profile_image'] = Rhymix\Framework\URL::getCurrentDomainUrl($profile_image->src); + } + } + $args->extra_data['type'] = strval($args->target_type); + $args->extra_data['subject'] = strval($args->target_summary); + $args->extra_data['content'] = isset($args->target_content) ? mb_substr($args->target_content, 0, 200, 'UTF-8') : ''; + } + + $oPush = new \Rhymix\Framework\Push(); + if (!isset($config->fcm_push_format) || $config->fcm_push_format === 'notification') + { + $oPush->setSubject($content); + $oPush->setContent(strval($args->extra_content)); + } + $oPush->setData($args->extra_data); + $oPush->setURL($target_url); + $oPush->addTo(intval($args->member_srl)); + $output = $oPush->send(); + + return $output; + } + function sendSmsMessage($args) { - $oNcenterliteModel = getModel('ncenterlite'); - - $config = $oNcenterliteModel->getConfig(); + $config = NcenterliteModel::getConfig(); if(!isset($config->use[$args->config_type]['sms'])) { return false; } - - $logged_info = Context::get('logged_info'); - if($logged_info->member_srl == $args->member_srl) + if($config->user_notify_setting == 'Y') + { + $target_member_config = NcenterliteModel::getUserConfig($args->member_srl); + if($target_member_config && isset($target_member_config->{$args->config_type}) && !in_array('sms', $target_member_config->{$args->config_type})) + { + return; + } + } + if($this->user->member_srl == $args->member_srl && $args->target_type != $this->_TYPE_CUSTOM) { return false; } + $oNcenterliteModel = getModel('ncenterlite'); $content = $oNcenterliteModel->getNotificationText($args); - $content = preg_replace('/<\/?(strong|)[^>]*>/', '', $content); + $content = htmlspecialchars_decode(preg_replace('/<\/?(strong|)[^>]*>/', '', $content)); $sms = $this->getSmsHandler(); if($sms === false) @@ -1305,10 +1698,24 @@ class ncenterliteController extends ncenterlite return false; } - $member_info = getModel('member')->getMemberInfoByMemberSrl($args->member_srl); + $member_info = MemberModel::getMemberInfoByMemberSrl($args->member_srl); if($config->variable_name) { - $phone_number = $member_info->{$config->variable_name}[0].$member_info->{$config->variable_name}[1].$member_info->{$config->variable_name}[2]; + if($config->variable_name === '#') + { + $phone_country = $member_info->phone_country ?? ''; + $phone_number = $member_info->phone_number ?? ''; + + // Sending SMS outside of Korea is currently not supported. + if($phone_country !== 'KOR') + { + return false; + } + } + else + { + $phone_number = implode('', $member_info->{$config->variable_name} ?? []); + } // Check if a Korean phone number contains a valid area code and the correct number of digits. $phone_format = Rhymix\Framework\Korea::isValidPhoneNumber($phone_number); @@ -1331,51 +1738,50 @@ class ncenterliteController extends ncenterlite function sendMailMessage($args) { - $oNcenterliteModel = getModel('ncenterlite'); - $config = $oNcenterliteModel->getConfig(); + $config = NcenterliteModel::getConfig(); if(!isset($config->use[$args->config_type]['mail'])) { return false; } - - $logged_info = Context::get('logged_info'); - if($logged_info->member_srl == $args->member_srl) + if($config->user_notify_setting == 'Y') + { + $target_member_config = NcenterliteModel::getUserConfig($args->member_srl); + if($target_member_config && isset($target_member_config->{$args->config_type}) && !in_array('mail', $target_member_config->{$args->config_type})) + { + return; + } + } + if($this->user->member_srl == $args->member_srl && $args->target_type != $this->_TYPE_CUSTOM) { return false; } + + $oNcenterliteModel = getModel('ncenterlite'); $content = $oNcenterliteModel->getNotificationText($args); - switch ($args->config_type) + $mail_title = lang('ncenterlite_type_' . $args->config_type); + if ($mail_title === 'ncenterlite_type_' . $args->config_type) { - case 'admin_content': - $mail_title = Context::getSiteTitle() . ' ' . lang('ncenterlite_admin_content'); - break; - case 'comment_comment': - $mail_title = Context::getSiteTitle() . ' ' . lang('ncenterlite_comment_comment_noti'); - break; - case 'comment': - $mail_title = Context::getSiteTitle() . ' ' . lang('ncenterlite_comment_noti'); - break; - case 'message': - $mail_title = Context::getSiteTitle() . ' ' . lang('ncenterlite_message_noti'); - break; - case 'vote': - $mail_title = Context::getSiteTitle() . ' ' . lang('ncenterlite_vote_noti'); - break; - case 'mention': - $mail_title = Context::getSiteTitle() . ' ' . lang('ncenterlite_mention_noti'); - break; - default: - return false; + $mail_title = lang('ncenterlite_type_custom'); + } + $mail_title = Context::getSiteTitle() . ' - ' . $mail_title; + + $target_url = strval($args->target_url); + if (!preg_match('!^https?://!', $target_url)) + { + $target_url = Rhymix\Framework\URL::getCurrentDomainUrl($target_url); } - $content = $content . '
                ' . Context::getSiteTitle() . '
                ' . Rhymix\Framework\URL::getCurrentDomainUrl($args->target_url); - - $member_info = getModel('member')->getMemberInfoByMemberSrl($args->member_srl); + $mail_content = sprintf("

                %s

                \n

                %s

                \n", $content, $target_url); + $member_info = MemberModel::getMemberInfoByMemberSrl($args->member_srl); + if (!$member_info || !$member_info->email_address) + { + return false; + } $oMail = new \Rhymix\Framework\Mail(); $oMail->setSubject($mail_title); - $oMail->setBody($content); + $oMail->setBody($mail_content); $oMail->addTo($member_info->email_address, $member_info->nick_name); $oMail->send(); } @@ -1387,21 +1793,20 @@ class ncenterliteController extends ncenterlite * @param $module_info * @param $is_anonymous * @param string $type - * @return Object|Bool|array + * @return array */ function insertMentionByTargets($mention_targets, $obj, $module_info, $is_anonymous, $type = 'D') { - $oNcenterliteModel = getModel('ncenterlite'); - $config = $oNcenterliteModel->getConfig(); + $config = NcenterliteModel::getConfig(); if(!is_array($mention_targets)) { - return false; + return array(); } if(!$module_info) { - return false; + return array(); } $notify_member_srls = array(); @@ -1409,9 +1814,8 @@ class ncenterliteController extends ncenterlite { if($config->user_notify_setting == 'Y') { - $target_member_config = $oNcenterliteModel->getUserConfig($mention_member_srl); - $notify_member_config = $target_member_config->data; - if ($notify_member_config->mention_notify == 'N') + $target_member_config = NcenterliteModel::getUserConfig($mention_member_srl); + if ($target_member_config && !$target_member_config->mention) { continue; } @@ -1423,8 +1827,8 @@ class ncenterliteController extends ncenterlite $args->srl = $obj->document_srl; $args->target_p_srl = $obj->document_srl; $args->target_srl = $obj->document_srl; - $args->target_summary = cut_str(strip_tags($obj->title), 50); - $args->target_url = getNotEncodedUrl('', 'document_srl', $obj->document_srl); + $args->target_summary = self::_createSummary($obj->title); + $args->target_url = getNotEncodedUrl('', 'mid', $module_info->mid, 'document_srl', $obj->document_srl); } elseif ($type == $this->_TYPE_COMMENT) { @@ -1439,8 +1843,8 @@ class ncenterliteController extends ncenterlite $args->srl = $obj->document_srl; $args->target_p_srl = $obj->comment_srl; $args->target_srl = $obj->comment_srl; - $args->target_url = $args->target_url = getNotEncodedUrl('', 'document_srl', $obj->document_srl, '_comment_srl', $obj->comment_srl) . '#comment_' . $obj->comment_srl; - $args->target_summary = cut_str(trim(utf8_normalize_spaces(strip_tags($obj->content))), 50) ?: (strpos($obj->content, 'target_url = getNotEncodedUrl('', 'mid', $module_info->mid, 'document_srl', $obj->document_srl, 'comment_srl', $obj->comment_srl) . '#comment_' . $obj->comment_srl; + $args->target_summary = self::_createSummary($obj->content) ?: (strpos($obj->content, 'config_type = 'mention'; $args->member_srl = $mention_member_srl; @@ -1454,10 +1858,93 @@ class ncenterliteController extends ncenterlite $output = $this->_insertNotify($args, $is_anonymous); if(!$output->toBool()) { - return $output; + // 실패시 지금까지 성공한 데이터를 리턴 + return $notify_member_srls; } $notify_member_srls[] = $mention_member_srl; } return $notify_member_srls; } + + /** + * trigger for document.getDocumentMenu. Append to popup menu a button for dispNcenterliteInsertUnsubscribe() + * + * @param array &$menu_list + * + * @return object + **/ + function triggerGetDocumentMenu(&$menu_list) + { + if(!Rhymix\Framework\Session::isMember()) return; + + $document_srl = Context::get('target_srl'); + + $config = NcenterliteModel::getConfig(); + + if($config->unsubscribe !== 'Y') return; + + $oDocumentController = getController('document'); + $url = getUrl('','module','ncenterlite','act','dispNcenterliteInsertUnsubscribe', 'target_srl', $document_srl, 'unsubscribe_type', 'document'); + $oDocumentController->addDocumentPopupMenu($url,'ncenterlite_cmd_unsubscribe_settings','','popup'); + } + + /** + * trigger for comment.getCommentMenu. Append to popup menu a button for dispNcenterliteInsertUnsubscribe() + * + * @param array &$menu_list + * + * @return object + **/ + function triggerGetCommentMenu(&$menu_list) + { + if(!Rhymix\Framework\Session::isMember()) return; + + $comment_srl = Context::get('target_srl'); + + $config = NcenterliteModel::getConfig(); + + if($config->unsubscribe !== 'Y') return; + + $oCommentController = getController('comment'); + $url = getUrl('','module','ncenterlite','act','dispNcenterliteInsertUnsubscribe', 'target_srl', $comment_srl, 'unsubscribe_type', 'comment'); + $oCommentController->addCommentPopupMenu($url,'ncenterlite_cmd_unsubscribe_settings','','popup'); + } + + /** + * Cut a string to fit the notification summary column. + * + * @param string $str + * @return string + */ + protected static function _createSummary($str): string + { + $str = escape(utf8_normalize_spaces(trim(strip_tags($str))), false); + if (function_exists('mb_strimwidth')) + { + return mb_strimwidth($str, 0, 50, '...', 'UTF-8'); + } + else + { + return cut_str($str, 45); + } + } + + /** + * Cut a string to fit the notification content column. + * + * @param string $str + * @return string + */ + protected static function _createContent($str): string + { + $str = escape(utf8_normalize_spaces(trim(strip_tags($str))), false); + if (function_exists('mb_strimwidth')) + { + return mb_strimwidth($str, 0, 200, '...', 'UTF-8'); + } + else + { + return cut_str($str, 195); + } + } } diff --git a/modules/ncenterlite/ncenterlite.mobile.php b/modules/ncenterlite/ncenterlite.mobile.php index 6d0ac9c21..e0b841b65 100644 --- a/modules/ncenterlite/ncenterlite.mobile.php +++ b/modules/ncenterlite/ncenterlite.mobile.php @@ -1,38 +1,6 @@ getConfig(); - $mskin = $config->mskin; - if(!$mskin) - { - $template_path = sprintf('%sm.skins/%s/', $this->module_path, 'default'); - } - elseif($mskin === '/USE_RESPONSIVE/') - { - $template_path = sprintf("%sskins/%s/", $this->module_path, $config->skin); - if(!is_dir($template_path) || !$config->skin) - { - $template_path = sprintf("%sskins/%s/", $this->module_path, 'default'); - } - } - else - { - $template_path = sprintf('%sm.skins/%s', $this->module_path, $mskin); - } - $this->setTemplatePath($template_path); - - $oLayoutModel = getModel('layout'); - $layout_info = $oLayoutModel->getLayout($config->mlayout_srl); - - if($layout_info) - { - $this->module_info->mlayout_srl = $config->mlayout_srl; - $this->setLayoutPath($layout_info->path); - } - } } diff --git a/modules/ncenterlite/ncenterlite.model.php b/modules/ncenterlite/ncenterlite.model.php index df3c06fc6..f27bc5769 100644 --- a/modules/ncenterlite/ncenterlite.model.php +++ b/modules/ncenterlite/ncenterlite.model.php @@ -1,83 +1,129 @@ getModuleConfig('ncenterlite'); - + if(!$config) { $config = new stdClass(); } - - if(!is_array($config->use)) + + $config->use = $config->use ?? array('message' => array('web' => 1)); + $config->display_use = $config->display_use ?? 'all'; + $config->always_display = $config->always_display ?? 'N'; + $config->user_config_list = $config->user_config_list ?? 'N'; + $config->user_notify_setting = $config->user_notify_setting ?? 'N'; + $config->document_read = $config->document_read ?? 'Y'; + $config->variable_name = $config->variable_name ?? 0; + $config->mention_names = $config->mention_names ?? 'nick_name'; + $config->mention_suffixes = $config->mention_suffixes ?? array('님', '様', 'さん', 'ちゃん'); + $config->mention_suffix_always_cut = $config->mention_suffix_always_cut ?? 'N'; + $config->mention_limit = $config->mention_limit ?? 20; + $config->anonymous_voter = $config->anonymous_voter ?? 'N'; + $config->anonymous_scrap = $config->anonymous_scrap ?? 'N'; + $config->highlight_effect = $config->highlight_effect ?? 'Y'; + $config->unsubscribe = $config->unsubscribe ?? 'N'; + $config->comment_all = $config->comment_all ?? 'N'; + $config->comment_all_notify_module_srls = $config->comment_all_notify_module_srls ?? array(); + $config->hide_module_srls = $config->hide_module_srls ?? array(); + $config->admin_notify_module_srls = $config->admin_notify_module_srls ?? array(); + $config->layout_srl = $config->layout_srl ?? 0; + $config->mlayout_srl = $config->mlayout_srl ?? 0; + $config->skin = $config->skin ?? 'default'; + $config->colorset = $config->colorset ?? 'black'; + $config->mskin = $config->mskin ?? 'default'; + $config->mcolorset = $config->mcolorset ?? 'black'; + $config->zindex = $config->zindex ?? '9999'; + $config->notify_count = $config->notify_count ?? 5; + + if(!isset($config->hide_module_srls)) { - if($config->use == 'Y') + $config->hide_module_srls = array(); + } + elseif(!is_array($config->hide_module_srls)) + { + $config->hide_module_srls = explode('|@|', $config->hide_module_srls); + } + + // Convert old config format + if($config->use === 'Y') + { + $config->use = array(); + foreach (self::getNotifyTypes() as $type => $srl) { - $config->use = array( - 'mention' => array('web' => 1), - 'comment' => array('web' => 1), - 'comment_comment' => array('web' => 1), - 'vote' => array('web' => 1), - 'message' => array('web' => 1), - 'admin_content' => array('web' => 1), - ); - } - else - { - $config->use = array('message' => array('web' => 1)); + $config->use[$type] = array('web' => 1); } } - else + elseif(is_array($config->use) && !is_array(array_first($config->use))) { - if(count($config->use) && !is_array(array_first($config->use))) + foreach($config->use as $key => $value) { - foreach($config->use as $key => $value) - { - $config->use[$key] = array(); - $config->use[$key]['web'] = $value; - } - getController('module')->insertModuleConfig('ncenterlite', $config); + $config->use[$key] = array(); + $config->use[$key]['web'] = $value; } - } - - if(!$config->display_use) $config->display_use = 'all'; - if(!$config->mention_names) $config->mention_names = 'nick_name'; - if(!$config->mention_suffixes) - { - $config->mention_suffixes = array('님', '様', 'さん', 'ちゃん'); + getController('module')->insertModuleConfig('ncenterlite', $config); } unset($config->mention_format); - if(!isset($config->mention_limit)) - { - $config->mention_limit = 20; - } - if(!$config->hide_module_srls) $config->hide_module_srls = array(); - if(!is_array($config->hide_module_srls)) $config->hide_module_srls = explode('|@|', $config->hide_module_srls); - if(!$config->document_read) $config->document_read = 'Y'; - if(!$config->skin) $config->skin = 'default'; - if(!$config->colorset) $config->colorset = 'black'; - if(!$config->zindex) $config->zindex = '9999'; - if(!$config->user_notify_setting) - { - $config->user_notify_setting = 'N'; - } - if(!$config->anonymous_voter) - { - $config->anonymous_voter = 'N'; - } - self::$config = $config; + self::$_config = $config; } - return self::$config; + return self::$_config; + } + + public static function getNotifyTypes() + { + $default = array( + 'comment' => 0, + 'comment_comment' => 0, + 'mention' => 0, + 'vote' => 0, + 'scrap' => 0, + 'message' => 0, + ); + if (Context::get('logged_info')->is_admin === 'Y') + { + $default['admin_content'] = 0; + } + + $custom_types = Rhymix\Framework\Cache::get('ncenterlite:notify_types'); + if (!$custom_types) + { + $custom_types = executeQueryArray('ncenterlite.getNotifyType', [])->data; + Rhymix\Framework\Cache::set('ncenterlite:notify_types', $custom_types, 0, true); + } + foreach ($custom_types as $type) + { + if (!isset($default[$type->notify_type_id])) + { + $default[$type->notify_type_id] = $type->notify_type_srl; + } + } + + $default['custom'] = 0; + return $default; + } + + public static function getUserSetNotifyTypes() + { + return array( + 'comment' => 0, + 'comment_comment' => 0, + 'mention' => 0, + 'vote' => 0, + 'scrap' => 0, + 'message' => 0, + ); } function getNotifyTypebySrl($notify_srl) @@ -129,7 +175,9 @@ class ncenterliteModel extends ncenterlite function insertNotifyType($args) { - return executeQuery('ncenterlite.insertNotifyType',$args); + $output = executeQuery('ncenterlite.insertNotifyType', $args); + Rhymix\Framework\Cache::delete('ncenterlite:notify_types'); + return $output; } /** @@ -137,23 +185,63 @@ class ncenterliteModel extends ncenterlite * @param null $member_srl * @return object|bool */ - function getUserConfig($member_srl = null) + public static function getUserConfig($member_srl = null) { if(!$member_srl) { - if(!Context::get('is_logged')) + $logged_info = Context::get('logged_info'); + $member_srl = $logged_info->member_srl ?? 0; + if (!$member_srl) { return false; } - $logged_info = Context::get('logged_info'); - $member_srl = $logged_info->member_srl; + } + + $member_srl = intval($member_srl); + $config = self::$_user_config[$member_srl] ?? null; + if ($config !== null) + { + return $config; + } + + $config = Rhymix\Framework\Cache::get('ncenterlite:user_config:' . $member_srl); + if ($config !== null) + { + return $config; } $args = new stdClass(); $args->member_srl = $member_srl; $output = executeQuery('ncenterlite.getUserConfig', $args); + if (!$output->data) + { + $config = false; + } + else + { + $config = new stdClass; + foreach (self::getNotifyTypes() as $type => $srl) + { + $disabled_list = $output->data->{$type . '_notify'} ?? ''; + if ($disabled_list === 'N') + { + $config->{$type} = []; + } + elseif ($disabled_list === 'Y' || $disabled_list === '') + { + $config->{$type} = ['web', 'mail', 'sms', 'push']; + } + else + { + $disabled_list = array_map(function($str) { return substr($str, 1); }, explode(',', $disabled_list)); + $config->{$type} = array_diff(['web', 'mail', 'sms', 'push'], $disabled_list); + } + } + } - return $output; + self::$_user_config[$member_srl] = $config; + Rhymix\Framework\Cache::set('ncenterlite:user_config:' . $member_srl, $config); + return $config; } function getAllMemberConfig() @@ -163,7 +251,7 @@ class ncenterliteModel extends ncenterlite return $output; } - function getMyNotifyList($member_srl=null, $page=1, $readed='N') + function getMyNotifyList($member_srl = null, $page = 1, $readed = 'N', $disp = false) { if(!$member_srl && !Context::get('is_logged')) { @@ -175,35 +263,51 @@ class ncenterliteModel extends ncenterlite $member_srl = Context::get('logged_info')->member_srl; } - $act = Context::get('act'); - if($act=='dispNcenterliteNotifyList') + if($disp) { - $output = $this->getMyDispNotifyList($member_srl); + $output = $this->getMyDispNotifyList($member_srl, $readed); } else { $output = $this->_getMyNotifyList($member_srl, $page, $readed); } - + $config = $this->getConfig(); $oMemberModel = getModel('member'); $list = $output->data; - + foreach($list as $k => $v) { $v->text = $this->getNotificationText($v); $v->ago = $this->getAgo($v->regdate); $v->url = getUrl('','act','procNcenterliteRedirect', 'notify', $v->notify); - if($v->target_type === $this->_TYPE_VOTED && $config->anonymous_voter === 'Y') + if (isset($v->data)) { - $v->target_member_srl = $member_srl; + $v->data = is_string($v->data) ? unserialize($v->data) : $v->data; + } + else + { + $v->data = []; + } + + if($v->target_member_srl < 0) + { + $v->target_member_srl = 0; $v->target_nick_name = lang('anonymous'); $v->target_user_id = $v->target_email_address = 'anonymous'; } + + if(($v->target_type === $this->_TYPE_VOTED && $config->anonymous_voter === 'Y') || ($v->target_type === $this->_TYPE_SCRAPPED && $config->anonymous_scrap === 'Y')) + { + $v->target_member_srl = 0; + $v->target_nick_name = lang('anonymous'); + $v->target_user_id = $v->target_email_address = 'anonymous'; + } + if($v->target_member_srl) { $profileImage = $oMemberModel->getProfileImage($v->target_member_srl); - $v->profileImage = $profileImage->src; + $v->profileImage = $profileImage ? $profileImage->src : null; } $list[$k] = $v; } @@ -227,6 +331,9 @@ class ncenterliteModel extends ncenterlite $tmp = $this->getMyNotifyList($member_srl, $page); foreach($tmp->data as $key => $obj) { + unset($tmp->data[$key]->target_email_address); + unset($tmp->data[$key]->target_user_id); + unset($tmp->data[$key]->target_member_srl); $tmp->data[$key]->url = str_replace('&', '&', $obj->url); } @@ -237,7 +344,7 @@ class ncenterliteModel extends ncenterlite $this->add('useProfileImage', $memberConfig->profile_image); } - function _getMyNotifyList($member_srl=null, $page=1, $readed='N') + function _getMyNotifyList($member_srl = null, $page = 1, $readed = 'N') { $oNcenterliteController = getController('ncenterlite'); @@ -253,7 +360,7 @@ class ncenterliteModel extends ncenterlite $cache_key = sprintf('ncenterlite:notify_list:%d', $member_srl); - if ($page <= 1) + if ($page <= 1 && $readed == 'N') { $output = Rhymix\Framework\Cache::get($cache_key); if($output !== null) @@ -263,7 +370,7 @@ class ncenterliteModel extends ncenterlite } $flag_path = \RX_BASEDIR . 'files/cache/ncenterlite/new_notify/' . getNumberingPath($member_srl) . $member_srl . '.php'; - if(FileHandler::exists($flag_path) && $page <= 1) + if($page <= 1 && $readed == 'N' && FileHandler::exists($flag_path)) { $deleteFlagPath = \RX_BASEDIR . 'files/cache/ncenterlite/new_notify/delete_date.php'; @@ -290,11 +397,19 @@ class ncenterliteModel extends ncenterlite $args = new stdClass(); $args->member_srl = $member_srl; + $args->page = $page ? $page : 1; if ($readed) { $args->readed = $readed; } + + $notify_count = intval(self::getConfig()->notify_count); + if($notify_count) + { + $args->list_count = $notify_count; + } + $output = executeQueryArray('ncenterlite.getNotifyList', $args); if (!$output->data) { @@ -303,14 +418,14 @@ class ncenterliteModel extends ncenterlite if (Rhymix\Framework\Cache::getDriverName() !== 'dummy') { - if($page <= 1) + if($page <= 1 && $readed == 'N') { Rhymix\Framework\Cache::set($cache_key, $output); } } else { - if($page <= 1) + if($page <= 1 && $readed == 'N') { $oNcenterliteController->updateFlagFile($member_srl, $output); } @@ -319,7 +434,7 @@ class ncenterliteModel extends ncenterlite return $output; } - function getMyDispNotifyList($member_srl = null) + function getMyDispNotifyList($member_srl = null, $readed = 'N') { if(!$member_srl) { @@ -328,10 +443,14 @@ class ncenterliteModel extends ncenterlite } $args = new stdClass(); - $args->page = Context::get('page'); + $args->page = max(1, intval(Context::get('page'))); $args->list_count = '20'; $args->page_count = '10'; $args->member_srl = $member_srl; + if ($readed) + { + $args->readed = $readed; + } $output = executeQueryArray('ncenterlite.getDispNotifyList', $args); if(!$output->data) $output->data = array(); @@ -360,7 +479,7 @@ class ncenterliteModel extends ncenterlite { $member_srl[] = $member->member_srl; } - + return $member_srl; } @@ -386,10 +505,9 @@ class ncenterliteModel extends ncenterlite elseif (Rhymix\Framework\Cache::getDriverName() !== 'dummy') { $output = $this->_getMyNotifyList($member_srl); - Rhymix\Framework\Cache::set($cache_key, $output); return $output->total_count; } - + $args = new stdClass(); $args->member_srl = $member_srl; $output = executeQuery('ncenterlite.getNotifyNewCount', $args); @@ -400,24 +518,30 @@ class ncenterliteModel extends ncenterlite function getColorsetList() { - $oModuleModel = getModel('module'); $skin = Context::get('skin'); + $skin_info = ModuleModel::loadSkinInfo($this->module_path, $skin); - $skin_info = $oModuleModel->loadSkinInfo($this->module_path, $skin); - - for($i=0, $c=count($skin_info->colorset); $i<$c; $i++) + $colorset_list = []; + foreach ($skin_info->colorset ?? [] as $colorset) { - $colorset = sprintf('%s|@|%s', $skin_info->colorset[$i]->name, $skin_info->colorset[$i]->title); - $colorset_list[] = $colorset; + $colorset_list[] = sprintf('%s|@|%s', $colorset->name, $colorset->title); + } + + if (count($colorset_list)) + { + $colorsets = implode("\n", $colorset_list); + } + else + { + $colorsets = ''; } - if(count($colorset_list)) $colorsets = implode("\n", $colorset_list); $this->add('colorset_list', $colorsets); } - + /** * Get information about a single notification. - * + * * @param string $notify * @param int $member_srl * @return object|false @@ -437,10 +561,10 @@ class ncenterliteModel extends ncenterlite return false; } } - + /** * Return the notification text. - * + * * @param object $notification * @return string */ @@ -461,7 +585,7 @@ class ncenterliteModel extends ncenterlite // Message. case 'E': - $type = lang('ncenterlite_type_message'); + $type = lang('member_message'); break; // Test. @@ -493,15 +617,15 @@ class ncenterliteModel extends ncenterlite $notification->target_summary, // %6$s $notification->target_url, // %7$s )); - + // Other. case 'U': default: - return $this->getNotifyTypeString($notification->notify_type, unserialize($notification->target_body)) ?: $lang->ncenterlite; + return $this->getNotifyTypeString($notification->notify_type, unserialize($notification->target_body)) ?: lang('ncenterlite'); } - + $config = $this->getConfig(); - + // Get the notification text. switch ($notification->target_type) { @@ -549,7 +673,7 @@ class ncenterliteModel extends ncenterlite // Voted. case 'V': - if($config->anonymous_voter === 'Y') + if($config->anonymous_voter !== 'N' || $notification->target_member_srl == 0) { $str = sprintf(lang('ncenterlite_vote_anonymous'), $notification->target_summary, $type); } @@ -559,6 +683,18 @@ class ncenterliteModel extends ncenterlite } break; + // Scrapped. + case 'R': + if($config->anonymous_scrap !== 'N' || $notification->target_member_srl == 0) + { + $str = sprintf(lang('ncenterlite_scrap_anonymous'), $notification->target_summary, $type); + } + else + { + $str = sprintf(lang('ncenterlite_scrap'), $notification->target_nick_name, $notification->target_summary, $type); + } + break; + // Admin notification. case 'B': $str = sprintf(lang('ncenterlite_admin_content_message'), $notification->target_nick_name, $notification->target_browser, $notification->target_summary); @@ -567,7 +703,7 @@ class ncenterliteModel extends ncenterlite case 'I': $str = sprintf(lang('ncenterlite_insert_member_message'), $notification->target_nick_name); break; - + case 'G': $str = sprintf(lang('ncenterlite_commented'), $notification->target_nick_name, $type, $notification->target_summary); break; @@ -594,7 +730,7 @@ class ncenterliteModel extends ncenterlite $date = getdate(strtotime(zdate($datetime, 'Y-m-d H:i:s'))); - $current = getdate(); + $current = getdate(time() + zgap()); $p = array('year', 'mon', 'mday', 'hours', 'minutes', 'seconds'); $factor = array(0, 12, 30, 24, 60, 60); @@ -637,15 +773,16 @@ class ncenterliteModel extends ncenterlite return $output->data; } - function getNotifyMemberSrlByCommentSrl($comment_srl) + function getNotifyListByCommentSrl($document_srl, $comment_srl) { - if(!$comment_srl === null) + if($comment_srl === null) { return false; } $args = new stdClass(); - $args->srl = $comment_srl; - $output = executeQueryArray('ncenterlite.getNotifyMemberSrlByCommentSrl', $args); + $args->document_srl = $document_srl; + $args->comment_srl = $comment_srl; + $output = executeQueryArray('ncenterlite.getNotifyListByCommentSrl', $args); if(!$output->toBool()) { return $output; @@ -653,4 +790,56 @@ class ncenterliteModel extends ncenterlite return $output->data; } + + /** + * 알림에서 member_srl 만 정리해서 보내준다. + * @param int $srl + * @return array + */ + function getNotifyMemberSrlBySrl(int $srl) : array + { + if(!$srl) + { + return []; + } + $args = new stdClass(); + $args->srl = $srl; + $output = executeQueryArray('ncenterlite.getNotifyMemberSrlBySrl', $args); + if(!$output->toBool()) + { + return []; + } + + $member_srls = array(); + foreach($output->data ?? [] as $value) + { + $member_srls[] = $value->member_srl; + } + + return $member_srls; + } + + function getUserUnsubscribeConfigByUnsubscribeSrl($unsubscribe_srl = 0) + { + $args = new stdClass(); + $args->unsubscribe_srl = $unsubscribe_srl; + $output = executeQuery('ncenterlite.getUserUnsubscribeConfigByUnsubscribeSrl', $args); + + return $output->data; + } + + function getUserUnsubscribeConfigByTargetSrl($target_srl = 0, $member_srl = null) + { + if(!$member_srl) + { + $member_srl = $this->user->member_srl; + } + + $args = new stdClass(); + $args->target_srl = $target_srl; + $args->member_srl = $member_srl; + $output = executeQuery('ncenterlite.getUserUnsubscribeConfigByTargetSrl', $args); + + return $output->data; + } } diff --git a/modules/ncenterlite/ncenterlite.view.php b/modules/ncenterlite/ncenterlite.view.php index aca07ec24..2bd6dc32c 100644 --- a/modules/ncenterlite/ncenterlite.view.php +++ b/modules/ncenterlite/ncenterlite.view.php @@ -1,34 +1,26 @@ getConfig(); - $template_path = sprintf("%sskins/%s/",$this->module_path, $config->skin); - if(!is_dir($template_path)||!$config->skin) - { - $config->skin = 'default'; - $template_path = sprintf("%sskins/%s/",$this->module_path, $config->skin); - } - $this->setTemplatePath($template_path); - - $oLayoutModel = getModel('layout'); - $layout_info = $oLayoutModel->getLayout($config->layout_srl); - - if($layout_info) - { - $this->module_info->layout_srl = $config->layout_srl; - $this->setLayoutPath($layout_info->path); - } + $this->setLayoutAndTemplatePaths($this instanceof NcenterliteMobile ? 'M' : 'P', $config); } function dispNcenterliteNotifyList() { - $oNcenterliteModel = getModel('ncenterlite'); + // Check member mid + $oMemberView = MemberView::getInstance(); + if (!$oMemberView->checkMidAndRedirect()) + { + $this->setRedirectUrl($oMemberView->getRedirectUrl()); + return; + } - $output = $oNcenterliteModel->getMyNotifyList(); + $oNcenterliteModel = ncenterliteModel::getInstance(); + $output = $oNcenterliteModel->getMyNotifyList($this->user->member_srl, 1, (Context::get('readed') == 'All' ? null : 'N'), true); Context::set('total_count', $output->page_navigation->total_count); Context::set('total_page', $output->page_navigation->total_page); @@ -36,39 +28,222 @@ class ncenterliteView extends ncenterlite Context::set('ncenterlite_list', $output->data); Context::set('page_navigation', $output->page_navigation); - $this->setTemplateFile('NotifyList'); + MemberView::setMemberPageBrowserTitle(lang('ncenterlite_my_list')); + $this->setTemplateFileOrDefault('NotifyList'); } function dispNcenterliteUserConfig() { - $oNcenterliteModel = getModel('ncenterlite'); + // Check member mid + $oMemberView = MemberView::getInstance(); + if (!$oMemberView->checkMidAndRedirect()) + { + $this->setRedirectUrl($oMemberView->getRedirectUrl()); + return; + } + + $oNcenterliteModel = ncenterliteModel::getInstance(); $config = $oNcenterliteModel->getConfig(); if($config->user_notify_setting != 'Y') { throw new Rhymix\Framework\Exception('msg_not_use_user_setting'); } - $oMemberModel = getModel('member'); - $member_srl = Context::get('member_srl'); - $logged_info = Context::get('logged_info'); - if(!Context::get('is_logged')) throw new Rhymix\Framework\Exception('ncenterlite_stop_login_required'); - - if($logged_info->is_admin == 'Y' && $member_srl) + if(!Rhymix\Framework\Session::getMemberSrl()) { - $member_info = $oMemberModel->getMemberInfoByMemberSrl($member_srl); + throw new Rhymix\Framework\Exceptions\MustLogin; } - if($logged_info->is_admin != 'Y' && $member_srl) + // Disable modifying other user's config #1925 #2148 + $member_srl = intval(Context::get('member_srl')) ?: $this->user->member_srl; + if ($this->user->member_srl !== $member_srl) { - if($member_srl != $logged_info->member_srl) + throw new Rhymix\Framework\Exceptions\NotPermitted('ncenterlite_stop_no_permission_other_user'); + } + + $user_selected = []; + $user_config = NcenterliteModel::getUserConfig($member_srl) ?: new stdClass; + $notify_types = NcenterliteModel::getUserSetNotifyTypes(); + foreach($notify_types as $notify_type => $notify_srl) + { + $user_config->{$notify_type . '_notify'} = (isset($user_config->{$notify_type}) && $user_config->{$notify_type}) ? 'Y' : 'N'; + $user_selected[$notify_type] = []; + foreach (['web', 'mail', 'sms', 'push'] as $item) { - throw new Rhymix\Framework\Exception('ncenterlite_stop_no_permission_other_user'); + $available = isset($config->use[$notify_type][$item]) && $config->use[$notify_type][$item] !== 'N'; + $selected = !is_array($user_config->{$notify_type} ?? null) || in_array($item, $user_config->{$notify_type}); + $user_selected[$notify_type][$item] = new stdClass(); + $user_selected[$notify_type][$item]->available = $available; + $user_selected[$notify_type][$item]->selected = $selected; } } - $output = $oNcenterliteModel->getUserConfig($member_srl); - Context::set('member_info', $member_info); - Context::set('user_config', $output->data); - $this->setTemplateFile('userconfig'); + Context::set('member_info', MemberModel::getMemberInfoByMemberSrl($member_srl)); + Context::set('notify_types', $notify_types); + Context::set('user_config', $user_config); + Context::set('user_selected', $user_selected); + Context::set('module_config', NcenterliteModel::getConfig()); + Context::set('sms_available', Rhymix\Framework\SMS::getDefaultDriver()->getName() !== 'Dummy'); + Context::set('push_available', count(Rhymix\Framework\Config::get('push.types') ?? []) > 0); + + MemberView::setMemberPageBrowserTitle(lang('ncenterlite_my_settings')); + $this->setTemplateFileOrDefault('userconfig'); + } + + /** + * Get to unsubscribe list. + * @throws \Rhymix\Framework\Exception + */ + function dispNcenterliteUnsubscribeList() + { + // Check member mid + $oMemberView = MemberView::getInstance(); + if (!$oMemberView->checkMidAndRedirect()) + { + $this->setRedirectUrl($oMemberView->getRedirectUrl()); + return; + } + + $oNcenterliteModel = ncenterliteModel::getInstance(); + $config = $oNcenterliteModel->getConfig(); + + if($config->unsubscribe !== 'Y') + { + throw new Rhymix\Framework\Exception('msg_unsubscribe_block_not_support'); + } + + if(!Rhymix\Framework\Session::getMemberSrl()) + { + throw new Rhymix\Framework\Exceptions\MustLogin; + } + + $member_srl = Context::get('member_srl'); + if(!$member_srl) + { + $member_srl = $this->user->member_srl; + } + if(!$this->user->isAdmin() && intval($this->user->member_srl) !== intval($member_srl)) + { + throw new Rhymix\Framework\Exceptions\NotPermitted('msg_unsubscribe_not_permission'); + } + + $args = new stdClass(); + $args->page = Context::get('page'); + $args->list_count = '20'; + $args->page_count = '10'; + $args->member_srl = $member_srl; + $output = executeQuery('ncenterlite.getUnsubscribeList', $args); + + Context::set('total_count', $output->page_navigation->total_count); + Context::set('total_page', $output->page_navigation->total_page); + Context::set('page', $output->page); + Context::set('unsubscribe_list', $output->data); + Context::set('page_navigation', $output->page_navigation); + + MemberView::setMemberPageBrowserTitle(lang('unsubscribe_list')); + $this->setTemplateFileOrDefault('unsubscribeList'); + } + + function dispNcenterliteInsertUnsubscribe() + { + $this->setLayoutPath('./common/tpl'); + $this->setLayoutFile('popup_layout'); + + // Check member mid + $oMemberView = MemberView::getInstance(); + if (!$oMemberView->checkMidAndRedirect()) + { + $this->setRedirectUrl($oMemberView->getRedirectUrl()); + return; + } + + $oNcenterliteModel = ncenterliteModel::getInstance(); + $target_srl = Context::get('target_srl'); + $unsubscribe_srl = Context::get('unsubscribe_srl'); + $unsubscribe_type = Context::get('unsubscribe_type'); + + $member_srl = Context::get('member_srl'); + + if(!$member_srl) + { + $member_srl = $this->user->member_srl; + } + + if($this->user->is_admin !== 'Y' && intval($member_srl) !== intval($this->user->member_srl)) + { + throw new \Rhymix\Framework\Exception('msg_invalid_request'); + } + + if($unsubscribe_srl) + { + $output = $oNcenterliteModel->getUserUnsubscribeConfigByUnsubscribeSrl($unsubscribe_srl); + } + else + { + $output = $oNcenterliteModel->getUserUnsubscribeConfigByTargetSrl($target_srl, $member_srl); + } + + if((!$target_srl || !$unsubscribe_type) && empty($output)) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + + if($unsubscribe_type == 'document') + { + $text = getModel('document')->getDocument($target_srl)->getTitleText(); + $type = lang('document'); + if(!$text) + { + $text = getModel('comment')->getComment($target_srl)->getContentPlainText(); + if(!$text) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + else + { + Context::set('unsubscribe_type', 'comment'); + $type = lang('comment'); + } + } + + } + else + { + $text = getModel('comment')->getComment($target_srl)->getContentPlainText(); + $type = lang('comment'); + if(!$text) + { + $text = getModel('document')->getDocument($target_srl)->getTitleText(); + if(!$text) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + else + { + Context::set('unsubscribe_type', 'document'); + $type = lang('document'); + } + } + } + + Context::set('unsubscribeData', $output); + Context::set('text', $text); + Context::set('type', $type); + + MemberView::setMemberPageBrowserTitle(lang('unsubscribe_list')); + $this->setTemplateFileOrDefault('unsubscribe'); + } + + public function setTemplateFileOrDefault($filename) + { + $path = $this->getTemplatePath(); + if (!file_exists($path . $filename . '.html')) + { + if (!file_exists($path . $filename . '.blade.php')) + { + $this->setTemplatePath(dirname($path) . '/default/'); + } + } + $this->setTemplateFile($filename); } } diff --git a/modules/ncenterlite/queries/deleteNotifyByTargetType.xml b/modules/ncenterlite/queries/deleteNotifyByTargetType.xml index 911d9741a..65945fa71 100644 --- a/modules/ncenterlite/queries/deleteNotifyByTargetType.xml +++ b/modules/ncenterlite/queries/deleteNotifyByTargetType.xml @@ -4,9 +4,10 @@ - - - + + + + diff --git a/modules/ncenterlite/queries/deleteUnsubscribe.xml b/modules/ncenterlite/queries/deleteUnsubscribe.xml new file mode 100644 index 000000000..dcea30118 --- /dev/null +++ b/modules/ncenterlite/queries/deleteUnsubscribe.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/modules/ncenterlite/queries/getDispNotifyList.xml b/modules/ncenterlite/queries/getDispNotifyList.xml index e39b573b2..5dda5d113 100644 --- a/modules/ncenterlite/queries/getDispNotifyList.xml +++ b/modules/ncenterlite/queries/getDispNotifyList.xml @@ -9,6 +9,7 @@ + diff --git a/modules/ncenterlite/queries/getNotifyListByCommentSrl.xml b/modules/ncenterlite/queries/getNotifyListByCommentSrl.xml new file mode 100644 index 000000000..59b996272 --- /dev/null +++ b/modules/ncenterlite/queries/getNotifyListByCommentSrl.xml @@ -0,0 +1,12 @@ + + +
                + + + + + + + + + diff --git a/modules/ncenterlite/queries/getNotifyMemberSrlByCommentSrl.xml b/modules/ncenterlite/queries/getNotifyMemberSrlByCommentSrl.xml deleted file mode 100644 index 7e2727799..000000000 --- a/modules/ncenterlite/queries/getNotifyMemberSrlByCommentSrl.xml +++ /dev/null @@ -1,12 +0,0 @@ - - -
                - - - - - - - - - diff --git a/modules/ncenterlite/queries/getNotifyMemberSrlBySrl.xml b/modules/ncenterlite/queries/getNotifyMemberSrlBySrl.xml new file mode 100644 index 000000000..95662dea6 --- /dev/null +++ b/modules/ncenterlite/queries/getNotifyMemberSrlBySrl.xml @@ -0,0 +1,15 @@ + + +
                + + + + + + + + + + + + diff --git a/modules/ncenterlite/queries/getNotifyType.xml b/modules/ncenterlite/queries/getNotifyType.xml index 803ce2aba..cb19fc5ac 100644 --- a/modules/ncenterlite/queries/getNotifyType.xml +++ b/modules/ncenterlite/queries/getNotifyType.xml @@ -2,12 +2,13 @@
                - - - + + + + diff --git a/modules/ncenterlite/queries/getOtherCommentByMemberSrl.xml b/modules/ncenterlite/queries/getOtherCommentByMemberSrl.xml index d3e390126..ab672df45 100644 --- a/modules/ncenterlite/queries/getOtherCommentByMemberSrl.xml +++ b/modules/ncenterlite/queries/getOtherCommentByMemberSrl.xml @@ -1,15 +1,21 @@
                +
                + + + +
                - + + - - + + diff --git a/modules/ncenterlite/queries/getSocialxeCount.xml b/modules/ncenterlite/queries/getSocialxeCount.xml deleted file mode 100644 index 33b2c96ae..000000000 --- a/modules/ncenterlite/queries/getSocialxeCount.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/modules/ncenterlite/queries/getUnsubscribeList.xml b/modules/ncenterlite/queries/getUnsubscribeList.xml new file mode 100644 index 000000000..eef36c49f --- /dev/null +++ b/modules/ncenterlite/queries/getUnsubscribeList.xml @@ -0,0 +1,19 @@ + + +
                + + + + + + + + + + + + + + + + diff --git a/modules/ncenterlite/queries/getUserUnsubscribeConfigByTargetSrl.xml b/modules/ncenterlite/queries/getUserUnsubscribeConfigByTargetSrl.xml new file mode 100644 index 000000000..b5c5d4deb --- /dev/null +++ b/modules/ncenterlite/queries/getUserUnsubscribeConfigByTargetSrl.xml @@ -0,0 +1,14 @@ + + +
                + + + + + + + + + + + diff --git a/modules/ncenterlite/queries/getUserUnsubscribeConfigByUnsubscribeSrl.xml b/modules/ncenterlite/queries/getUserUnsubscribeConfigByUnsubscribeSrl.xml new file mode 100644 index 000000000..ecd3d1e7f --- /dev/null +++ b/modules/ncenterlite/queries/getUserUnsubscribeConfigByUnsubscribeSrl.xml @@ -0,0 +1,13 @@ + + +
                + + + + + + + + + + diff --git a/modules/ncenterlite/queries/insertNotify.xml b/modules/ncenterlite/queries/insertNotify.xml index 24e96cdd4..ba041339f 100644 --- a/modules/ncenterlite/queries/insertNotify.xml +++ b/modules/ncenterlite/queries/insertNotify.xml @@ -6,19 +6,21 @@ + + + + - - - - + + - - - + + + diff --git a/modules/ncenterlite/queries/insertUnsubscribe.xml b/modules/ncenterlite/queries/insertUnsubscribe.xml new file mode 100644 index 000000000..872aba7db --- /dev/null +++ b/modules/ncenterlite/queries/insertUnsubscribe.xml @@ -0,0 +1,12 @@ + + +
                + + + + + + + + + diff --git a/modules/ncenterlite/queries/insertUserConfig.xml b/modules/ncenterlite/queries/insertUserConfig.xml index c2bc581e3..e09cf6e03 100644 --- a/modules/ncenterlite/queries/insertUserConfig.xml +++ b/modules/ncenterlite/queries/insertUserConfig.xml @@ -5,7 +5,10 @@ + + + diff --git a/modules/ncenterlite/queries/updateNotifyReadedByTargetSrl.xml b/modules/ncenterlite/queries/updateNotifyReadedByTargetSrl.xml index 842363152..1390f93a8 100644 --- a/modules/ncenterlite/queries/updateNotifyReadedByTargetSrl.xml +++ b/modules/ncenterlite/queries/updateNotifyReadedByTargetSrl.xml @@ -7,6 +7,6 @@ - + diff --git a/modules/ncenterlite/queries/updateUserConfig.xml b/modules/ncenterlite/queries/updateUserConfig.xml index 7bfcb07d6..41ac087df 100644 --- a/modules/ncenterlite/queries/updateUserConfig.xml +++ b/modules/ncenterlite/queries/updateUserConfig.xml @@ -4,7 +4,10 @@ + + + diff --git a/modules/ncenterlite/schemas/ncenterlite_notify.xml b/modules/ncenterlite/schemas/ncenterlite_notify.xml index 6a0d9ef2b..dfd33de73 100644 --- a/modules/ncenterlite/schemas/ncenterlite_notify.xml +++ b/modules/ncenterlite/schemas/ncenterlite_notify.xml @@ -1,24 +1,22 @@
                - - - - - + + + + + - - - - - - - - - - + + + + + + + +
                diff --git a/modules/ncenterlite/schemas/ncenterlite_unsubscribe.xml b/modules/ncenterlite/schemas/ncenterlite_unsubscribe.xml new file mode 100644 index 000000000..fe1808c51 --- /dev/null +++ b/modules/ncenterlite/schemas/ncenterlite_unsubscribe.xml @@ -0,0 +1,7 @@ + + + + + + +
                diff --git a/modules/ncenterlite/schemas/ncenterlite_user_set.xml b/modules/ncenterlite/schemas/ncenterlite_user_set.xml index 6143f1693..d5d1569a3 100644 --- a/modules/ncenterlite/schemas/ncenterlite_user_set.xml +++ b/modules/ncenterlite/schemas/ncenterlite_user_set.xml @@ -1,7 +1,10 @@ - - - - + + + + + + +
                diff --git a/modules/ncenterlite/scripts/cleanNotifications.php b/modules/ncenterlite/scripts/cleanNotifications.php new file mode 100644 index 000000000..49253175a --- /dev/null +++ b/modules/ncenterlite/scripts/cleanNotifications.php @@ -0,0 +1,44 @@ + date('YmdHis', time() - ($days * 86400)), +]); + +if ($output->toBool()) +{ + echo "Successfully deleted all notifications older than $days days.\n"; + $delete_obj = (object)array('regdate' => time()); + Rhymix\Framework\Cache::clearGroup('ncenterlite'); + Rhymix\Framework\Storage::writePHPData(\RX_BASEDIR . 'files/cache/ncenterlite/new_notify/delete_date.php', $delete_obj); +} +else +{ + echo "Error while deleting notifications older than $days days.\n"; + echo $output->getMessage() . "\n"; + $exit_status = 11; +} + +// Set the exit status if there were any errors. +if ($exit_status != 0) +{ + exit($exit_status); +} diff --git a/modules/ncenterlite/skins/default/NotifyList.html b/modules/ncenterlite/skins/default/NotifyList.html index 6428b8878..8fc3e81c9 100644 --- a/modules/ncenterlite/skins/default/NotifyList.html +++ b/modules/ncenterlite/skins/default/NotifyList.html @@ -18,7 +18,7 @@ {$val->target_nick_name} {$member_info->nick_name} {$lang->ncenterlite_no_target} - {$val->text} + {$val->text} {$lang->ncenterlite_read_y} {$lang->ncenterlite_read_n} @@ -35,14 +35,14 @@ - \ No newline at end of file + diff --git a/modules/ncenterlite/skins/default/ncenterlite.css b/modules/ncenterlite/skins/default/ncenterlite.css index 60df8e548..f677d1c68 100644 --- a/modules/ncenterlite/skins/default/ncenterlite.css +++ b/modules/ncenterlite/skins/default/ncenterlite.css @@ -151,7 +151,7 @@ text-decoration:none; } -@media only screen and max-device-width 480px { +@media only screen and (max-device-width: 480px) { #nc_container { position:relative; height:auto; diff --git a/modules/ncenterlite/skins/default/unsubscribe.html b/modules/ncenterlite/skins/default/unsubscribe.html new file mode 100644 index 000000000..f1ee35cb8 --- /dev/null +++ b/modules/ncenterlite/skins/default/unsubscribe.html @@ -0,0 +1,62 @@ + +
                +
                + + + + + + + + + + + + + + +
                +

                {$lang->ncenterlite_cmd_unsubscribe_settings}

                +
                + +
                +
                +

                + {$type} +

                +

                + + {escape($unsubscribeData->text, false)} + + {escape($text, false)} + +

                +
                +
                + +
                + +
                +
                + + +

                {$lang->about_this_message_unsubscribe}

                +
                +
                +
                + + +
                +
                + diff --git a/modules/ncenterlite/skins/default/unsubscribeList.html b/modules/ncenterlite/skins/default/unsubscribeList.html new file mode 100644 index 000000000..65257455d --- /dev/null +++ b/modules/ncenterlite/skins/default/unsubscribeList.html @@ -0,0 +1,64 @@ + +
                +

                {$XE_VALIDATOR_MESSAGE}

                +
                + + + + + + + + + + + + + + + + + +
                {lang('ncenterlite_content_type')}{lang('content')}{lang('ncenterlite_notify_settings')}
                + + {lang('document')} + + {lang('comment')} + + + + {escape($val->text, false)} + + {escape($val->text, false)} + + +
                +
                + + + + + + + + + +
                +
                +
                + + + diff --git a/modules/ncenterlite/skins/default/userconfig.html b/modules/ncenterlite/skins/default/userconfig.html index fffbfeaeb..748c4c651 100644 --- a/modules/ncenterlite/skins/default/userconfig.html +++ b/modules/ncenterlite/skins/default/userconfig.html @@ -1,12 +1,10 @@ -

                {$XE_VALIDATOR_MESSAGE}

                -
                @@ -20,43 +18,22 @@

                {$lang->ncenterlite_userconfig_about} ({$lang->ncenterlite_userconfig_about_warning})

                +
                - -
                - - -

                {$lang->ncenterlite_comment_noti_about}

                + +
                + + + + + + + + +

                {sprintf($lang->get('ncenterlite_' . $notify_type . '_noti_about'), $logged_info->nick_name)}

                -
                - -
                - - -

                {$lang->ncenterlite_mention_noti_about}

                -
                -
                -
                - -
                - - -

                {$lang->ncenterlite_message_noti_about}

                -
                -
                - +
                @@ -64,5 +41,5 @@
                -
                - \ No newline at end of file + + diff --git a/modules/ncenterlite/skins/default_bottom/NotifyList.html b/modules/ncenterlite/skins/default_bottom/NotifyList.html index c3369bf08..7f1ffedd7 100644 --- a/modules/ncenterlite/skins/default_bottom/NotifyList.html +++ b/modules/ncenterlite/skins/default_bottom/NotifyList.html @@ -1,24 +1,37 @@ + +
                +
                + +
                +
                +
                - - - + + + + {@ $oMemberModel = getModel('member'); $member_info = $oMemberModel->getMemberInfoByMemberSrl($val->member_srl); } - - - + - + + - + - + - + + @@ -128,7 +135,7 @@ jQuery(function($){ - +
              • {$lang->last_page} »
              • diff --git a/modules/page/tpl/js/page_admin.js b/modules/page/tpl/js/page_admin.js index 218c14107..8960a9d2a 100644 --- a/modules/page/tpl/js/page_admin.js +++ b/modules/page/tpl/js/page_admin.js @@ -35,7 +35,7 @@ function completeArticleDocumentInserted(ret_obj){ alert(message); var url = ''; - + if(is_mobile == 'Y') url = current_url.setQuery('act', 'dispPageAdminMobileContent').setQuery('mid', mid); else @@ -103,7 +103,7 @@ function doRemoveWidgetCache(module_srl) { function completeRemoveWidgetCache(ret_obj) { var message = ret_obj['message']; - location.reload(); + location.reload(); } /* 일괄 설정 */ @@ -120,5 +120,28 @@ function doCartSetup(url) { } jQuery(function($){ - $('#pageBtnArea').delay(1000).show(1); + $('#pageBtnArea').delay(1000).removeAttr("hidden"); + $('#opage_proc_php').on('change', function() { + if (!$(this).prop('checked')) { + $('#opage_proc_tpl').prop('checked', false); + } + }); + $('#opage_proc_tpl').on('change', function() { + if ($(this).prop('checked')) { + $('#opage_proc_php').prop('checked', true); + } + }); + + $('#use_mobile_y,#use_mobile_n').on('change', function() { + if ($(this).is(':checked')) { + if ($(this).val() == 'Y') { + $('.hide-if-not-mobile-view').show(); + } else { + $('.hide-if-not-mobile-view').hide(); + } + } + }); + if ($('#use_mobile_n').is(':checked')) { + $('.hide-if-not-mobile-view').hide(); + } }); diff --git a/modules/page/tpl/mobile.html b/modules/page/tpl/mobile.html index ec955fb92..3bafab497 100644 --- a/modules/page/tpl/mobile.html +++ b/modules/page/tpl/mobile.html @@ -1,4 +1,3 @@ -
                {$page_content}
                diff --git a/modules/page/tpl/page_content_modify.html b/modules/page/tpl/page_content_modify.html index f9f99c332..8f22ac1be 100644 --- a/modules/page/tpl/page_content_modify.html +++ b/modules/page/tpl/page_content_modify.html @@ -43,7 +43,10 @@ diff --git a/modules/page/tpl/page_info.html b/modules/page/tpl/page_info.html index b37420632..cc704d07a 100644 --- a/modules/page/tpl/page_info.html +++ b/modules/page/tpl/page_info.html @@ -1,4 +1,5 @@ - + +

                {$XE_VALIDATOR_MESSAGE}

                @@ -15,8 +16,20 @@
                {$lang->page_type_name[$module_info->page_type]}
                - +
                + + {\RX_BASEURL}index.php?mid=

                {$lang->about_mid}

                @@ -37,16 +50,38 @@
                +
                + +
                + + +

                {$lang->msg_about_robots_tag}

                +
                +
                - +
                - + +
                +
                +
                + +
                + + +

                {$lang->about_mobile_view}

                @@ -59,16 +94,7 @@

                {$lang->about_layout}

                -
                - -
                - -
                -
                -
                +
                {$lang->unit_min} + {$lang->unit_min}

                {$lang->about_page_caching_interval}

                @@ -92,13 +118,27 @@

                {$lang->about_opage_path}{realpath("./")}

                -
                +

                {$lang->about_opage_mobile_path}{realpath("./")}

                +
                + +
                + + +

                {$lang->about_opage_postprocessing}

                +
                +
                @@ -108,7 +148,7 @@

                {$lang->about_skin}

                -
                +
                -

                {$lang->about_max_level}

                -
                -
                @@ -29,6 +22,13 @@

                {$lang->about_point_name}

                +
                + +
                + +

                {$lang->about_max_level}

                +
                +
                @@ -52,7 +52,7 @@
                {$lang->about_disable_read_document}   - + {$lang->disable_read_document_except_robots}
                @@ -65,42 +65,42 @@

                {$lang->give_point}

                {@ $config_array = get_object_vars($config)} - {@ $config_array['insert_comment_limit'] = $config_array['insert_comment_limit'] ?: $config_array['no_point_date']} + {@ $config_array['insert_comment_limit'] = $config_array['insert_comment_limit'] ?? $config_array['no_point_date']} {@ $action_types = array( - 'insert_document' => ['time_limit' => 0, 'except_notice' => 0], - 'insert_comment' => ['time_limit' => 1, 'except_notice' => 0], - 'upload_file' => ['time_limit' => 0, 'except_notice' => 0], - 'download_file' => ['time_limit' => 0, 'except_notice' => 0], - 'read_document' => ['time_limit' => 1, 'except_notice' => 1], - 'voter' => ['time_limit' => 1, 'except_notice' => 0], - 'blamer' => ['time_limit' => 1, 'except_notice' => 0], - 'voter_comment' => ['time_limit' => 1, 'except_notice' => 0], - 'blamer_comment' => ['time_limit' => 1, 'except_notice' => 0], - 'download_file_author' => ['time_limit' => 0, 'except_notice' => 0], - 'read_document_author' => ['time_limit' => 1, 'except_notice' => 1], - 'voted' => ['time_limit' => 1, 'except_notice' => 0], - 'blamed' => ['time_limit' => 1, 'except_notice' => 0], - 'voted_comment' => ['time_limit' => 1, 'except_notice' => 0], - 'blamed_comment' => ['time_limit' => 1, 'except_notice' => 0], + 'insert_document' => ['time_limit' => 0, 'except_notice' => 0, 'revert_on_delete' => 1], + 'insert_comment' => ['time_limit' => 1, 'except_notice' => 0, 'revert_on_delete' => 1], + 'upload_file' => ['time_limit' => 0, 'except_notice' => 0, 'revert_on_delete' => 1], + 'download_file' => ['time_limit' => 0, 'except_notice' => 0, 'revert_on_delete' => 0], + 'read_document' => ['time_limit' => 1, 'except_notice' => 1, 'revert_on_delete' => 0], + 'voter' => ['time_limit' => 1, 'except_notice' => 0, 'revert_on_delete' => 0], + 'blamer' => ['time_limit' => 1, 'except_notice' => 0, 'revert_on_delete' => 0], + 'voter_comment' => ['time_limit' => 1, 'except_notice' => 0, 'revert_on_delete' => 0], + 'blamer_comment' => ['time_limit' => 1, 'except_notice' => 0, 'revert_on_delete' => 0], + 'download_file_author' => ['time_limit' => 0, 'except_notice' => 0, 'revert_on_delete' => 0], + 'read_document_author' => ['time_limit' => 1, 'except_notice' => 1, 'revert_on_delete' => 0], + 'voted' => ['time_limit' => 1, 'except_notice' => 0, 'revert_on_delete' => 0], + 'blamed' => ['time_limit' => 1, 'except_notice' => 0, 'revert_on_delete' => 0], + 'voted_comment' => ['time_limit' => 1, 'except_notice' => 0, 'revert_on_delete' => 0], + 'blamed_comment' => ['time_limit' => 1, 'except_notice' => 0, 'revert_on_delete' => 0], )} - +
                {$lang->ncenterlite_sender}{$lang->ncenterlite_addressee}{$lang->ncenterlite_noti_contents}{$lang->ncenterlite_noti_contents} {$lang->ncenterlite_read} {$lang->date}
                읽지 않은 알림이 없습니다.
                {$val->target_nick_name}{$member_info->nick_name} {$lang->ncenterlite_no_target}{$val->text}{$val->text} {$lang->ncenterlite_read_y} {$lang->ncenterlite_read_n} @@ -38,11 +51,11 @@
              • « {$lang->first_page}
              • -
              • {$page_no}
              • +
              • {$page_no}
              • {$lang->last_page} »
              • - \ No newline at end of file + diff --git a/modules/ncenterlite/skins/default_bottom/ncenterlite.css b/modules/ncenterlite/skins/default_bottom/ncenterlite.css index d22532260..c1deb914d 100644 --- a/modules/ncenterlite/skins/default_bottom/ncenterlite.css +++ b/modules/ncenterlite/skins/default_bottom/ncenterlite.css @@ -143,7 +143,7 @@ text-decoration:none; } -@media only screen and max-device-width 480px { +@media only screen and (max-device-width: 480px) { #nc_container { position:relative; height:auto; diff --git a/modules/ncenterlite/skins/default_bottom/unsubscribe.html b/modules/ncenterlite/skins/default_bottom/unsubscribe.html new file mode 100644 index 000000000..d17c634da --- /dev/null +++ b/modules/ncenterlite/skins/default_bottom/unsubscribe.html @@ -0,0 +1,66 @@ + +
                + +
                +

                {$XE_VALIDATOR_MESSAGE}

                +
                +
                + + + + + + + + + + + + + + +
                +

                {$lang->ncenterlite_cmd_unsubscribe_settings}

                +
                + +
                +
                +

                + {$type} +

                +

                + + {escape($unsubscribeData->text, false)} + + {escape($text, false)} + +

                +
                +
                + +
                + +
                +
                + + +

                {$lang->about_this_message_unsubscribe}

                +
                +
                +
                + + +
                +
                + diff --git a/modules/ncenterlite/skins/default_bottom/unsubscribeList.html b/modules/ncenterlite/skins/default_bottom/unsubscribeList.html new file mode 100644 index 000000000..00dfa4ff8 --- /dev/null +++ b/modules/ncenterlite/skins/default_bottom/unsubscribeList.html @@ -0,0 +1,64 @@ + +
                +

                {$XE_VALIDATOR_MESSAGE}

                +
                + + + + + + + + + + + + + + + + + +
                {lang('ncenterlite_content_type')}{lang('content')}{lang('ncenterlite_notify_settings')}
                + + {lang('document')} + + {lang('comment')} + + + + {escape($val->text, false)} + + {escape($val->text, false)} + + +
                +
                + + + + + + + + + +
                +
                +
                + + + diff --git a/modules/ncenterlite/skins/default_bottom/userconfig.html b/modules/ncenterlite/skins/default_bottom/userconfig.html index 52e2e2f0b..748c4c651 100644 --- a/modules/ncenterlite/skins/default_bottom/userconfig.html +++ b/modules/ncenterlite/skins/default_bottom/userconfig.html @@ -1,16 +1,14 @@ -
                -
                +

                {$XE_VALIDATOR_MESSAGE}

                -
                - +

                {@$user_str = $member_info->nick_name} @@ -20,43 +18,22 @@

                {$lang->ncenterlite_userconfig_about} ({$lang->ncenterlite_userconfig_about_warning})

                +
                - -
                - - -

                {$lang->ncenterlite_comment_noti_about}

                + +
                + + + + + + + + +

                {sprintf($lang->get('ncenterlite_' . $notify_type . '_noti_about'), $logged_info->nick_name)}

                -
                - -
                - - -

                {$lang->ncenterlite_mention_noti_about}

                -
                -
                -
                - -
                - - -

                {$lang->ncenterlite_message_noti_about}

                -
                -
                - +
                @@ -64,5 +41,5 @@
                -
                - \ No newline at end of file + + diff --git a/modules/ncenterlite/tpl/advancedconfig.html b/modules/ncenterlite/tpl/advancedconfig.html index 761c7d864..b3826cb3d 100644 --- a/modules/ncenterlite/tpl/advancedconfig.html +++ b/modules/ncenterlite/tpl/advancedconfig.html @@ -9,8 +9,9 @@
                - + +

                {$lang->member_phone_variable_about}

                @@ -62,14 +63,62 @@

                {$lang->about_anonymous_voter}

                +
                + +
                + + +

                {$lang->about_anonymous_scrap}

                +
                +
                +
                + +
                + + +

                {$lang->about_highlight_effect}

                +
                +
                +
                + +
                + + +

                {$lang->about_unsubscribe}

                +
                +
                +
                + +
                + + +

                {$lang->about_fcm_push_format}

                +
                +
                diff --git a/modules/ncenterlite/tpl/config.html b/modules/ncenterlite/tpl/config.html index 1595f1d7c..4d9af3a31 100644 --- a/modules/ncenterlite/tpl/config.html +++ b/modules/ncenterlite/tpl/config.html @@ -9,51 +9,22 @@
                - + + + + + + +
                + + + + +
                + +
                - - - - -
                - -
                - - - - -
                - -
                - - - - -
                - -
                - - - - -
                - -
                - - - - -
                - -
                - - - - -

                -

                {$lang->ncenterlite_use_help}
                -
                {$lang->ncenterlite_warning} {$lang->ncenterlite_dont_use_push}
                -

                +

                {$lang->ncenterlite_use_help}

                @@ -104,6 +75,14 @@

                {$lang->about_user_notify_setting}

                +
                + +
                + + +

                {$lang->ncenterlite_push_before_sms_about}

                +
                +
                diff --git a/modules/ncenterlite/tpl/othercomment.html b/modules/ncenterlite/tpl/othercomment.html index 567748528..8f3b8d209 100644 --- a/modules/ncenterlite/tpl/othercomment.html +++ b/modules/ncenterlite/tpl/othercomment.html @@ -18,7 +18,7 @@ -

                {$lang->ncenterlite_use_help}

                +

                {$lang->ncenterlite_use_othercomment_help}

                diff --git a/modules/ncenterlite/tpl/skinsetting.html b/modules/ncenterlite/tpl/skinsetting.html index 8f8ad3115..f950b2aa8 100644 --- a/modules/ncenterlite/tpl/skinsetting.html +++ b/modules/ncenterlite/tpl/skinsetting.html @@ -79,6 +79,13 @@

                {$lang->ncenterlite_zindex_about}

                +
                + +
                + +

                {$lang->ncenterlite_notify_count_about}

                +
                +
                diff --git a/modules/page/conf/info.xml b/modules/page/conf/info.xml index dd57de107..14c99c226 100644 --- a/modules/page/conf/info.xml +++ b/modules/page/conf/info.xml @@ -18,8 +18,8 @@ Этот модуль служит для создания страниц, чтобы связать их с содержимым. 製作頁面並連結內容的模組。 Bu modül içeriklere bağlanacak sayfaları oluşturmak içindir. - 1.7 - 2013-11-27 + RX_VERSION + RX_CORE service diff --git a/modules/page/conf/module.xml b/modules/page/conf/module.xml index fa909551e..df91bd657 100644 --- a/modules/page/conf/module.xml +++ b/modules/page/conf/module.xml @@ -3,12 +3,15 @@ 페이지 수정 - page modify + Edit Page - - + + + + + @@ -19,7 +22,7 @@ - + diff --git a/modules/page/lang/en.php b/modules/page/lang/en.php index 3d44499a7..5a68c233f 100644 --- a/modules/page/lang/en.php +++ b/modules/page/lang/en.php @@ -15,10 +15,17 @@ $lang->page_type_name['ARTICLE'] = 'Article Page'; $lang->page_type_name['WIDGET'] = 'Widget Page'; $lang->page_type_name['OUTSIDE'] = 'External Page'; $lang->about_page_type = 'Select Page Type to build a page.
                1. Widget: Create multiple widgets.
                2. Article: Create articles with titles, contents and tags for posting page.
                3. External Page: Use external HTML or PHP files in Rhymix.
                '; -$lang->opage_path = 'Location of External Document'; +$lang->opage_path = 'Location of External Document for PC'; $lang->about_opage = 'This module enables to use external html or php files in Rhymix. It allows absolute or relative path, and if the url starts with \'http://\' , it can display the external page of the server.'; $lang->about_opage_path = 'Please enter the location of external document. Both absolute path such as \'/path1/path2/sample.php\' or relative path such as \'../path2/sample.php\' can be used. If you input the path like \'http://url/sample.php\', the result will be received and then displayed. This is current Rhymix\'s absolute path. '; -$lang->opage_mobile_path = 'Location of External Document for Mobile View'; +$lang->opage_mobile_path = 'Location of External Document for Mobile'; $lang->about_opage_mobile_path = 'Please enter the location of external document for mobile view. If not inputted, it uses the external document specified above. Both absolute path such as \'/path1/path2/sample.php\' or relative path such as \'../path2/sample.php\' can be used. If you input the path like \'http://url/sample.php\', the result will be received and then displayed. This is current Rhymix\'s absolute path. '; +$lang->opage_postprocessing = 'Postprocessing'; +$lang->about_opage_postprocessing = 'Parse and execute the external document as a PHP script and/or Rhymix template. This does not apply to URL paths.
                Beware that executing untrusted sources can lead to security problems.'; +$lang->opage_proc_php = 'Execute PHP code'; +$lang->opage_proc_tpl = 'Parse as Rhymix template'; $lang->page_management = 'Manage of page'; $lang->page_delete_warning = 'If you delete a page, the files of the page will be removed also.'; +$lang->msg_not_selected_page = 'Page not selected.'; +$lang->msg_invalid_opage_pc_path = 'Invalid path for the external document for PC.'; +$lang->msg_invalid_opage_mobile_path = 'Invalid path for the external document for Mobile.'; diff --git a/modules/page/lang/ko.php b/modules/page/lang/ko.php index 73738ca8d..785dc867b 100644 --- a/modules/page/lang/ko.php +++ b/modules/page/lang/ko.php @@ -15,11 +15,17 @@ $lang->page_type_name['ARTICLE'] = '문서 페이지'; $lang->page_type_name['WIDGET'] = '위젯 페이지'; $lang->page_type_name['OUTSIDE'] = '외부 페이지'; $lang->about_page_type = '페이지 타입을 선택하여 원하는 화면을 구성할 수 있습니다.
                1. 위젯형 : 여러가지 위젯들을 생성하여 화면을 구성합니다.
                2. 문서형 : 제목, 내용, 태그를 갖는 문서를 제작하여 포스팅 형식의 페이지를 작성합니다.
                3. 외부페이지형 : 외부 HTML 또는 PHP 파일을 Rhymix에서 사용할 수 있습니다.
                '; -$lang->opage_path = '외부 문서 위치'; -$lang->about_opage = 'Rhymix가 아닌 외부 HTML 또는 PHP 파일을 삽입할 수 있습니다. 절대경로, 상대경로를 이용할 수 있으며 http:// 로 시작할 경우 서버 외부의 페이지도 표시할 수 있습니다'; +$lang->opage_path = 'PC용 외부 문서 위치'; +$lang->about_opage = 'Rhymix가 아닌 외부 HTML 또는 PHP 파일을 삽입할 수 있습니다. 절대경로, 상대경로를 이용할 수 있으며 http:// 또는 https://로 시작할 경우 서버 외부의 페이지도 표시할 수 있습니다'; $lang->about_opage_path = '외부문서의 위치를 입력해주세요. /path1/path2/sample.php 와 같이 절대경로나 ../path2/sample.php와 같은 상대경로 모두 사용가능합니다. http://url/sample.php 와 같이 사용하면 해당 페이지를 웹으로 전송 받아 출력 하게 됩니다. 현재 Rhymix가 설치된 절대경로는 다음과 같습니다. '; $lang->opage_mobile_path = '모바일용 외부 문서 위치'; $lang->about_opage_mobile_path = '모바일용 외부문서의 위치를 입력해주세요. 입력하지 않으면 위에서 지정한 외부문서 위치의 페이지를 이용합니다. /path1/path2/sample.php 와 같이 절대경로나 ../path2/sample.php와 같은 상대경로 모두 사용가능합니다. http://url/sample.php 와 같이 사용하면 해당 페이지를 웹으로 전송 받아 출력 하게 됩니다. 현재 Rhymix가 설치된 절대경로는 다음과 같습니다. '; +$lang->opage_postprocessing = '외부 문서 후처리'; +$lang->about_opage_postprocessing = '외부 문서의 내용을 PHP 및 Rhymix 템플릿 문법에 따라 해석하도록 할 수 있습니다. http:// 또는 https://로 시작하는 외부 페이지에는 적용되지 않습니다.
                신뢰할 수 없는 문서를 실행할 경우 보안상 위협의 될 수 있으니 주의하십시오.'; +$lang->opage_proc_php = 'PHP 코드 실행'; +$lang->opage_proc_tpl = '템플릿 해석'; $lang->page_management = '페이지 관리'; $lang->page_delete_warning = '페이지를 삭제할 때 파일도 함께 삭제합니다'; $lang->msg_not_selected_page = '선택한 페이지가 없습니다.'; +$lang->msg_invalid_opage_pc_path = '사용할 수 없는 PC용 외부 문서 경로입니다.'; +$lang->msg_invalid_opage_mobile_path = '사용할 수 없는 모바일용 외부 문서 경로입니다.'; diff --git a/modules/page/m.skins/default/mobile.html b/modules/page/m.skins/default/mobile.html index 85984b8b6..1fbdf370c 100644 --- a/modules/page/m.skins/default/mobile.html +++ b/modules/page/m.skins/default/mobile.html @@ -1,2 +1,2 @@ -

                {$oDocument->getTitle()}

                -{$oDocument->getContent($module_info->display_popupmenu != 'hide')} +

                {$oDocument->getTitle()}

                +{$oDocument->getContent(false)} diff --git a/modules/page/m.skins/default/skin.xml b/modules/page/m.skins/default/skin.xml index f9dd04a0c..f52300a19 100644 --- a/modules/page/m.skins/default/skin.xml +++ b/modules/page/m.skins/default/skin.xml @@ -44,26 +44,5 @@ 非表示 - - 팝업메뉴 표시 - Display popup menu - ポップアップメニュー表示 - - 문서 하단의 팝업 메뉴를 표시 할지 선택합니다. - - - ドキュメント下段のポップアップメニューの表示可否を選択します。 - - - 출력 - Show - 表示 - - - 출력하지 않음 - Hide - 非表示 - - diff --git a/modules/page/page.admin.controller.php b/modules/page/page.admin.controller.php index 8835cf3b5..0c61117a9 100644 --- a/modules/page/page.admin.controller.php +++ b/modules/page/page.admin.controller.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * @brief page of the module admin controller class */ -class pageAdminController extends page +class PageAdminController extends Page { /** * @brief Initialization @@ -19,27 +19,45 @@ class pageAdminController extends page */ function procPageAdminInsert() { - // Create model/controller object of the module module - $oModuleController = getController('module'); - $oModuleModel = getModel('module'); // Set board module $args = Context::getRequestVars(); $args->module = 'page'; $args->mid = $args->page_name; //because if mid is empty in context, set start page mid $args->path = (!$args->path) ? '' : $args->path; $args->mpath = (!$args->mpath) ? '' : $args->mpath; + if (preg_match('!\bfiles/cache/!i', $args->path)) + { + $this->setError(-1); + $this->setMessage('msg_invalid_opage_pc_path'); + $this->setRedirectUrl(Context::get('success_return_url')); + return; + } + if (preg_match('!\bfiles/cache/!i', $args->mpath)) + { + $this->setError(-1); + $this->setMessage('msg_invalid_opage_mobile_path'); + $this->setRedirectUrl(Context::get('success_return_url')); + return; + } + $args->opage_proc_php = $args->opage_proc_php ?? 'N'; + $args->opage_proc_tpl = $args->opage_proc_tpl ?? 'N'; + if ($args->opage_proc_tpl === 'Y') + { + $args->opage_proc_php = 'Y'; + } unset($args->page_name); if($args->use_mobile != 'Y') $args->use_mobile = ''; $args->browser_title = trim(utf8_normalize_spaces($args->browser_title)); + $args->robots_tag = ($args->robots_tag === 'noindex') ? 'noindex' : 'all'; $args->meta_keywords = $args->meta_keywords ? implode(', ', array_map('trim', explode(',', $args->meta_keywords))) : ''; $args->meta_description = trim(utf8_normalize_spaces($args->meta_description)); - + // Check if an original module exists by using module_srl if($args->module_srl) { $columnList = array('module_srl'); - $module_info = $oModuleModel->getModuleInfoByModuleSrl($args->module_srl, $columnList); + $module_info = ModuleModel::getModuleInfoByModuleSrl($args->module_srl, $columnList); if($module_info->module_srl != $args->module_srl) { unset($args->module_srl); @@ -79,6 +97,7 @@ class pageAdminController extends page } } // Insert/update depending on module_srl + $oModuleController = ModuleController::getInstance(); if(!$args->module_srl) { $output = $oModuleController->insertModule($args); @@ -173,12 +192,11 @@ class pageAdminController extends page if(!$output->toBool()) return $output; // On the page, change the validity status of the attached file $oFileController = getController('file'); - $oFileController->setFilesValid($module_info->module_srl); + $oFileController->setFilesValid($module_info->module_srl, 'mod'); $this->add("module_srl", $module_info->module_srl); $this->add("page", Context::get('page')); $this->add("mid", $module_info->mid); - $this->setMessage($msg_code); // Create cache file $this->procPageAdminRemoveWidgetCache(); @@ -269,7 +287,7 @@ class pageAdminController extends page if($module_info->page_type == 'WIDGET') { - $path = _XE_PATH_ . 'files/cache/page/'; + $path = RX_BASEDIR . 'files/cache/page/'; $cache_files = FileHandler::readDir($path, '/^' . $module_info->module_srl . '\./'); foreach($cache_files as $file_name) { @@ -278,7 +296,7 @@ class pageAdminController extends page } else if($module_info->page_type == 'OUTSIDE') { - $path = _XE_PATH_ . 'files/cache/page/'; + $path = RX_BASEDIR . 'files/cache/page/'; $cache_files = FileHandler::readDir($path, '/^' . $module_info->module_srl . './'); foreach($cache_files as $file_name) { @@ -291,15 +309,10 @@ class pageAdminController extends page function procPageAdminArticleDocumentInsert() { - $oDocumentModel = getModel('document'); - $oDocumentController = getController('document'); - + // Check privileges $logged_info = Context::get('logged_info'); - - $oModuleModel = getModel('module'); - $grant = $oModuleModel->getGrant($this->module_info, $logged_info); - - if(!$grant->manager) + $grant = ModuleModel::getGrant($this->module_info, $logged_info); + if(!$grant->manager && !$grant->modify) { throw new Rhymix\Framework\Exceptions\NotPermitted; } @@ -308,47 +321,64 @@ class pageAdminController extends page $obj->module_srl = $this->module_info->module_srl; $obj->is_notice = 'N'; - settype($obj->title, "string"); - if($obj->title == '') $obj->title = cut_str(strip_tags($obj->content),20,'...'); - //그래도 없으면 Untitled - if($obj->title == '') $obj->title = 'Untitled'; + // If the tile is empty, extract string from the contents. + $obj->title = escape($obj->title, false); + if ($obj->title === '') + { + $obj->title = escape(cut_str(trim(utf8_normalize_spaces(strip_tags($obj->content))), 20, '...'), false); + } + if ($obj->title === '') + { + $obj->title = 'Untitled'; + } - $document_srl = $obj->document_srl; + // Check original document + $oDocument = DocumentModel::getDocument($obj->document_srl); + if ($oDocument->isExists() && $oDocument->get('module_srl') != $this->module_info->module_srl) + { + throw new Rhymix\Framework\Exceptions\NotPermitted; + } - // 이미 존재하는 글인지 체크 - $oDocument = $oDocumentModel->getDocument($obj->document_srl); - - $bAnonymous = false; - $target = ($obj->ismobile == 'Y') ? 'mdocument_srl' : 'document_srl'; - - // 이미 존재하는 경우 수정 - if($oDocument->isExists() && $oDocument->document_srl == $obj->document_srl) + // Insert or update document + $oDocumentController = DocumentController::getInstance(); + if ($oDocument->isExists()) { $output = $oDocumentController->updateDocument($oDocument, $obj); + if (!$output->toBool()) + { + return $output; + } $msg_code = 'success_updated'; + $document_srl = $obj->document_srl; } else { - // 그렇지 않으면 신규 등록 - $output = $oDocumentController->insertDocument($obj, $bAnonymous); + $output = $oDocumentController->insertDocument($obj); + if (!$output->toBool()) + { + return $output; + } $msg_code = 'success_registed'; $document_srl = $output->get('document_srl'); } + // Update module info + $target = ($obj->isMobile == 'Y') ? 'mdocument_srl' : 'document_srl'; if(!isset($this->module_info->{$target}) || (isset($this->module_info->{$target}) && $this->module_info->{$target} !== $document_srl)) { - $oModuleController = getController('module'); $this->module_info->{$target} = $document_srl; - $oModuleController->updateModule($this->module_info); + $oModuleController = ModuleController::getInstance(); + $output = $oModuleController->updateModule($this->module_info); + if (!$output->toBool()) + { + return $output; + } } - // 오류 발생시 멈춤 - if(!$output->toBool()) return $output; - // 결과를 리턴 $this->add('mid', Context::get('mid')); $this->add('document_srl', $output->get('document_srl')); - $this->add('is_mobile', $obj->ismobile); + $this->add('is_mobile', $obj->isMobile); // 성공 메세지 등록 $this->setMessage($msg_code); diff --git a/modules/page/page.admin.view.php b/modules/page/page.admin.view.php index 511857439..f78ee30c6 100644 --- a/modules/page/page.admin.view.php +++ b/modules/page/page.admin.view.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * @brief page admin view of the module class */ -class pageAdminView extends page +class PageAdminView extends Page { var $module_srl = 0; var $list_count = 20; @@ -18,12 +18,10 @@ class pageAdminView extends page { // Pre-check if module_srl exists. Set module_info if exists $module_srl = Context::get('module_srl'); - // Create module model object - $oModuleModel = getModel('module'); // module_srl two come over to save the module, putting the information in advance if($module_srl) { - $module_info = $oModuleModel->getModuleInfoByModuleSrl($module_srl); + $module_info = ModuleModel::getModuleInfoByModuleSrl($module_srl); if(!$module_info) { Context::set('module_srl',''); @@ -37,7 +35,7 @@ class pageAdminView extends page } } // Get a list of module categories - $module_category = $oModuleModel->getModuleCategories(); + $module_category = ModuleModel::getModuleCategories(); Context::set('module_category', $module_category); //Security $security = new Security(); @@ -65,8 +63,7 @@ class pageAdminView extends page if(in_array($search_target,$search_target_list) && $search_keyword) $args->{$search_target} = $search_keyword; $output = executeQuery('page.getPageList', $args); - $oModuleModel = getModel('module'); - $page_list = $oModuleModel->addModuleExtraVars($output->data); + $page_list = ModuleModel::addModuleExtraVars($output->data); moduleModel::syncModuleToSite($page_list); $oModuleAdminModel = getAdminModel('module'); /* @var $oModuleAdminModel moduleAdminModel */ @@ -103,29 +100,26 @@ class pageAdminView extends page // If you do not value module_srl just showing the index page if(!$module_srl) return $this->dispPageAdminContent(); // If the layout is destined to add layout information haejum (layout_title, layout) - if($module_info->layout_srl) + if($module_info->layout_srl > 0) { - $oLayoutModel = getModel('layout'); - $layout_info = $oLayoutModel->getLayout($module_info->layout_srl); + $layout_info = LayoutModel::getLayout($module_info->layout_srl); $module_info->layout = $layout_info->layout; $module_info->layout_title = $layout_info->layout_title; } // Get a layout list - $oLayoutModel = getModel('layout'); - $layout_list = $oLayoutModel->getLayoutList(); + $layout_list = LayoutModel::getLayoutList(); Context::set('layout_list', $layout_list); - $mobile_layout_list = $oLayoutModel->getLayoutList(0,"M"); + $mobile_layout_list = LayoutModel::getLayoutList(0,"M"); Context::set('mlayout_list', $mobile_layout_list); // Set a template file if($this->module_info->page_type == 'ARTICLE') { - $oModuleModel = getModel('module'); - $skin_list = $oModuleModel->getSkins($this->module_path); + $skin_list = ModuleModel::getSkins($this->module_path); Context::set('skin_list',$skin_list); - $mskin_list = $oModuleModel->getSkins($this->module_path, "m.skins"); + $mskin_list = ModuleModel::getSkins($this->module_path, "m.skins"); Context::set('mskin_list', $mskin_list); } @@ -171,7 +165,7 @@ class pageAdminView extends page Context::set('module_srl',$this->module_srl); } - $oPageMobile = &getMobile('page'); + $oPageMobile = getMobile('page'); $oPageMobile->module_info = $this->module_info; $page_type_name = strtolower($this->module_info->page_type); $method = '_get' . ucfirst($page_type_name) . 'Content'; @@ -179,8 +173,7 @@ class pageAdminView extends page { if($method == '_getArticleContent' && $this->module_info->is_mskin_fix == 'N') { - $oModuleModel = getModel('module'); - $oPageMobile->module_info->mskin = $oModuleModel->getModuleDefaultSkin('page', 'M'); + $oPageMobile->module_info->mskin = ModuleModel::getModuleDefaultSkin('page', 'M'); } $page_content = $oPageMobile->{$method}(); } @@ -188,10 +181,10 @@ class pageAdminView extends page { throw new Rhymix\Framework\Exception(sprintf('%s method is not exists', $method)); } - + Context::set('module_info', $this->module_info); Context::set('page_content', $page_content); - + $this->setLayoutFile(''); $this->setTemplateFile('mcontent'); } @@ -246,23 +239,22 @@ class pageAdminView extends page Context::set('content', $content); // Convert them to teach the widget - $oWidgetController = getController('widget'); + $oWidgetController = WidgetController::getInstance(); $content = $oWidgetController->transWidgetCode($content, true, !$isMobile); // $content = str_replace('$', '$', $content); Context::set('page_content', $content); // Set widget list - $oWidgetModel = getModel('widget'); - $widget_list = $oWidgetModel->getDownloadedWidgetList(); + $widget_list = WidgetModel::getDownloadedWidgetList(); Context::set('widget_list', $widget_list); //Security $security = new Security(); $security->encodeHTML('widget_list..title','module_info.mid'); - + // Load admin resources - $oTemplate = TemplateHandler::getInstance(); + $oTemplate = Rhymix\Framework\Template::getInstance(); $oTemplate->compile('modules/admin/tpl', '_admin_common.html'); - + // Set a template file $this->setLayoutFile(''); $this->setTemplateFile($templateFile); @@ -270,8 +262,7 @@ class pageAdminView extends page function _setArticleTypeContentModify($isMobile = false) { - $oDocumentModel = getModel('document'); - $oDocument = $oDocumentModel->getDocument(0); + $oDocument = DocumentModel::getDocument(0); if($isMobile) { @@ -289,7 +280,7 @@ class pageAdminView extends page $document_srl = $this->module_info->{$target}; $oDocument->setDocument($document_srl); Context::set('document_srl', $document_srl); - } + } else if(Context::get('document_srl')) { $document_srl = Context::get('document_srl'); @@ -300,13 +291,25 @@ class pageAdminView extends page { $oDocument->add('module_srl', $this->module_info->module_srl); } - + Context::addJsFilter($this->module_path.'tpl/filter', 'insert_article.xml'); Context::set('oDocument', $oDocument); Context::set('mid', $this->module_info->mid); - - $this->setLayoutFile(''); - $this->setTemplateFile('article_content_modify'); + + if(config('view.manager_layout') !== 'admin') + { + $this->setLayoutAndTemplatePaths($isMobile ? 'M' : 'P', $this->module_info); + } + $skin_path = rtrim($this->getTemplatePath(), '/') . '/'; + if (file_exists($skin_path . 'content_modify.blade.php') || file_exists($skin_path . 'content_modify.html')) + { + $this->setTemplateFile('content_modify'); + } + else + { + $this->setTemplatePath($this->module_path . 'tpl'); + $this->setTemplateFile('article_content_modify'); + } } /** @@ -317,9 +320,8 @@ class pageAdminView extends page $module_srl = Context::get('module_srl'); if(!$module_srl) return $this->dispContent(); - $oModuleModel = getModel('module'); $columnList = array('module_srl', 'module', 'mid'); - $module_info = $oModuleModel->getModuleInfoByModuleSrl($module_srl, $columnList); + $module_info = ModuleModel::getModuleInfoByModuleSrl($module_srl, $columnList); Context::set('module_info',$module_info); // Set a template file $this->setTemplateFile('page_delete'); diff --git a/modules/page/page.api.php b/modules/page/page.api.php index 675cc9d99..59954d558 100644 --- a/modules/page/page.api.php +++ b/modules/page/page.api.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * @brief View Action page for the module API processing */ -class pageAPI extends page +class PageAPI extends Page { /** * @brief Page information diff --git a/modules/page/page.class.php b/modules/page/page.class.php index e1bf14d09..1c008803a 100644 --- a/modules/page/page.class.php +++ b/modules/page/page.class.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * @brief high class of the module page */ -class page extends ModuleObject +class Page extends ModuleObject { /** * @brief Implement if additional tasks are necessary when installing diff --git a/modules/page/page.controller.php b/modules/page/page.controller.php index 24378049c..897b1c54c 100644 --- a/modules/page/page.controller.php +++ b/modules/page/page.controller.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * @brief controller class of the document module */ -class pageController extends page +class PageController extends Page { var $target_path = ''; @@ -67,7 +67,7 @@ class pageController extends page } /** - * @brief Change the value of src, href in the content + * @brief Change the value of src, href in the content */ function replaceSrc($content, $path) { diff --git a/modules/page/page.mobile.php b/modules/page/page.mobile.php index 1b4f41bf2..967b13013 100644 --- a/modules/page/page.mobile.php +++ b/modules/page/page.mobile.php @@ -1,106 +1,12 @@ */ -class pageMobile extends pageView +class PageMobile extends PageView { - function init() - { - switch($this->module_info->page_type) - { - case 'WIDGET' : - { - $this->cache_file = sprintf("%sfiles/cache/page/%d.%s.%s.m.cache.php", _XE_PATH_, $this->module_info->module_srl, Context::getLangType(), Context::getSslStatus()); - $this->interval = (int)($this->module_info->page_caching_interval); - break; - } - case 'OUTSIDE' : - { - $this->cache_file = sprintf("./files/cache/opage/%d.%s.m.cache.php", $this->module_info->module_srl, Context::getSslStatus()); - $this->interval = (int)($this->module_info->page_caching_interval); - $this->path = $this->module_info->mpath; - break; - } - } - } - - function dispPageIndex() - { - // Variables used in the template Context:: set() - if($this->module_srl) Context::set('module_srl',$this->module_srl); - - $page_type_name = strtolower($this->module_info->page_type); - $method = '_get' . ucfirst($page_type_name) . 'Content'; - if (method_exists($this, $method)) - { - $page_content = $this->{$method}(); - } - else - { - throw new Rhymix\Framework\Exception(sprintf('%s method is not exists', $method)); - } - - Context::set('module_info', $this->module_info); - Context::set('page_content', $page_content); - - $this->setTemplatePath($this->module_path . 'tpl'); - $this->setTemplateFile('mobile'); - } - - function _getWidgetContent() - { - // Arrange a widget ryeolro - if($this->module_info->mcontent) - { - $cache_file = sprintf("%sfiles/cache/page/%d.%s.m.cache.php", _XE_PATH_, $this->module_info->module_srl, Context::getLangType()); - $interval = (int)($this->module_info->page_caching_interval); - if($interval>0) - { - if(!file_exists($cache_file) || filesize($cache_file) < 1) - { - $mtime = 0; - } - else - { - $mtime = filemtime($cache_file); - } - - if($mtime + $interval*60 > $_SERVER['REQUEST_TIME']) - { - $page_content = FileHandler::readFile($cache_file); - $page_content = str_replace('' . PHP_EOL; - $contents = sprintf($sign, 'start') . $contents . sprintf($sign, 'end'); - } - - return $contents; + + // Convert relative paths to absolute paths. + $this->path = str_replace('\\', '/', dirname($real_target_file)) . '/'; + $content = preg_replace_callback('/\b(target=|src=|href=|url\()("|\')?([^"\'\)]+)("|\'\))?/is', array($this, '_replacePath'), $content); + $content = preg_replace_callback('/( + +

                {$oDocument->getTitle()}

                + + {$oDocument->getContent(false)} + + {$lang->none_content} + diff --git a/modules/page/skins/default/skin.xml b/modules/page/skins/default/skin.xml index 59537de8e..9dc0e520a 100644 --- a/modules/page/skins/default/skin.xml +++ b/modules/page/skins/default/skin.xml @@ -44,26 +44,5 @@ 非表示 - - 팝업메뉴 표시 - Display popup menu - ポップアップメニュー表示 - - 문서 하단의 팝업 메뉴를 표시 할지 선택합니다. - - - ドキュメント下段のポップアップメニューの表示可否を選択します。 - - - 출력 - Show - 表示 - - - 출력하지 않음 - Hide - 非表示 - - diff --git a/modules/page/tpl/article_content_modify.html b/modules/page/tpl/article_content_modify.html index e7de6efa7..48c809a02 100644 --- a/modules/page/tpl/article_content_modify.html +++ b/modules/page/tpl/article_content_modify.html @@ -5,13 +5,13 @@
                - +
                {$oDocument->getEditor()}
                - +

                {$lang->about_tag}

                diff --git a/modules/page/tpl/content.html b/modules/page/tpl/content.html index 87c1c9b3d..affc2304b 100644 --- a/modules/page/tpl/content.html +++ b/modules/page/tpl/content.html @@ -15,4 +15,13 @@
                + + diff --git a/modules/page/tpl/css/mpage.css b/modules/page/tpl/css/mpage.css deleted file mode 100644 index 5b460639c..000000000 --- a/modules/page/tpl/css/mpage.css +++ /dev/null @@ -1,6 +0,0 @@ -/* Mobile XE */ -body{margin:0;background:#fff;color:#000;word-wrap:break-word} -body,input,textarea,select,button,table{font-family:Tahoma, Geneva, sans-serif} -img{border:0} -em{font-style:normal} -.bd{position:relative;overflow:hidden;clear:both} diff --git a/modules/page/tpl/css/page.css b/modules/page/tpl/css/page.css index 1a0f31e54..e13bfbf88 100644 --- a/modules/page/tpl/css/page.css +++ b/modules/page/tpl/css/page.css @@ -4,4 +4,4 @@ h3 { margin:0 10px 0 10px; } .buttonBox { border:2px solid #EEEEEE; padding:5px; overflow:hidden; *zoom:1;} .buttonBox .fr { height:28px; } .buttonBox .fl { height:28px; } -.buttonBox .buttonDiv { margin:2px 0; padding:0; border-top:1px solid EEEEEE; clear:both; } +.buttonBox .buttonDiv { margin:2px 0; padding:0; border-top:1px solid #EEEEEE; clear:both; } diff --git a/modules/page/tpl/header.html b/modules/page/tpl/header.html index 38c7f679b..d6193a43b 100644 --- a/modules/page/tpl/header.html +++ b/modules/page/tpl/header.html @@ -3,13 +3,13 @@

                {$lang->page_management} - > {$module_info->mid}({$lang->is_default}) + > {$module_info->mid}({$lang->is_default}) - +

                - -
                  + +
                  • {$lang->cmd_list}
                  • {$lang->cmd_back}
                  • diff --git a/modules/page/tpl/index.html b/modules/page/tpl/index.html index dc9d9ab8b..ce838a420 100644 --- a/modules/page/tpl/index.html +++ b/modules/page/tpl/index.html @@ -12,16 +12,16 @@ - + {$lang->cmd_cancel} @@ -47,17 +47,18 @@ jQuery(function($){
                {$lang->no} {$lang->module_category} {$lang->page_type}{$lang->mid}{$lang->domain} /{$lang->url} {$lang->browser_title} {$lang->regdate} {$lang->cmd_edit}
                {$no}{$val->module_srl} @@ -69,16 +70,22 @@ jQuery(function($){ {$module_category[$val->module_category_srl]->title} {$val->page_type}{$lang->page_type_name[$val->page_type] ?? $val->page_type} + {@ + if (isset($val->domain_srl) && $val->domain_srl > -1 && !isset($val->domain)): + $val->domain = lang('deleted_domain'); + endif; + } + {$val->domain ?? ''}{\RX_BASEURL} + {$val->mid} {$val->browser_title} {zdate($val->regdate,"Y-m-d")} - {$lang->cmd_setup} - | - {$lang->cmd_copy} - | - {$lang->cmd_delete} + {$lang->cmd_setup}   + {$lang->cmd_copy}   + {$lang->cmd_delete}
                @@ -108,20 +108,26 @@ @@ -129,7 +135,7 @@
                {$lang->cmd_signup} -  {$lang->point_given_prefix} + {strtr($lang->point_given_prefix, ['$'.'point' => $config->point_name])} -  {$lang->point_given_suffix} +  {strtr($lang->point_given_suffix, ['$'.'point' => $config->point_name])}
                {$lang->cmd_login} -  {$lang->point_given_prefix} + {strtr($lang->point_given_prefix, ['$'.'point' => $config->point_name])} -  {$lang->point_given_suffix} +  {strtr($lang->point_given_suffix, ['$'.'point' => $config->point_name])}
                {lang('point_' . $action_type)} -  {$lang->point_given_prefix} - -  {$lang->point_given_suffix} + {strtr($lang->point_given_prefix, ['$'.'point' => $config->point_name])} + +  {strtr($lang->point_given_suffix, ['$'.'point' => $config->point_name])} + + + {$lang->point_time_limit_prefix}  - +  {$lang->point_time_limit_suffix}
                - +
                @@ -156,11 +162,13 @@ -
                +
                - + +  {$lang->level} + {$lang->default_group}
                @@ -170,13 +178,13 @@
                -{@$point_group = @array_flip($config->point_group)} + {@$point_group = @array_flip($config->point_group ?? [])}

                {$lang->level_point}

                {$lang->expression}

                - +
                @@ -189,9 +197,9 @@ 1 - 1 - -{@$point_group_item = $point_group[1]} + 1 + +{@$point_group_item = $point_group[1] ?? null} {@$title=array()} @@ -203,7 +211,7 @@ {implode(', ', $title)} -{@$point_group_item = $point_group[$i]} +{@$point_group_item = $point_group[$i] ?? null} {@$title[0] = $group_list[$point_group_item.'']->title} @@ -213,13 +221,14 @@ {$i} - {$i} - + {$i} + {implode(', ', $title)}
                +
                diff --git a/modules/point/tpl/js/point_admin.js b/modules/point/tpl/js/point_admin.js index 2ba54935b..80675df11 100644 --- a/modules/point/tpl/js/point_admin.js +++ b/modules/point/tpl/js/point_admin.js @@ -6,6 +6,14 @@ jQuery(function($){ +$('#point_module_config_form').on('submit', function() { + var level_step = []; + $(this).find('.level_step').each(function() { + level_step.push(parseInt($(this).val(), 10)); + }); + $('#level_step').val(level_step.join(',')); +}); + $('button.calc_point').click(function(){ var $this, form, elems, reset, el, fn, i=0; diff --git a/modules/point/tpl/member_list.html b/modules/point/tpl/member_list.html index 1bf6c30fc..437594bfc 100644 --- a/modules/point/tpl/member_list.html +++ b/modules/point/tpl/member_list.html @@ -23,7 +23,7 @@ @@ -126,7 +126,7 @@ diff --git a/modules/point/tpl/point_module_config.html b/modules/point/tpl/point_module_config.html index 065ab98ce..e8e5f822a 100644 --- a/modules/point/tpl/point_module_config.html +++ b/modules/point/tpl/point_module_config.html @@ -1,101 +1,118 @@

                {$lang->point}

                -
                - +
                - {$module_config['point_name']} + {$module_config['point_name']} +   {$lang->cmd_point_revert_on_delete} +
                - {$module_config['point_name']} + {$module_config['point_name']} +   {$lang->cmd_point_revert_on_delete} +
                - {$module_config['point_name']} + {$module_config['point_name']} +   {$lang->cmd_point_revert_on_delete} +
                - {$module_config['point_name']} + {$module_config['point_name']}
                - {$module_config['point_name']} + {$module_config['point_name']}
                - {$module_config['point_name']} + {$module_config['point_name']}
                - {$module_config['point_name']} + {$module_config['point_name']}
                - {$module_config['point_name']} + {$module_config['point_name']}
                - {$module_config['point_name']} + {$module_config['point_name']}
                - {$module_config['point_name']} + {$module_config['point_name']}
                - {$module_config['point_name']} + {$module_config['point_name']}
                - {$module_config['point_name']} + {$module_config['point_name']}
                - {$module_config['point_name']} + {$module_config['point_name']}
                - {$module_config['point_name']} + {$module_config['point_name']}
                - {$module_config['point_name']} + {$module_config['point_name']}
                diff --git a/modules/poll/conf/info.xml b/modules/poll/conf/info.xml index 88ed78dda..b6cb0f92b 100644 --- a/modules/poll/conf/info.xml +++ b/modules/poll/conf/info.xml @@ -18,8 +18,8 @@ Этот модуль служит для управления опросами. 管理投票調查的模組。 Oylamaları düzenlemek için kullanılan modüldür. - 2.0 - 2015-06-09 + RX_VERSION + RX_CORE content diff --git a/modules/poll/conf/module.xml b/modules/poll/conf/module.xml index 9b102a5c9..1dd0cd384 100644 --- a/modules/poll/conf/module.xml +++ b/modules/poll/conf/module.xml @@ -6,24 +6,32 @@ - + - + - + - + + + + + + + + + Poll diff --git a/modules/poll/lang/en.php b/modules/poll/lang/en.php index 3ce642f44..c2776325f 100644 --- a/modules/poll/lang/en.php +++ b/modules/poll/lang/en.php @@ -11,6 +11,7 @@ $lang->success_poll = 'Thank you for joining the poll.'; $lang->msg_already_poll = 'You already polled!'; $lang->msg_poll_is_null = 'Please select a poll to delete.'; $lang->msg_checked_poll_is_deleted = '%d poll(s) are deleted.'; +$lang->confirm_poll_delete = 'Are you sure to delete %s of poll(s)?'; $lang->msg_check_poll_item = 'Please select a poll item to poll.\\n(Required poll item(s) may be different in each poll.)'; $lang->msg_poll_not_exists = 'The selected poll does not exist.'; $lang->cmd_null_item = 'No item value exists to post a poll. Please re-try.'; diff --git a/modules/poll/poll.admin.controller.php b/modules/poll/poll.admin.controller.php index 8be1eae7e..ed8aaf2bf 100644 --- a/modules/poll/poll.admin.controller.php +++ b/modules/poll/poll.admin.controller.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * @brief The admin controller class of the poll module */ -class pollAdminController extends poll +class PollAdminController extends Poll { /** * @brief Initialization @@ -93,7 +93,7 @@ class pollAdminController extends poll $args->poll_index_srl = $poll_index_srl; - $oDB = &DB::getInstance(); + $oDB = DB::getInstance(); $oDB->begin(); $output = executeQueryArray('poll.getPollByDeletePollTitle', $args); @@ -145,7 +145,7 @@ class pollAdminController extends poll $args = new stdClass; $args->poll_srl = $poll_srl; - $oDB = &DB::getInstance(); + $oDB = DB::getInstance(); $oDB->begin(); $output = $oDB->executeQuery('poll.deletePoll', $args); diff --git a/modules/poll/poll.admin.model.php b/modules/poll/poll.admin.model.php index 036a31e8a..e7dc17daa 100644 --- a/modules/poll/poll.admin.model.php +++ b/modules/poll/poll.admin.model.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * @brief The admin model class of the poll module */ -class pollAdminModel extends poll +class PollAdminModel extends Poll { /** * @brief Initialization @@ -19,11 +19,7 @@ class pollAdminModel extends poll */ function getPollList($args) { - $output = executeQueryArray('poll.getPollList', $args); - if(!$output->toBool()) return $output; - - //if($output->data && !is_array($output->data)) $output->data = array($output->data); - return $output; + return executeQueryArray('poll.getPollList', $args); } /** @@ -31,10 +27,7 @@ class pollAdminModel extends poll */ function getPollListWithMember($args) { - $output = executeQueryArray('poll.getPollListWithMember', $args); - if(!$output->toBool()) return $output; - - return $output; + return executeQueryArray('poll.getPollListWithMember', $args); } /** @@ -42,28 +35,26 @@ class pollAdminModel extends poll */ function getPollAdminTarget() { - $poll_srl = Context::get('poll_srl'); + //$poll_srl = Context::get('poll_srl'); $upload_target_srl = Context::get('upload_target_srl'); - $oDocumentModel = getModel('document'); - $oCommentModel = getModel('comment'); - - $oDocument = $oDocumentModel->getDocument($upload_target_srl); - - if(!$oDocument->isExists()) $oComment = $oCommentModel->getComment($upload_target_srl); - - if($oComment && $oComment->isExists()) - { - $this->add('document_srl', $oComment->get('document_srl')); - $this->add('comment_srl', $oComment->get('comment_srl')); - } - elseif($oDocument->isExists()) + $oDocument = DocumentModel::getDocument($upload_target_srl); + if ($oDocument->isExists()) { $this->add('document_srl', $oDocument->get('document_srl')); } else { - $this->setError('msg_not_founded'); + $oComment = CommentModel::getComment($upload_target_srl); + if ($oComment->isExists()) + { + $this->add('document_srl', $oComment->get('document_srl')); + $this->add('comment_srl', $oComment->get('comment_srl')); + } + else + { + $this->setError('msg_not_founded'); + } } } } diff --git a/modules/poll/poll.admin.view.php b/modules/poll/poll.admin.view.php index e65742301..092e950d9 100644 --- a/modules/poll/poll.admin.view.php +++ b/modules/poll/poll.admin.view.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * @brief The admin view class of the poll module */ -class pollAdminView extends poll +class PollAdminView extends Poll { /** * @brief Initialization @@ -50,9 +50,13 @@ class pollAdminView extends poll // Get the list $oPollAdminModel = getAdminModel('poll'); $output = $oPollAdminModel->getPollListWithMember($args); + if (!$output->toBool()) + { + return $output; + } // check poll type. document or comment - if(is_array($output->data)) + if(is_array($output->data) && count($output->data)) { $uploadTargetSrlList = array(); foreach($output->data as $value) @@ -88,7 +92,6 @@ class pollAdminView extends poll Context::set('page', $output->page); Context::set('poll_list', $output->data); Context::set('page_navigation', $output->page_navigation); - Context::set('module_list', $module_list); $security = new Security(); $security->encodeHTML('poll_list..title', 'poll_list..nick_name'); diff --git a/modules/poll/poll.class.php b/modules/poll/poll.class.php index 046248aca..be6e6e163 100644 --- a/modules/poll/poll.class.php +++ b/modules/poll/poll.class.php @@ -5,29 +5,19 @@ * @author NAVER (developers@xpressengine.com) * @brief The parent class of the poll module */ -class poll extends ModuleObject +class Poll extends ModuleObject { /** * @brief Additional tasks required to accomplish during the installation */ function moduleInstall() { - // Register in the action forward (to use in administrator mode) - $oModuleController = getController('module'); // Set the default skin - $oModuleController = getController('module'); - $config = new stdClass; $config->skin = 'default'; $config->colorset = 'normal'; + $oModuleController = ModuleController::getInstance(); $oModuleController->insertModuleConfig('poll', $config); - // 2007.10.17 When deleting posts/comments delete the poll as well - $oModuleController->insertTrigger('document.insertDocument', 'poll', 'controller', 'triggerInsertDocumentPoll', 'after'); - $oModuleController->insertTrigger('comment.insertComment', 'poll', 'controller', 'triggerInsertCommentPoll', 'after'); - $oModuleController->insertTrigger('document.updateDocument', 'poll', 'controller', 'triggerUpdateDocumentPoll', 'after'); - $oModuleController->insertTrigger('comment.updateComment', 'poll', 'controller', 'triggerUpdateCommentPoll', 'after'); - $oModuleController->insertTrigger('document.deleteDocument', 'poll', 'controller', 'triggerDeleteDocumentPoll', 'after'); - $oModuleController->insertTrigger('comment.deleteComment', 'poll', 'controller', 'triggerDeleteCommentPoll', 'after'); } /** @@ -35,16 +25,7 @@ class poll extends ModuleObject */ function checkUpdate() { - $oModuleModel = getModel('module'); - $oDB = &DB::getInstance(); - - // 2007.10.17 When deleting posts/comments delete the poll as well - if(!$oModuleModel->getTrigger('document.insertDocument', 'poll', 'controller', 'triggerInsertDocumentPoll', 'after')) return true; - if(!$oModuleModel->getTrigger('comment.insertComment', 'poll', 'controller', 'triggerInsertCommentPoll', 'after')) return true; - if(!$oModuleModel->getTrigger('document.updateDocument', 'poll', 'controller', 'triggerUpdateDocumentPoll', 'after')) return true; - if(!$oModuleModel->getTrigger('comment.updateComment', 'poll', 'controller', 'triggerUpdateCommentPoll', 'after')) return true; - if(!$oModuleModel->getTrigger('document.deleteDocument', 'poll', 'controller', 'triggerDeleteDocumentPoll', 'after')) return true; - if(!$oModuleModel->getTrigger('comment.deleteComment', 'poll', 'controller', 'triggerDeleteCommentPoll', 'after')) return true; + $oDB = DB::getInstance(); if(!$oDB->isColumnExists('poll', 'poll_type')) { @@ -69,24 +50,7 @@ class poll extends ModuleObject */ function moduleUpdate() { - $oModuleModel = getModel('module'); - $oModuleController = getController('module'); - $oDB = &DB::getInstance(); - - // 2007.10.17 When deleting posts/comments delete the poll as well - if(!$oModuleModel->getTrigger('document.deleteDocument', 'poll', 'controller', 'triggerDeleteDocumentPoll', 'after')) - $oModuleController->insertTrigger('document.deleteDocument', 'poll', 'controller', 'triggerDeleteDocumentPoll', 'after'); - if(!$oModuleModel->getTrigger('comment.deleteComment', 'poll', 'controller', 'triggerDeleteCommentPoll', 'after')) - $oModuleController->insertTrigger('comment.deleteComment', 'poll', 'controller', 'triggerDeleteCommentPoll', 'after'); - // 2008.04.22 A poll connection to add posts/comments - if(!$oModuleModel->getTrigger('document.insertDocument', 'poll', 'controller', 'triggerInsertDocumentPoll', 'after')) - $oModuleController->insertTrigger('document.insertDocument', 'poll', 'controller', 'triggerInsertDocumentPoll', 'after'); - if(!$oModuleModel->getTrigger('comment.insertComment', 'poll', 'controller', 'triggerInsertCommentPoll', 'after')) - $oModuleController->insertTrigger('comment.insertComment', 'poll', 'controller', 'triggerInsertCommentPoll', 'after'); - if(!$oModuleModel->getTrigger('document.updateDocument', 'poll', 'controller', 'triggerUpdateDocumentPoll', 'after')) - $oModuleController->insertTrigger('document.updateDocument', 'poll', 'controller', 'triggerUpdateDocumentPoll', 'after'); - if(!$oModuleModel->getTrigger('comment.updateComment', 'poll', 'controller', 'triggerUpdateCommentPoll', 'after')) - $oModuleController->insertTrigger('comment.updateComment', 'poll', 'controller', 'triggerUpdateCommentPoll', 'after'); + $oDB = DB::getInstance(); if(!$oDB->isColumnExists('poll','poll_type')) { @@ -104,13 +68,6 @@ class poll extends ModuleObject } } - /** - * @brief Re-generate the cache file - */ - function recompileCache() - { - } - /** * @brief Check if this poll could display member information */ diff --git a/modules/poll/poll.controller.php b/modules/poll/poll.controller.php index f577b9242..eb3499e0b 100644 --- a/modules/poll/poll.controller.php +++ b/modules/poll/poll.controller.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * @brief Controller class for poll module */ -class pollController extends poll +class PollController extends Poll { /** * @brief Initialization @@ -30,9 +30,7 @@ class pollController extends poll $stop_date = date('YmdHis', $_SERVER['REQUEST_TIME']+60*60*24*30); } - $logged_info = Context::get('logged_info'); $vars = Context::getRequestVars(); - $args = new stdClass; $tmp_args = array(); @@ -70,9 +68,9 @@ class pollController extends poll $tmp_args[$poll_index]->item = array(); } - if($logged_info->is_admin != 'Y') + if(!$this->user->isAdmin()) { - $val = htmlspecialchars($val, ENT_COMPAT | ENT_HTML401, 'UTF-8', false); + $val = escape($val, false); } switch($tmp_arr[0]) @@ -81,7 +79,7 @@ class pollController extends poll $tmp_args[$poll_index]->title = $val; break; case 'checkcount': - $tmp_args[$poll_index]->checkcount = $val; + $tmp_args[$poll_index]->checkcount = max(1, intval($val)); break; case 'item': $tmp_args[$poll_index]->item[] = $val; @@ -96,27 +94,29 @@ class pollController extends poll $val->checkcount = 1; } - if($val->title && count($val->item)) + if($val->title && is_array($val->item) && count($val->item)) { $args->poll[] = $val; } } - if(!count($args->poll)) throw new Rhymix\Framework\Exception('cmd_null_item'); + if(!isset($args->poll) || !is_array($args->poll) || !count($args->poll)) + { + throw new Rhymix\Framework\Exception('cmd_null_item'); + } $args->stop_date = $stop_date; // Configure the variables $poll_srl = getNextSequence(); - $member_srl = $logged_info->member_srl?$logged_info->member_srl:0; - $oDB = &DB::getInstance(); + $oDB = DB::getInstance(); $oDB->begin(); // Register the poll $poll_args = new stdClass; $poll_args->poll_srl = $poll_srl; - $poll_args->member_srl = $member_srl; + $poll_args->member_srl = $this->user->member_srl; $poll_args->list_order = $poll_srl*-1; $poll_args->stop_date = $args->stop_date; $poll_args->poll_count = 0; @@ -139,7 +139,7 @@ class pollController extends poll $title_args->checkcount = $val->checkcount; $title_args->poll_count = 0; $title_args->list_order = $title_args->poll_index_srl * -1; - $title_args->member_srl = $member_srl; + $title_args->member_srl = $poll_args->member_srl; $title_args->upload_target_srl = $vars->upload_target_srl; $output = executeQuery('poll.insertPollTitle', $title_args); if(!$output->toBool()) @@ -180,8 +180,10 @@ class pollController extends poll if($poll_item_title=='') throw new Rhymix\Framework\Exception('msg_item_title_cannot_empty'); - $logged_info = Context::get('logged_info'); - if(!$logged_info) throw new Rhymix\Framework\Exception('msg_cannot_add_item'); + if(!$this->user->member_srl) + { + throw new Rhymix\Framework\Exception('msg_cannot_add_item'); + } if(!$poll_srl || !$poll_index_srl) throw new Rhymix\Framework\Exceptions\InvalidRequest; @@ -189,19 +191,29 @@ class pollController extends poll $args->poll_srl = $poll_srl; // Get the information related to the survey - $columnList = array('poll_type'); + $columnList = array('poll_type', 'stop_date'); $output = executeQuery('poll.getPoll', $args, $columnList); - if(!$output->data) throw new Rhymix\Framework\Exception('poll_no_poll_or_deleted_poll'); - $type = $output->data->poll_type; - - if(!$this->isAbletoAddItem($type)) throw new Rhymix\Framework\Exception('msg_cannot_add_item'); - - if($logged_info->is_admin != 'Y') + if(!$output->data) { - $poll_item_title = htmlspecialchars($poll_item_title, ENT_COMPAT | ENT_HTML401, 'UTF-8', false); + throw new Rhymix\Framework\Exception('poll_no_poll_or_deleted_poll'); + } + if($output->data->stop_date < date('Ymd')) + { + throw new Rhymix\Framework\Exception('msg_cannot_add_item'); } - $oDB = &DB::getInstance(); + $type = $output->data->poll_type; + if(!$this->isAbletoAddItem($type)) + { + throw new Rhymix\Framework\Exception('msg_cannot_add_item'); + } + + if(!$this->user->isAdmin()) + { + $poll_item_title = escape($poll_item_title, false); + } + + $oDB = DB::getInstance(); $oDB->begin(); $item_args = new stdClass; @@ -210,13 +222,14 @@ class pollController extends poll $item_args->title = $poll_item_title; $item_args->poll_count = 0; $item_args->upload_target_srl = 0; - $item_args->add_user_srl = $logged_info->member_srl; + $item_args->add_user_srl = $this->user->member_srl; $output = executeQuery('poll.insertPollItem', $item_args); if(!$output->toBool()) { $oDB->rollback(); return $output; } + $oDB->commit(); return $output; } @@ -226,8 +239,10 @@ class pollController extends poll $poll_index_srl = (int) Context::get('index_srl'); $poll_item_srl = Context::get('item_srl'); - $logged_info = Context::get('logged_info'); - if(!$logged_info) throw new Rhymix\Framework\Exception('msg_cannot_delete_item'); + if(!$this->user->member_srl) + { + throw new Rhymix\Framework\Exception('msg_cannot_delete_item'); + } if(!$poll_srl || !$poll_index_srl || !$poll_item_srl) throw new Rhymix\Framework\Exceptions\InvalidRequest; @@ -237,7 +252,7 @@ class pollController extends poll $args->poll_item_srl = $poll_item_srl; // Get the information related to the survey - $columnList = array('add_user_srl','poll_count'); + $columnList = array('add_user_srl', 'poll_count', 'stop_date'); $output = executeQuery('poll.getPollItem', $args, $columnList); $add_user_srl = $output->data->add_user_srl; $poll_count = $output->data->poll_count; @@ -245,13 +260,26 @@ class pollController extends poll // Get the information related to the survey $columnList = array('member_srl'); $output = executeQuery('poll.getPoll', $args, $columnList); - if(!$output->data) throw new Rhymix\Framework\Exception('poll_no_poll_or_deleted_poll'); + if(!$output->data) + { + throw new Rhymix\Framework\Exception('poll_no_poll_or_deleted_poll'); + } + if($output->data->stop_date < date('Ymd')) + { + throw new Rhymix\Framework\Exception('msg_cannot_delete_item'); + } + $poll_member_srl = $output->data->member_srl; + if($add_user_srl != $this->user->member_srl && $poll_member_srl != $this->user->member_srl) + { + throw new Rhymix\Framework\Exception('msg_cannot_delete_item'); + } + if($poll_count > 0) + { + throw new Rhymix\Framework\Exception('msg_cannot_delete_item_poll_exist'); + } - if($add_user_srl!=$logged_info->member_srl && $poll_member_srl!=$logged_info->member_srl) throw new Rhymix\Framework\Exception('msg_cannot_delete_item'); - if($poll_count>0) throw new Rhymix\Framework\Exception('msg_cannot_delete_item_poll_exist'); - - $oDB = &DB::getInstance(); + $oDB = DB::getInstance(); $oDB->begin(); $item_args = new stdClass; @@ -264,7 +292,7 @@ class pollController extends poll $oDB->rollback(); return $output; } - + $oDB->commit(); return $output; } @@ -299,12 +327,15 @@ class pollController extends poll } // If there is no response item, display an error - if(!count($item_srls)) throw new Rhymix\Framework\Exception('msg_check_poll_item'); + if(!is_array($item_srls) || !count($item_srls)) + { + throw new Rhymix\Framework\Exception('msg_check_poll_item'); + } // Make sure is the poll has already been taken $oPollModel = getModel('poll'); if($oPollModel->isPolled($poll_srl)) throw new Rhymix\Framework\Exception('msg_already_poll'); - $oDB = &DB::getInstance(); + $oDB = DB::getInstance(); $oDB->begin(); $args = new stdClass; @@ -330,12 +361,8 @@ class pollController extends poll $log_args = new stdClass; $log_args->poll_srl = $poll_srl; $log_args->poll_item = $args->poll_item_srl; - - $logged_info = Context::get('logged_info'); - $member_srl = $logged_info->member_srl?$logged_info->member_srl:0; - - $log_args->member_srl = $member_srl; - $log_args->ipaddress = $_SERVER['REMOTE_ADDR']; + $log_args->member_srl = $this->user->member_srl; + $log_args->ipaddress = \RX_CLIENT_IP; $output = executeQuery('poll.insertPollLog', $log_args); if(!$output->toBool()) @@ -346,18 +373,9 @@ class pollController extends poll $oDB->commit(); - //$skin = Context::get('skin'); - //if(!$skin || !is_dir(_XE_PATH_ . 'modules/poll/skins/'.$skin)) $skin = 'default'; - // Get tpl - //$tpl = $oPollModel->getPollHtml($poll_srl, '', $skin); - $this->add('poll_srl', $poll_srl); $this->add('poll_item_srl',$item_srls); - //$this->add('tpl',$tpl); $this->setMessage('success_poll'); - - //$returnUrl = Context::get('success_return_url') ? Context::get('success_return_url') : getNotEncodedUrl('', 'module', 'admin', 'act', 'dispPollAdminConfig'); - //$this->setRedirectUrl($returnUrl); } /** @@ -367,8 +385,15 @@ class pollController extends poll { $poll_srl = Context::get('poll_srl'); - $skin = Context::get('skin'); - if(!$skin || !is_dir(_XE_PATH_ . 'modules/poll/skins/'.$skin)) $skin = 'default'; + $skin = Context::get('skin') ?: 'default'; + if (!preg_match('/^[a-zA-Z0-9_-]+$/', $skin)) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest(); + } + if (!Rhymix\Framework\Storage::isDirectory(RX_BASEDIR . 'modules/poll/skins/' . $skin)) + { + $skin = 'default'; + } $oPollModel = getModel('poll'); $tpl = $oPollModel->getPollResultHtml($poll_srl, $skin); diff --git a/modules/poll/poll.model.php b/modules/poll/poll.model.php index 2c8a29185..de867ffb9 100644 --- a/modules/poll/poll.model.php +++ b/modules/poll/poll.model.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * @brief The model class for the poll modules */ -class pollModel extends poll +class PollModel extends Poll { /** * @brief Initialization @@ -15,7 +15,7 @@ class pollModel extends poll } /** - * @brief returns poll infomation + * @brief returns poll information */ public function _getPollinfo($poll_srl) { @@ -62,6 +62,7 @@ class pollModel extends poll unset($val->poll_srl); $val->my_item = false; if(($val->add_user_srl==$logged_info->member_srl || $poll_member_srl == $logged_info->member_srl) && $val->add_user_srl!=0) $val->my_item = true; + $val->hide = strpos($val->title, '[HIDE]') !== FALSE; $poll->poll[$val->poll_index_srl]->item[] = $val; } @@ -77,7 +78,7 @@ class pollModel extends poll } /** - * @brief returns poll infomation + * @brief returns poll information */ public function getPollinfo() { @@ -88,7 +89,7 @@ class pollModel extends poll } /** - * @brief returns poll item infomation + * @brief returns poll item information */ public function getPollitemInfo() { @@ -199,7 +200,7 @@ class pollModel extends poll } else { - $args->ipaddress = $_SERVER['REMOTE_ADDR']; + $args->ipaddress = \RX_CLIENT_IP; } $output = executeQuery('poll.getPollLog', $args); if($output->data->count) return true; @@ -221,9 +222,10 @@ class pollModel extends poll if(!$output->data) return ''; $poll = new stdClass; - $poll->style = $style; + $poll->style = preg_replace('/[^a-zA-Z0-9_-]/', '', $style); $poll->poll_count = (int)$output->data->poll_count; $poll->stop_date = $output->data->stop_date; + $skin = preg_replace('/[^a-zA-Z0-9_-]/', '', $skin); $columnList = array('poll_index_srl', 'title', 'checkcount', 'poll_count'); $output = executeQuery('poll.getPollTitle', $args, $columnList); @@ -262,7 +264,7 @@ class pollModel extends poll // The skin for the default configurations, and the colorset configurations $tpl_path = sprintf("%sskins/%s/", $this->module_path, $skin); - $oTemplate = &TemplateHandler::getInstance(); + $oTemplate = TemplateHandler::getInstance(); return $oTemplate->compile($tpl_path, $tpl_file); } @@ -279,7 +281,7 @@ class pollModel extends poll if(!$output->data) return ''; $poll = new stdClass; - $poll->style = $skin; + $poll->style = preg_replace('/[^a-zA-Z0-9_-]/', '', $skin); $poll->poll_count = (int)$output->data->poll_count; $poll->stop_date = $output->data->stop_date; @@ -311,7 +313,7 @@ class pollModel extends poll // The skin for the default configurations, and the colorset configurations $tpl_path = sprintf("%sskins/%s/", $this->module_path, $skin); - $oTemplate = &TemplateHandler::getInstance(); + $oTemplate = TemplateHandler::getInstance(); return $oTemplate->compile($tpl_path, $tpl_file); } /** [TO REVIEW] @@ -320,7 +322,15 @@ class pollModel extends poll */ public function getPollGetColorsetList() { - $skin = Context::get('skin'); + $skin = Context::get('skin') ?: 'default'; + if (!preg_match('/^[a-zA-Z0-9_-]+$/', $skin)) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest(); + } + if (!Rhymix\Framework\Storage::isDirectory(RX_BASEDIR . 'modules/poll/skins/' . $skin)) + { + $skin = 'default'; + } $oModuleModel = getModel('module'); $skin_info = $oModuleModel->loadSkinInfo($this->module_path, $skin); diff --git a/modules/poll/queries/getPollLog.xml b/modules/poll/queries/getPollLog.xml index 22d373b04..ac394b22d 100644 --- a/modules/poll/queries/getPollLog.xml +++ b/modules/poll/queries/getPollLog.xml @@ -9,7 +9,7 @@ - + diff --git a/modules/poll/queries/updatePollItemTarget.xml b/modules/poll/queries/updatePollItemTarget.xml index 31262f37b..101c2a029 100644 --- a/modules/poll/queries/updatePollItemTarget.xml +++ b/modules/poll/queries/updatePollItemTarget.xml @@ -1,4 +1,4 @@ - + diff --git a/modules/poll/schemas/poll.xml b/modules/poll/schemas/poll.xml index 2b770d7a2..6f9b9c0b3 100644 --- a/modules/poll/schemas/poll.xml +++ b/modules/poll/schemas/poll.xml @@ -1,11 +1,11 @@
                - - - - - - - - - + + + + + + + + +
                diff --git a/modules/poll/schemas/poll_item.xml b/modules/poll/schemas/poll_item.xml index d027c1ee0..7942222f4 100644 --- a/modules/poll/schemas/poll_item.xml +++ b/modules/poll/schemas/poll_item.xml @@ -1,9 +1,9 @@ - - - - - - - + + + + + + +
                diff --git a/modules/poll/schemas/poll_log.xml b/modules/poll/schemas/poll_log.xml index d878ba2dc..d94591d90 100644 --- a/modules/poll/schemas/poll_log.xml +++ b/modules/poll/schemas/poll_log.xml @@ -1,7 +1,7 @@ - - - - - + + + + +
                diff --git a/modules/poll/schemas/poll_title.xml b/modules/poll/schemas/poll_title.xml index 7ddfcd1e1..416bdcd55 100644 --- a/modules/poll/schemas/poll_title.xml +++ b/modules/poll/schemas/poll_title.xml @@ -1,12 +1,12 @@ - - - - - - - - - - + + + + + + + + + +
                diff --git a/modules/poll/skins/default/form.html b/modules/poll/skins/default/form.html index dfc87cc38..d8310f913 100644 --- a/modules/poll/skins/default/form.html +++ b/modules/poll/skins/default/form.html @@ -52,7 +52,7 @@ {@$_idx = $poll->poll_srl.'_'.$poll_srl_index.'_'.$item_srl} -
                +
                diff --git a/modules/poll/skins/default/skin.xml b/modules/poll/skins/default/skin.xml index 32f822982..f6ef18029 100644 --- a/modules/poll/skins/default/skin.xml +++ b/modules/poll/skins/default/skin.xml @@ -3,7 +3,7 @@ 설문조사 기본 스킨 投票系统默认皮肤 アンケート調査デフォルトスキン - Default Skin of teh Poll + Default Skin of the Poll Skin mặc định của thăm dò 投票系統預設面板 Oylamanın Varsayılan Dış Görünümü diff --git a/modules/poll/skins/simple/form.html b/modules/poll/skins/simple/form.html index 6f6fac4c1..2d6da2d9c 100644 --- a/modules/poll/skins/simple/form.html +++ b/modules/poll/skins/simple/form.html @@ -36,7 +36,7 @@ {@$_idx = $poll->poll_srl.'_'.$poll_srl_index.'_'.$item_srl} -
                +
                diff --git a/modules/rss/conf/info.xml b/modules/rss/conf/info.xml index 49c7d267d..e5726b1ad 100644 --- a/modules/rss/conf/info.xml +++ b/modules/rss/conf/info.xml @@ -10,8 +10,8 @@ Этот модуль служит для печати RSS. 負責輸出 RSS 的模組。 Bu modül RSS çıktısı almak içindir. - 2.0 - 2017-09-01 + RX_VERSION + RX_CORE utility diff --git a/modules/rss/conf/module.xml b/modules/rss/conf/module.xml index 5a1c76936..2bd09130c 100644 --- a/modules/rss/conf/module.xml +++ b/modules/rss/conf/module.xml @@ -4,12 +4,17 @@ - - + + - + + + + + + RSS diff --git a/modules/rss/lang/en.php b/modules/rss/lang/en.php index be3aef2b7..d368f3448 100644 --- a/modules/rss/lang/en.php +++ b/modules/rss/lang/en.php @@ -3,14 +3,15 @@ $lang->feed = 'Publish RSS Feed'; $lang->total_feed = 'Aggregated Feeds'; $lang->rss_disable = 'Disable RSS Feed'; $lang->feed_copyright = 'Copyright'; -$lang->feed_document_count = 'Number of articles per page'; +$lang->feed_document_count = 'Number of Articles per Page'; $lang->feed_image = 'Feed Image'; -$lang->rss_type = 'RSS feed type'; +$lang->rss_type = 'RSS Feed Type'; +$lang->module_feed_management = 'Feeds for Each Module'; $lang->open_rss = 'Open RSS'; -$lang->open_rss_types['Y'] = 'Open all'; -$lang->open_rss_types['H'] = 'Open summary'; -$lang->open_rss_types['N'] = 'Not open'; -$lang->open_feed_to_total = 'Included in aggregated feed'; +$lang->open_rss_types['Y'] = 'Full'; +$lang->open_rss_types['H'] = 'Summary'; +$lang->open_rss_types['N'] = 'Not Open'; +$lang->open_feed_to_total = 'Included in Aggregated Feed'; $lang->about_rss_disable = 'If checked, RSS will be disabled.'; $lang->about_rss_type = 'You can assign RSS feed type.'; $lang->about_open_rss = 'You can select RSS on the current page to be open to the public.\\nIf it is enabled, the article will be open to the public despite its view permissions.'; diff --git a/modules/rss/lang/ko.php b/modules/rss/lang/ko.php index 58f3a8b1e..ea6dec7e2 100644 --- a/modules/rss/lang/ko.php +++ b/modules/rss/lang/ko.php @@ -6,6 +6,7 @@ $lang->feed_copyright = '저작권'; $lang->feed_document_count = '한 페이지당 글 수'; $lang->feed_image = '피드 이미지'; $lang->rss_type = '출력할 피드(Feed) 형식'; +$lang->module_feed_management = '모듈별 피드 관리'; $lang->open_rss = '피드(Feed) 공개'; $lang->open_rss_types['Y'] = '전문 공개 '; $lang->open_rss_types['H'] = '요약 공개'; diff --git a/modules/rss/rss.admin.controller.php b/modules/rss/rss.admin.controller.php index 5241d5d06..37fb12a15 100644 --- a/modules/rss/rss.admin.controller.php +++ b/modules/rss/rss.admin.controller.php @@ -5,12 +5,12 @@ * * @author NAVER (developers@xpressengine.com) */ -class rssAdminController extends rss +class RssAdminController extends Rss { function init() { } - + /** * configuration */ @@ -18,7 +18,7 @@ class rssAdminController extends rss { $vars = Context::getRequestVars(); $config = getModel('rss')->getConfig(); - + if($img_file = $vars->image) { // Delete image file @@ -26,22 +26,22 @@ class rssAdminController extends rss { FileHandler::removeFile($config->image); } - + $vars->image = ''; - + // Upload image file if($img_file['tmp_name'] && is_uploaded_file($img_file['tmp_name'])) { $path = 'files/attach/images/rss'; $file_ext = strtolower(array_pop(explode('.', $img_file['name']))); $file_name = sprintf('%s/feed_image.%s', $path, $file_ext); - + // If file exists, delete if(file_exists($file_name)) { FileHandler::removeFile($file_name); } - + // Check image file extension if(!in_array($file_ext, array('jpg', 'jpeg', 'gif', 'png'))) { @@ -59,15 +59,19 @@ class rssAdminController extends rss } } } - + if(!in_array($vars->use_total_feed, array('Y','N'))) { $vars->open_rss = 'Y'; } - $vars->feed_document_count = (int) $vars->feed_document_count; - + $vars->feed_document_count = intval($vars->feed_document_count); + if ($vars->feed_document_count < 1 || $vars->feed_document_count > 1000) + { + $vars->feed_document_count = 20; + } + getController('module')->updateModuleConfig('rss', $vars); - + if(isset($msg['error'])) { throw new Rhymix\Framework\Exception($msg['error']); @@ -76,38 +80,38 @@ class rssAdminController extends rss { $this->setMessage(isset($msg) ? $msg : 'success_updated'); } - + $this->setRedirectUrl(Context::get('success_return_url') ?: getNotEncodedUrl('', 'module', 'admin', 'act', 'dispRssAdminIndex')); } - + /** * Part configuration */ function procRssAdminInsertModuleConfig() { $vars = Context::getRequestVars(); - + if($vars->target_module_srl) { $target_module_srls = explode(',', $vars->target_module_srl); } else { - $target_module_srls = array_keys($vars->open_rss); + $target_module_srls = array_keys($vars->open_rss ?: []); } - + if(!count($target_module_srls)) { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - + foreach($target_module_srls as $module_srl) { if(!$module_srl = intval($module_srl)) { continue; } - + $config = new stdClass; if(isset($vars->open_rss[$module_srl])) { @@ -117,12 +121,12 @@ class rssAdminController extends rss } else { - $config->open_rss = $vars->open_rss; + $config->open_rss = $vars->open_rss ?: []; $config->open_total_feed = $vars->open_total_feed; $config->feed_description = $vars->feed_description; $config->feed_copyright = $vars->feed_copyright; } - + if(!in_array($config->open_rss, array('Y', 'H', 'N'))) { $config->open_rss = 'N'; @@ -131,14 +135,14 @@ class rssAdminController extends rss { $config->open_total_feed = 'T_N'; } - + getController('module')->updateModulePartConfig('rss', $module_srl, $config); } $this->setMessage('success_updated'); $this->setRedirectUrl(Context::get('success_return_url') ?: getNotEncodedUrl('', 'module', 'admin', 'act', 'dispRssAdminIndex')); } - + function procRssAdminDeleteFeedImage() { $config = getModel('rss')->getConfig(); @@ -146,13 +150,13 @@ class rssAdminController extends rss { throw new Rhymix\Framework\Exceptions\InvalidRequest; } - + FileHandler::removeFile($config->image); - + $config->image = ''; getController('module')->insertModuleConfig('rss', $config); } - + /** * Compatible function */ @@ -161,7 +165,7 @@ class rssAdminController extends rss getController('module')->insertModuleConfig('rss', $config); return new BaseObject(); } - + /** * Compatible function */ @@ -170,7 +174,7 @@ class rssAdminController extends rss $config = new stdClass; $config->open_rss = $open_rss; $config->open_total_feed = $open_total_feed; - + if($feed_description != 'N') { $config->feed_description = $feed_description; @@ -179,7 +183,7 @@ class rssAdminController extends rss { $config->feed_copyright = $feed_copyright; } - + getController('module')->insertModulePartConfig('rss', $module_srl, $config); return new BaseObject(); } diff --git a/modules/rss/rss.admin.view.php b/modules/rss/rss.admin.view.php index 9197f863a..00e29c6d8 100644 --- a/modules/rss/rss.admin.view.php +++ b/modules/rss/rss.admin.view.php @@ -5,37 +5,49 @@ * * @author NAVER (developers@xpressengine.com) */ -class rssAdminView extends rss +class RssAdminView extends Rss { function init() { Context::set('config', getModel('rss')->getConfig()); - + $this->setTemplatePath($this->module_path . 'tpl'); } - + function dispRssAdminIndex() { $oRssModel = getModel('rss'); - $oModuleModel = getModel('module'); - + $rss_list = array(); - foreach($oModuleModel->getModulePartConfigs('rss') as $module_srl => $module_config) + foreach (ModuleModel::getMidList((object)['module' => 'board']) as $module_info) { - $module_info = $oModuleModel->getModuleInfoByModuleSrl($module_srl); - $args = new stdClass; $args->mid = $module_info->mid; $args->url = $oRssModel->getRssURL('rss', $module_info->mid); + $args->open_feed = 'N'; + $args->open_total_feed = 'N'; + $args->feed_description = ''; + + $rss_list[$module_info->module_srl] = $args; + } + + foreach (ModuleModel::getModulePartConfigs('rss') as $module_srl => $module_config) + { + $module_info = ModuleModel::getModuleInfoByModuleSrl($module_srl); + + $args = new stdClass; + $args->browser_title = $module_info->browser_title; + $args->mid = $module_info->mid; + $args->url = $oRssModel->getRssURL('rss', $module_info->mid); $args->open_feed = $module_config->open_rss; $args->open_total_feed = $module_config->open_total_feed; - $args->feed_description = $module_config->feed_description; - + $args->feed_description = $module_config->feed_description ?? ''; + $rss_list[$module_srl] = $args; } Context::set('rss_list', $rss_list); Context::set('general_rss_url', $oRssModel->getRssURL('rss')); - + $this->setTemplateFile('rss_admin_index'); } } diff --git a/modules/rss/rss.class.php b/modules/rss/rss.class.php index ab464c433..167bd9a41 100644 --- a/modules/rss/rss.class.php +++ b/modules/rss/rss.class.php @@ -5,110 +5,52 @@ * * @author NAVER (developers@xpressengine.com) */ -class rss extends ModuleObject +class Rss extends ModuleObject { // Add forwards protected static $add_forwards = array( array('rss', 'view', 'rss'), array('rss', 'view', 'atom'), ); - - // Add triggers - protected static $add_triggers = array( - array('moduleHandler.proc', 'rss', 'controller', 'triggerRssUrlInsert', 'after'), - array('module.dispAdditionSetup', 'rss', 'view', 'triggerDispRssAdditionSetup', 'before'), - array('module.procModuleAdminCopyModule', 'rss', 'controller', 'triggerCopyModule', 'after'), - ); - - // Remove triggers - protected static $remove_triggers = array( - array('display', 'rss', 'controller', 'triggerRssUrlInsert', 'before'), - ); - + /** * Install */ - function moduleInstall() + public function moduleInstall() { $this->moduleUpdate(); } - + /** * Check update */ - function checkUpdate() + public function checkUpdate() { - $oModuleModel = getModel('module'); - // Check forwards for add foreach(self::$add_forwards as $forward) { - if(!$oModuleModel->getActionForward($forward[2])) + if(!ModuleModel::getActionForward($forward[2])) { return true; } } - - // Check triggers for add - foreach(self::$add_triggers as $trigger) - { - if(!$oModuleModel->getTrigger($trigger[0], $trigger[1], $trigger[2], $trigger[3], $trigger[4])) - { - return true; - } - } - - // Check triggers for remove - foreach(self::$remove_triggers as $trigger) - { - if($oModuleModel->getTrigger($trigger[0], $trigger[1], $trigger[2], $trigger[3], $trigger[4])) - { - return true; - } - } - + return false; } - + /** * Update */ - function moduleUpdate() + public function moduleUpdate() { - $oModuleModel = getModel('module'); - $oModuleController = getController('module'); - // Add forwards foreach(self::$add_forwards as $forward) { - if(!$oModuleModel->getActionForward($forward[2])) + if(!ModuleModel::getActionForward($forward[2])) { - $oModuleController->insertActionForward($forward[0], $forward[1], $forward[2]); + ModuleController::getInstance()->insertActionForward($forward[0], $forward[1], $forward[2]); } } - - // Add triggers - foreach(self::$add_triggers as $trigger) - { - if(!$oModuleModel->getTrigger($trigger[0], $trigger[1], $trigger[2], $trigger[3], $trigger[4])) - { - $oModuleController->insertTrigger($trigger[0], $trigger[1], $trigger[2], $trigger[3], $trigger[4]); - } - } - - // Remove triggers - foreach(self::$remove_triggers as $trigger) - { - if($oModuleModel->getTrigger($trigger[0], $trigger[1], $trigger[2], $trigger[3], $trigger[4])) - { - $oModuleController->deleteTrigger($trigger[0], $trigger[1], $trigger[2], $trigger[3], $trigger[4]); - } - } - } - - function recompileCache() - { - } } /* End of file rss.class.php */ diff --git a/modules/rss/rss.controller.php b/modules/rss/rss.controller.php index 152e2ed77..aa04a078d 100644 --- a/modules/rss/rss.controller.php +++ b/modules/rss/rss.controller.php @@ -5,49 +5,49 @@ * * @author NAVER (developers@xpressengine.com) */ -class rssController extends rss +class RssController extends Rss { function init() { } - + /** * Set RSS URL */ function triggerRssUrlInsert($obj) { - if(!$current_module_srl = Context::get('current_module_info')->module_srl) + $current_module_srl = Context::get('current_module_info')->module_srl ?? null; + if (!$current_module_srl) { return; } - - $oRssModel = getModel('rss'); - $config = $oRssModel->getConfig(); - $module_config = $oRssModel->getRssModuleConfig($current_module_srl); - + + $config = rssModel::getConfig(); + $module_config = rssModel::getRssModuleConfig($current_module_srl); + if($config->use_total_feed != 'N' && Context::get('site_module_info')->mid == Context::get('mid')) { - Context::set('general_rss_url', $oRssModel->getRssURL('rss')); - Context::set('general_atom_url', $oRssModel->getRssURL('atom')); + Context::set('general_rss_url', rssModel::getRssUrl('rss')); + Context::set('general_atom_url', rssModel::getRssUrl('atom')); } - + if($module_config->open_rss != 'N') { - Context::set('rss_url', $oRssModel->getRssURL('rss', Context::get('mid'))); - Context::set('atom_url', $oRssModel->getRssURL('atom', Context::get('mid'))); + Context::set('rss_url', rssModel::getRssUrl('rss', Context::get('mid'))); + Context::set('atom_url', rssModel::getRssUrl('atom', Context::get('mid'))); } } - + /** * Copy RSS configuration */ function triggerCopyModule(&$obj) { - $module_config = getModel('rss')->getRssModuleConfig($obj->originModuleSrl); - + $module_config = rssModel::getRssModuleConfig($obj->originModuleSrl); + foreach($obj->moduleSrlList as $module_srl) { - getController('module')->insertModulePartConfig('rss', $module_srl, $module_config); + ModuleController::getInstance()->insertModulePartConfig('rss', $module_srl, $module_config); } } } diff --git a/modules/rss/rss.model.php b/modules/rss/rss.model.php index 2545a0f03..ddff34bf8 100644 --- a/modules/rss/rss.model.php +++ b/modules/rss/rss.model.php @@ -5,37 +5,40 @@ * * @author NAVER (developers@xpressengine.com) */ -class rssModel extends rss +class RssModel extends Rss { - function getRssURL($format = 'rss', $mid = '') + public static function getRssURL($format = 'rss', $mid = '') { return getFullUrl('', 'mid', $mid, 'act', $format); } - - function getConfig() + + public static function getConfig() { - $config = getModel('module')->getModuleConfig('rss') ?: new stdClass; - $config->use_total_feed = $config->use_total_feed ?: 'Y'; - $config->feed_document_count = $config->feed_document_count ?: 15; - $config->image_url = $config->image . '?' . date('YmdHis', filemtime($config->image)); - + $config = ModuleModel::getModuleConfig('rss') ?: new stdClass; + $config->use_total_feed = $config->use_total_feed ?? 'Y'; + $config->feed_document_count = intval($config->feed_document_count ?? 15) ?: 15; + if (isset($config->image) && $config->image) + { + $config->image_url = $config->image . '?t=' . filemtime($config->image); + } + return $config; } - - function getRssModuleConfig($module_srl) + + public static function getRssModuleConfig($module_srl) { - $config = getModel('module')->getModulePartConfig('rss', $module_srl) ?: new stdClass; + $config = ModuleModel::getModulePartConfig('rss', $module_srl) ?: new stdClass; $config->module_srl = $module_srl; - $config->open_rss = $config->open_rss ?: 'N'; - $config->open_total_feed = $config->open_total_feed ?: 'N'; - + $config->open_rss = $config->open_rss ?? 'N'; + $config->open_total_feed = $config->open_total_feed ?? 'N'; + return $config; } - + /** * Compatible function */ - function getModuleFeedUrl($vid, $mid, $format = 'rss', $absolute_url = false) + public static function getModuleFeedUrl($vid, $mid, $format = 'rss', $absolute_url = false) { if($absolute_url) { diff --git a/modules/rss/rss.view.php b/modules/rss/rss.view.php index 18599d652..143c020c1 100644 --- a/modules/rss/rss.view.php +++ b/modules/rss/rss.view.php @@ -5,15 +5,15 @@ * * @author NAVER (developers@xpressengine.com) */ -class rssView extends rss +class RssView extends Rss { // Disable gzhandler public $gzhandler_enable = false; - + function init() { } - + function rss($document_list = null, $rss_title = null, $add_description = null) { $obj = new stdClass; @@ -22,12 +22,12 @@ class rssView extends rss $obj->document_list = $document_list; $this->output(Context::get('format'), $obj); } - + function atom() { $this->output('atom'); } - + function dispError($module_srl = null) { $obj = new stdClass; @@ -36,7 +36,7 @@ class rssView extends rss $obj->description = lang('msg_rss_is_disabled'); $this->output(Context::get('format'), $obj); } - + /** * Feed output */ @@ -46,7 +46,7 @@ class rssView extends rss { $obj = new stdClass; } - + $act = Context::get('act'); $page = $obj->page ?: Context::get('page'); $start = $obj->start_date ?: Context::get('start_date'); @@ -55,7 +55,7 @@ class rssView extends rss $current_module_srl = Context::get('current_module_info')->module_srl; $target_module_srl = isset($obj->module_srl) ? $obj->module_srl : ($current_module_srl ?: $site_module_srl); $is_part_feed = (isset($obj->module_srl) || $target_module_srl !== $site_module_srl) ? true : false; - + // Set format switch($format) { @@ -76,17 +76,16 @@ class rssView extends rss $template = 'rss20'; break; } - - $oRssModel = getModel('rss'); - $config = $oRssModel->getConfig(); - $module_config = $oRssModel->getRssModuleConfig($target_module_srl); - $module_info = getModel('module')->getModuleInfoByModuleSrl($target_module_srl); - + + $config = rssModel::getConfig(); + $module_config = rssModel::getRssModuleConfig($target_module_srl); + $module_info = ModuleModel::getModuleInfoByModuleSrl($target_module_srl); + // Get URL $format = ($act != $format) ? $format : ''; $mid = $is_part_feed ? $module_info->mid : ''; $channel_url = getFullUrl('', 'mid', $mid, 'act', $act, 'format', $format, 'page', $page, 'start_date', $start, 'end_date', $end); - + // Check error if($obj->error) { @@ -100,7 +99,7 @@ class rssView extends rss { return $this->dispError(); } - + // Set target module $target_modules = array(); if($is_part_feed) @@ -113,7 +112,7 @@ class rssView extends rss // total feed elseif($config->use_total_feed == 'Y') { - foreach(getModel('module')->getModulePartConfigs('rss') as $module_srl => $part_config) + foreach(ModuleModel::getModulePartConfigs('rss') as $module_srl => $part_config) { if($part_config->open_rss == 'N' || $part_config->open_total_feed == 'T_N') { @@ -123,7 +122,7 @@ class rssView extends rss } } Context::set('target_modules', $target_modules); - + // Set document list $document_list = $obj->document_list; if(!is_array($document_list)) @@ -132,30 +131,31 @@ class rssView extends rss { return $this->dispError($module_info->module_srl); } - + $args = new stdClass; $args->start_date = $start; $args->end_date = $end; $args->search_target = 'is_secret'; $args->search_keyword = 'N'; - $args->page = $page > 0 ? $page : 1; $args->module_srl = array_keys($target_modules); - $args->list_count = $config->feed_document_count; + $args->list_count = $config->feed_document_count > 0 ? $config->feed_document_count : 20; + $args->offset = ($page > 1) ? ($args->list_count * ($page - 1)) : 0; + $args->page = 0; $args->sort_index = 'regdate'; $args->order_type = 'desc'; - $document_list = getModel('document')->getDocumentList($args)->data; + $document_list = DocumentModel::getDocumentList($args)->data; } Context::set('document_list', $document_list); - + // Set category list $category_list = array(); foreach($target_modules as $module_srl => $open_rss) { - $category_list[$module_srl] = getModel('document')->getCategoryList($module_srl); + $category_list[$module_srl] = DocumentModel::getCategoryList($module_srl); } Context::set('category_list', $category_list); } - + // Set feed information $info = new stdClass; if($is_part_feed) @@ -172,26 +172,25 @@ class rssView extends rss $info->description = $config->feed_description; $info->feed_copyright = $config->feed_copyright; } - + $info->id = $channel_url; $info->feed_title = $config->feed_title; - $info->title = $obj->title ?: $info->title; + $info->title = Context::replaceUserLang($obj->title ?: $info->title); $info->description = $obj->description ?: $info->description; $info->language = Context::getLangType(); $info->site_url = Context::getRequestUri(); $info->date_r = date('r'); $info->date_c = date('c'); $info->image = $config->image ? Context::getRequestUri() . $config->image : ''; - getController('module')->replaceDefinedLangCode($info->title); - + Context::set('info', $info); - + // Set XML Output - Context::setResponseMethod('XMLRPC'); + Context::setResponseMethod('RAW', 'text/xml'); $this->setTemplatePath($this->module_path . 'tpl/format'); $this->setTemplateFile($template); } - + /** * Additional configurations for a service module */ @@ -204,10 +203,10 @@ class rssView extends rss return; } } - + // Get part configuration - Context::set('module_config', getModel('rss')->getRssModuleConfig($current_module_srl)); - + Context::set('module_config', rssModel::getRssModuleConfig($current_module_srl)); + // Add output after compile template $output .= TemplateHandler::getInstance()->compile($this->module_path . 'tpl', 'rss_module_config'); } diff --git a/modules/rss/tpl/format/atom10.html b/modules/rss/tpl/format/atom10.html index 71109c9d4..76b75701f 100644 --- a/modules/rss/tpl/format/atom10.html +++ b/modules/rss/tpl/format/atom10.html @@ -1,4 +1,4 @@ -{''} +'; ?> @@ -21,8 +21,8 @@ {$oDocument->getNickName()} {$oDocument->getSummary(400)} - {utf8_trim(utf8_normalize_spaces($oDocument->get('content')))|escape} - + {\Rhymix\Framework\Filters\HTMLFilter::fixRelativeUrls(utf8_trim(utf8_normalize_spaces($oDocument->get('content'))))|escape} + - + diff --git a/modules/rss/tpl/format/rss10.html b/modules/rss/tpl/format/rss10.html index e2958947d..d829d1bd9 100644 --- a/modules/rss/tpl/format/rss10.html +++ b/modules/rss/tpl/format/rss10.html @@ -1,4 +1,4 @@ -{''} +'; ?> @@ -28,7 +28,7 @@ {$oDocument->getTitleText()} {$oDocument->getPermanentUrl()} - {$oDocument->getSummary(400)|escape} + {$oDocument->getSummary(400)} {$oDocument->getNickName()} {date('c', ztime($oDocument->get('regdate')))} diff --git a/modules/rss/tpl/format/rss20.html b/modules/rss/tpl/format/rss20.html index 084d9bd71..b88e42a09 100644 --- a/modules/rss/tpl/format/rss20.html +++ b/modules/rss/tpl/format/rss20.html @@ -1,4 +1,4 @@ -{''} +'; ?> @@ -20,12 +20,12 @@ {$oDocument->getTitleText()} {$oDocument->getPermanentUrl()} - {utf8_trim(utf8_normalize_spaces($oDocument->get('content')))|escape} + {\Rhymix\Framework\Filters\HTMLFilter::fixRelativeUrls(utf8_trim(utf8_normalize_spaces($oDocument->get('content'))))|escape} - {$oDocument->getSummary(400)|escape} + {$oDocument->getSummary(400)} - {$oDocument->getModuleName()} - {$category_name} + {Context::replaceUserLang($oDocument->getModuleName())} + {Context::replaceUserLang($category_name)} {$tag} {$oDocument->getNickName()} {$oDocument->getPermanentUrl()} diff --git a/modules/rss/tpl/format/xe.html b/modules/rss/tpl/format/xe.html index bdb8b485f..6af2c6476 100644 --- a/modules/rss/tpl/format/xe.html +++ b/modules/rss/tpl/format/xe.html @@ -1,4 +1,4 @@ -{''} +'; ?> @@ -15,7 +15,7 @@ {$oDocument->getNickName()} {$oDocument->getPermanentUrl()} - {utf8_trim(utf8_normalize_spaces($oDocument->get('content')))|escape} + {\Rhymix\Framework\Filters\HTMLFilter::fixRelativeUrls(utf8_trim(utf8_normalize_spaces($oDocument->get('content'))))|escape} {$oDocument->getSummary(400)|escape} diff --git a/modules/rss/tpl/rss_admin_index.html b/modules/rss/tpl/rss_admin_index.html index e661b8a07..4d5d8e9f8 100644 --- a/modules/rss/tpl/rss_admin_index.html +++ b/modules/rss/tpl/rss_admin_index.html @@ -28,23 +28,23 @@
                - +
                - +

                {$lang->about_feed_description}

                -
                -
                +
                +
                image - +

                @@ -53,13 +53,13 @@
                - +
                - +
                @@ -68,12 +68,12 @@
                -

                {$lang->feed} {$lang->cmd_management}

                +

                {$lang->module_feed_management}

                - + @@ -86,7 +86,8 @@
                - {$module_config->mid} + {$module_config->browser_title} +
                {$module_config->mid}
                @@ -110,7 +111,7 @@
                - +
                diff --git a/modules/rss/tpl/rss_module_config.html b/modules/rss/tpl/rss_module_config.html index 48a9a0163..f23fa5c11 100644 --- a/modules/rss/tpl/rss_module_config.html +++ b/modules/rss/tpl/rss_module_config.html @@ -1,13 +1,13 @@

                {$lang->open_rss}

                {$lang->about_open_rss}

                - + - +
                @@ -28,14 +28,14 @@
                - +

                {$lang->about_feed_description}

                - +

                {$lang->about_feed_copyright}

                diff --git a/modules/session/conf/info.xml b/modules/session/conf/info.xml index b04f5ca09..a1857f0d2 100644 --- a/modules/session/conf/info.xml +++ b/modules/session/conf/info.xml @@ -1,6 +1,6 @@ - 세션 관리자 + 세션 관리 セッション管理 Session Session @@ -35,8 +35,8 @@ 管理線上會員SESSION功能的模組。 提供最基本的SESSION設置和使用,且還可以獲得此功能的線上會員資料。 - 1.7 - 2013-11-27 + RX_VERSION + RX_CORE system diff --git a/modules/session/queries/getExpiredSessions.xml b/modules/session/queries/getExpiredSessions.xml index 3f79ba41b..616383a4e 100644 --- a/modules/session/queries/getExpiredSessions.xml +++ b/modules/session/queries/getExpiredSessions.xml @@ -1,4 +1,4 @@ - + diff --git a/modules/session/schemas/session.xml b/modules/session/schemas/session.xml index 43f18af9d..c7c36c159 100644 --- a/modules/session/schemas/session.xml +++ b/modules/session/schemas/session.xml @@ -1,9 +1,9 @@
                - - - - - - - + + + + + + +
                diff --git a/modules/session/session.admin.controller.php b/modules/session/session.admin.controller.php index 5dbc28e92..e078f660c 100644 --- a/modules/session/session.admin.controller.php +++ b/modules/session/session.admin.controller.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * @brief The admin controller class of the session module */ -class sessionAdminController extends session +class SessionAdminController extends Session { /** * @brief Initialization diff --git a/modules/session/session.admin.view.php b/modules/session/session.admin.view.php index d1d4098b2..7c6e5e9d7 100644 --- a/modules/session/session.admin.view.php +++ b/modules/session/session.admin.view.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * @brief The admin view class of the session module */ -class sessionAdminView extends session +class SessionAdminView extends Session { /** * @brief Initialization diff --git a/modules/session/session.class.php b/modules/session/session.class.php index e0ba46515..9b8ffac27 100644 --- a/modules/session/session.class.php +++ b/modules/session/session.class.php @@ -8,7 +8,7 @@ * * The session management class */ -class session extends ModuleObject +class Session extends ModuleObject { var $lifetime = 18000; var $session_started = false; @@ -16,6 +16,7 @@ class session extends ModuleObject function __construct() { if(Context::isInstalled()) $this->session_started= true; + parent::__construct(); } /** @@ -23,7 +24,7 @@ class session extends ModuleObject */ function moduleInstall() { - $oDB = &DB::getInstance(); + $oDB = DB::getInstance(); $oDB->addIndex("session","idx_session_update_mid", array("member_srl","last_update","cur_mid")); } @@ -32,9 +33,7 @@ class session extends ModuleObject */ function checkUpdate() { - $oDB = &DB::getInstance(); - if(!$oDB->isTableExists('session')) return true; - if(!$oDB->isColumnExists("session","cur_mid")) return true; + $oDB = DB::getInstance(); if(!$oDB->isIndexExists("session","idx_session_update_mid")) return true; return false; } @@ -44,14 +43,11 @@ class session extends ModuleObject */ function moduleUpdate() { - $oDB = &DB::getInstance(); - $oModuleModel = getModel('module'); - - if(!$oDB->isTableExists('session')) $oDB->createTableByXmlFile($this->module_path.'schemas/session.xml'); - - if(!$oDB->isColumnExists("session","cur_mid")) $oDB->addColumn('session',"cur_mid","varchar",128); - - if(!$oDB->isIndexExists("session","idx_session_update_mid")) $oDB->addIndex("session","idx_session_update_mid", array("member_srl","last_update","cur_mid")); + $oDB = DB::getInstance(); + if(!$oDB->isIndexExists("session","idx_session_update_mid")) + { + $oDB->addIndex("session","idx_session_update_mid", array("member_srl","last_update","cur_mid")); + } } /** diff --git a/modules/session/session.controller.php b/modules/session/session.controller.php index f77638e27..5335a1757 100644 --- a/modules/session/session.controller.php +++ b/modules/session/session.controller.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * @brief The controller class of the session module */ -class sessionController extends session +class SessionController extends Session { /** * @brief Initialization @@ -34,15 +34,7 @@ class sessionController extends session $output = executeQuery('session.getSession', $args); $session_info = $output->data; - //if ip has changed delete the session from db - if($session_info->session_key == $session_key && $session_info->ipaddress != $_SERVER['REMOTE_ADDR']) - { - executeQuery('session.deleteSession', $args); - - return true; - } - - $args->expired = date("YmdHis", $_SERVER['REQUEST_TIME'] + $this->lifetime); + $args->expired = date("YmdHis", time() + $this->lifetime); $args->val = $val; $args->cur_mid = Context::get('mid'); @@ -61,8 +53,8 @@ class sessionController extends session { $args->member_srl = 0; } - $args->ipaddress = $_SERVER['REMOTE_ADDR']; - $args->last_update = date("YmdHis", $_SERVER['REQUEST_TIME']); + $args->ipaddress = \RX_CLIENT_IP; + $args->last_update = date('YmdHis'); //put session into db if($session_info->session_key) diff --git a/modules/session/session.model.php b/modules/session/session.model.php index 0b6056fe5..b9ef8583b 100644 --- a/modules/session/session.model.php +++ b/modules/session/session.model.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * @brief The Model class of the session module */ -class sessionModel extends session +class SessionModel extends Session { /** * @brief Initialization diff --git a/modules/session/tpl/js/session.js b/modules/session/tpl/js/session.js index f9793ad28..027872f17 100644 --- a/modules/session/tpl/js/session.js +++ b/modules/session/tpl/js/session.js @@ -1,10 +1,6 @@ function doClearSession() { if (!confirm(xe.lang.confirm_run)) return; - var response_tags = new Array('error','message','result'); - var params = new Array(); - exec_xml('session','procSessionAdminClear', params, completeClearSession, response_tags); -} - -function completeClearSession(ret_obj, response_tags) { - alert(ret_obj['result']); + exec_json('session.procSessionAdminClear', {}, function(data) { + alert(data.result); + }); } diff --git a/modules/spamfilter/captcha/recaptcha.php b/modules/spamfilter/captcha/recaptcha.php new file mode 100644 index 000000000..f53c2797d --- /dev/null +++ b/modules/spamfilter/captcha/recaptcha.php @@ -0,0 +1,84 @@ + self::$config->secret_key, + 'response' => $response, + 'remoteip' => \RX_CLIENT_IP, + ], [], [], ['timeout' => 10]); + if ($verify_request->getStatusCode() !== 200 || !$verify_request->getBody()) + { + throw new Exception('msg_recaptcha_connection_error'); + } + + $verify = @json_decode($verify_request->getBody(), true); + if (!$verify || !$verify['success']) + { + throw new Exception('msg_recaptcha_server_error'); + } + if ($verify && isset($verify['error-codes']) && in_array('invalid-input-response', $verify['error-codes'])) + { + throw new Exception('msg_recaptcha_invalid_response'); + } + + $_SESSION['recaptcha_authenticated'] = true; + } + + public function addScripts() + { + if (!self::$scripts_added) + { + self::$scripts_added = true; + Context::loadFile(array('./modules/spamfilter/tpl/js/recaptcha.js', 'body')); + Context::addHtmlFooter(''); + $html = '
                '; + $html = sprintf($html, escape(self::$config->site_key), self::$config->theme ?: 'auto', self::$config->size ?: 'normal', implode(',', array_keys($this->_target_actions))); + Context::addHtmlFooter($html); + } + } + + public function setTargetActions(array $target_actions) + { + $this->_target_actions = $target_actions; + } + + public function isTargetAction(string $action): bool + { + return isset($this->_target_actions[$action]); + } + + public function __toString() + { + return sprintf('
                ', self::$instances_inserted++); + } +} diff --git a/modules/spamfilter/captcha/turnstile.php b/modules/spamfilter/captcha/turnstile.php new file mode 100644 index 000000000..e9161c3b7 --- /dev/null +++ b/modules/spamfilter/captcha/turnstile.php @@ -0,0 +1,90 @@ + self::$config->secret_key, + 'response' => $response, + 'remoteip' => \RX_CLIENT_IP, + ], [], [], ['timeout' => 10]); + if ($verify_request->getStatusCode() !== 200 || !$verify_request->getBody()) + { + throw new Exception('msg_recaptcha_connection_error'); + } + + $verify = @json_decode($verify_request->getBody(), true); + if (!$verify || !$verify['success']) + { + throw new Exception('msg_recaptcha_server_error'); + } + if ($verify && isset($verify['error-codes']) && in_array('invalid-input-response', $verify['error-codes'])) + { + throw new Exception('msg_recaptcha_invalid_response'); + } + + $_SESSION['recaptcha_authenticated'] = true; + } + + public function addScripts() + { + if (!self::$scripts_added) + { + self::$scripts_added = true; + Context::loadFile(array('./modules/spamfilter/tpl/js/turnstile.js', 'body')); + Context::addHtmlFooter(''); + $html = '
                '; + $html = sprintf($html, escape(self::$config->site_key), self::$config->theme ?: 'auto', self::$config->size ?: 'normal', implode(',', array_keys($this->_target_actions))); + Context::addHtmlFooter($html); + } + } + + public function setTargetActions(array $target_actions) + { + $this->_target_actions = $target_actions; + } + + public function isTargetAction(string $action): bool + { + return isset($this->_target_actions[$action]); + } + + public function __toString() + { + return sprintf('
                ', self::$instances_inserted++); + } +} diff --git a/modules/spamfilter/conf/info.xml b/modules/spamfilter/conf/info.xml index 74f416808..50ad9fbcd 100644 --- a/modules/spamfilter/conf/info.xml +++ b/modules/spamfilter/conf/info.xml @@ -9,17 +9,17 @@ Фильтр спама Spam Filtreleyici 垃圾過濾 - XE의 기본 스팸필터입니다. - XE的基本垃圾过滤模块。 - The default spam filter of XE. - Bộ lọc Spam mặc định của XE. - Filtro de Span predefinido de XE. - XEのスパムフィルターです。 - Стандартный фильтр спама XE. - XE的基本垃圾過濾模組。 - XE\'nin varsayılan spam filtreleyicisidir. - 1.7 - 2013-11-27 + 기본 스팸필터 모듈입니다. + Rhymix的基本垃圾过滤模块。 + The default spam filter module. + Bộ lọc Spam mặc định của Rhymix. + Filtro de Span predefinido de Rhymix. + スパムフィルターです。 + Стандартный фильтр спама Rhymix. + Rhymix的基本垃圾過濾模組。 + Rhymix\'nin varsayılan spam filtreleyicisidir. + RX_VERSION + RX_CORE accessory diff --git a/modules/spamfilter/conf/module.xml b/modules/spamfilter/conf/module.xml index 99322a982..8bef3c6f9 100644 --- a/modules/spamfilter/conf/module.xml +++ b/modules/spamfilter/conf/module.xml @@ -5,13 +5,32 @@ - + + + + + - + + + + + + + + + + + + + + + + Spam Filter diff --git a/modules/spamfilter/lang/en.php b/modules/spamfilter/lang/en.php index 3525a7188..53d905804 100644 --- a/modules/spamfilter/lang/en.php +++ b/modules/spamfilter/lang/en.php @@ -2,6 +2,8 @@ $lang->cmd_denied_ip = 'IP Address Blacklist'; $lang->cmd_denied_word = 'Keyword Blacklist'; $lang->cmd_config_block = 'Automatic Blocking'; +$lang->cmd_captcha_config = 'CAPTCHA Settings'; +$lang->cmd_captcha_test = 'CAPTCHA Test'; $lang->add_denied_ip = 'Add IP address or range'; $lang->add_denied_word = 'Add keyword'; $lang->spamfilter = 'Spam Filter'; @@ -13,18 +15,23 @@ $lang->word = 'Keyword'; $lang->hit = 'Hit'; $lang->latest_hit = 'Latest Hits'; $lang->custom_message = 'Error Message'; +$lang->enable_description = 'Enter # as description'; $lang->about_custom_message = 'You can customize the error message that will be displayed if a spam keyword is found.
                %s can be used as a placeholder for the keyword. If not used, the keyword will be hidden.'; $lang->about_interval = 'All articles attempted for posting within the assigned time will be blocked.'; -$lang->about_denied_ip = 'Please enter one IP address (e.g. 127.0.0.1) or range (e.g. 127.0.0.0/24) per line. Comments may start with //.'; -$lang->about_denied_word = 'Please enter one keyword per line. Keywords may contain 2 to 180 characters.
                Formats such as /spam(key|word)?/ will be treated as a regular expression, and must use the proper syntax.
                Spam keywords are not case sensitive.'; -$lang->msg_denied_word_is_regexp = 'REGEXP'; +$lang->about_denied_ip = 'Please enter one IP address (e.g. 127.0.0.1) or range (e.g. 127.0.0.0/24) per line. Comments may start with // or #.'; +$lang->about_denied_word = 'Please enter one keyword (2 to 180 characters) per line. Comments start with #. If you need to block a keyword that includes #, disable the checkbox above.
                Formats such as /spam(key|word)?/ will be treated as a regular expression, and must use the proper syntax.
                Spam keywords are not case sensitive.'; $lang->msg_alert_limited_by_config = 'Please do not post repeatedly within %d seconds. If you keep trying, your IP address will be blocked.'; $lang->msg_alert_limited_message_by_config = 'Please do not send messages repeatedly within %d seconds. If you keep trying, your IP address will be blocked.'; $lang->msg_alert_denied_word = 'The word "%s" is not allowed on this site.'; $lang->msg_alert_registered_denied_ip = 'Your IP address has been blocked for abuse. Please contact the administrator.'; $lang->msg_alert_trackback_denied = 'Only one trackback per an article is allowed.'; +$lang->cmd_spamfilter_except_member = 'Except Members'; +$lang->cmd_spamfilter_filter_html = 'HTML'; +$lang->cmd_spamfilter_is_regexp = 'REGEXP'; $lang->cmd_interval = 'Block Post/Comment Spam'; -$lang->cmd_interval_help = 'Block IP addresses that post or comment too much in a short time. Blocked IP addresses will not be able to post, comment, or send messages.'; +$lang->cmd_interval_help = 'Block IP addresses that post or comment too much in a short time.'; +$lang->cmd_blocked_actions = 'Blocked actions'; +$lang->cmd_blocked_actions_help = 'The actions above will be disabled from blocked IP addresses.'; $lang->cmd_check_trackback = 'Block Trackback Spam'; $lang->cmd_check_trackback_help = 'Block IP addresses that send multiple trackbacks to the same document.
                This only works if the trackback module is installed.'; $lang->cmd_limits_interval = 'Block Interval'; @@ -36,9 +43,36 @@ $lang->cmd_ipv6_block_range = 'IPv6 Block Range'; $lang->cmd_block_range_self = 'single IP address only'; $lang->cmd_block_range_help = 'This option allows you to block an entire range of IP addresses when a spammer is found.
                Caution: if you block an excessively wide range, you may also end up blocking innocent users.'; $lang->cmd_block_range = 'IP addresses with the same %d last blocks'; +$lang->cmd_spamfilter_except_ip = 'Whitelist IP'; +$lang->cmd_spamfilter_except_ip_help = 'IPs or IP ranges entered here will not be blocked.
                e.g. 123.45.67.89, 123.45.67.0/24, 123.45.67.*'; $lang->unit_write_count = 'times'; $lang->add = 'Add'; $lang->msg_duplicate = 'Duplicate'; $lang->msg_invalid_ip = 'Invalid IP address format.'; $lang->msg_invalid_word = 'Spam keywords must be between 2 and 180 characters.'; $lang->msg_faillist = '
                Error (already blocked)
                %s '; +$lang->use_captcha = 'Use CAPTCHA'; +$lang->about_captcha_position = 'The skin file for your login form, write form, etc. should indicate the CAPTCHA position with the following code: {$captcha}
                The CAPTCHA may be inserted in an unexpected position if the form does not contain the code.'; +$lang->recaptcha_theme = 'Color Theme'; +$lang->recaptcha_theme_auto = 'Auto'; +$lang->recaptcha_theme_light = 'Light'; +$lang->recaptcha_theme_dark = 'Dark'; +$lang->recaptcha_size = 'Display Size'; +$lang->recaptcha_size_normal = 'Normal'; +$lang->recaptcha_size_compact = 'Compact'; +$lang->recaptcha_target_devices = 'Target Devices'; +$lang->recaptcha_target_actions = 'Target Actions'; +$lang->recaptcha_target_document = 'Document Post'; +$lang->recaptcha_target_comment = 'Comment Post'; +$lang->recaptcha_target_users = 'Target Users'; +$lang->recaptcha_target_guest = 'Guests Only'; +$lang->recaptcha_target_everyone = 'Everyone'; +$lang->recaptcha_target_frequency = 'Frequency'; +$lang->recaptcha_target_first_time_only = 'First Time Only'; +$lang->recaptcha_target_every_time = 'Every Time'; +$lang->msg_recaptcha_not_configured = 'Your CAPTCHA is not properly configured.'; +$lang->msg_recaptcha_connection_error = 'An error occurred while connecting to the CAPTCHA verification server.'; +$lang->msg_recaptcha_server_error = 'An error occurred while verifying your CAPTCHA response.'; +$lang->msg_recaptcha_invalid_response = 'Please check the CAPTCHA.'; +$lang->msg_recaptcha_keys_not_set = 'Please fill in your CAPTCHA Site Key and Secret Key.'; +$lang->msg_recaptcha_test_success = 'You passed the CAPTCHA test successfully.'; diff --git a/modules/spamfilter/lang/ja.php b/modules/spamfilter/lang/ja.php index 6b42e6cf0..d8c9ff08e 100644 --- a/modules/spamfilter/lang/ja.php +++ b/modules/spamfilter/lang/ja.php @@ -26,7 +26,6 @@ $lang->cmd_interval = '10秒の間3回以上書き込みをすると、スパム $lang->cmd_check_trackback = 'ひとつの書き込みに2回以上トラックバックを登録するとスパムとみなしますか?トラックバックをブロックします。'; $lang->add = '追加'; $lang->yes = 'はい'; -$lang->no = 'いいえ'; $lang->msg_duplicate = '既に存在します。'; $lang->msg_invalid_ip = 'IPアドレスの形式が正しくありません。'; $lang->msg_invalid_word = 'スパムキーワードは2〜180文字の範囲で指定します。'; diff --git a/modules/spamfilter/lang/ko.php b/modules/spamfilter/lang/ko.php index 7da7e63e9..a7f16d0ae 100644 --- a/modules/spamfilter/lang/ko.php +++ b/modules/spamfilter/lang/ko.php @@ -2,6 +2,8 @@ $lang->cmd_denied_ip = '스팸 IP 목록'; $lang->cmd_denied_word = '스팸 키워드 목록'; $lang->cmd_config_block = '자동 차단 설정'; +$lang->cmd_captcha_config = '캡챠 설정'; +$lang->cmd_captcha_test = '캡챠 테스트'; $lang->add_denied_ip = '스팸 IP 추가'; $lang->add_denied_word = '스팸 키워드 추가'; $lang->spamfilter = '스팸필터'; @@ -13,18 +15,23 @@ $lang->word = '키워드'; $lang->hit = '히트'; $lang->latest_hit = '최근 히트'; $lang->custom_message = '차단 메시지 설정'; +$lang->enable_description = '# 뒷부분은 설명으로 입력'; $lang->about_custom_message = '스팸 키워드 발견시 표시할 에러 메시지를 지정할 수 있습니다.
                %s를 넣으면 그 자리에 해당 키워드를 표시하고, 그렇지 않으면 키워드를 숨깁니다.'; $lang->about_interval = '지정된 시간 내에 글을 등록하지 못하게 합니다.'; -$lang->about_denied_ip = '한 줄에 하나씩 IP 주소 또는 대역을 입력하세요. "//" 또는 "#" 뒷부분은 설명으로 저장됩니다. 예: 127.0.0.1 //설명, 127.0.0.1 #설명
                IP 대역 표기법은 매뉴얼을 참고하십시오.'; -$lang->about_denied_word = '한 줄에 하나씩 스팸 키워드를 입력하세요. (2~180자)
                /스팸(키+|워드)?/ 와 같은 형태로 입력하면 정규식으로 간주하며, 올바른 정규식 문법을 사용해야 합니다.
                대소문자는 구분하지 않습니다.'; -$lang->msg_denied_word_is_regexp = '정규식'; +$lang->about_denied_ip = '한 줄에 하나씩 IP 주소 또는 대역을 입력하세요. "//" 또는 "#" 뒷부분은 설명으로 저장됩니다.
                예: 127.0.0.1 // 설명, 127.0.0.1 #설명
                IP 대역 표기법은 매뉴얼을 참고하십시오.'; +$lang->about_denied_word = '한 줄에 하나씩 스팸 키워드(2~180자)를 입력하세요. "#" 뒷부분은 설명으로 입력됩니다. "#"을 포함하는 키워드를 차단하려면 위의 설정을 해제하세요.
                /스팸(키+|워드)?/ 와 같은 형태로 입력하면 정규식으로 간주하며, 올바른 정규식 문법을 사용해야 합니다.
                대소문자는 구분하지 않습니다.'; $lang->msg_alert_limited_by_config = '%d초 이내에 연속 글 작성은 금지됩니다. 계속 시도하면 IP가 차단될 수 있습니다.'; $lang->msg_alert_limited_message_by_config = '%d초 이내에 연속 쪽지 발송은 금지됩니다. 계속 시도하면 IP가 차단될 수 있습니다.'; $lang->msg_alert_denied_word = '"%s"은(는) 사용이 금지된 단어입니다.'; $lang->msg_alert_registered_denied_ip = 'IP가 차단되었습니다. 사이트 관리자에게 문의 바랍니다.'; $lang->msg_alert_trackback_denied = '한 글에는 하나의 트랙백만 허용됩니다.'; -$lang->cmd_interval = '글, 댓글 스팸 차단'; -$lang->cmd_interval_help = '아래에 지정한 시간 내에 다수의 글이나 댓글을 작성하면 스패머로 간주하고 글, 댓글 작성과 엮인글 발송, 쪽지 발송을 차단합니다.'; +$lang->cmd_spamfilter_except_member = '회원 제외'; +$lang->cmd_spamfilter_filter_html = 'HTML'; +$lang->cmd_spamfilter_is_regexp = '정규식'; +$lang->cmd_interval = '단시간 다수 작성 차단'; +$lang->cmd_interval_help = '지정한 시간 내에 다수의 글이나 댓글을 작성하면 스패머로 간주하고 IP를 차단합니다.'; +$lang->cmd_blocked_actions = '차단할 행동'; +$lang->cmd_blocked_actions_help = '차단된 IP에서는 위의 행동들을 할 수 없게 됩니다.'; $lang->cmd_check_trackback = '트랙백 스팸 차단'; $lang->cmd_check_trackback_help = '하나의 글에 2회 이상 엮인글을 등록하면 스패머로 간주하고 엮인글을 차단합니다.
                트랙백 모듈이 설치되어 있는 경우에만 적용됩니다.'; $lang->cmd_limits_interval = '글, 댓글 제한 시간'; @@ -36,9 +43,36 @@ $lang->cmd_ipv6_block_range = 'IPv6 차단 범위'; $lang->cmd_block_range_self = '해당 IP만 차단'; $lang->cmd_block_range_help = '스패머 발견시 비슷한 대역의 IP를 한꺼번에 차단할 수 있습니다. 숫자가 작을수록 광범위하게 차단됩니다.
                지나치게 광범위하게 차단하면 정상적인 사용자에게 피해가 발생할 수 있으니 주의하시기 바랍니다.'; $lang->cmd_block_range = '마지막 %d자리가 같은 IP를 모두 차단'; +$lang->cmd_spamfilter_except_ip = '예외 IP'; +$lang->cmd_spamfilter_except_ip_help = '차단하지 않을 IP 또는 IP 대역을 한 줄에 하나씩 입력하십시오.
                예: 123.45.67.89, 123.45.67.0/24, 123.45.67.*'; $lang->unit_write_count = '회'; $lang->add = '추가'; $lang->msg_duplicate = '이미 존재합니다.'; $lang->msg_invalid_ip = 'IP 주소 형식이 올바르지 않습니다.'; $lang->msg_invalid_word = '스팸 키워드는 2~180자 사이여야 합니다.'; $lang->msg_faillist = '
                실패 (이미 차단되어 있습니다)
                %s '; +$lang->use_captcha = '캡챠 사용'; +$lang->about_captcha_position = '로그인 폼, 글쓰기 폼 등의 스킨에서 캡챠를 표시할 위치에 {$captcha} 코드가 들어 있어야 합니다.
                코드가 없는 경우 임의의 위치에 캡챠가 삽입되므로 디자인이 틀어질 수 있습니다.'; +$lang->recaptcha_theme = '색상 테마'; +$lang->recaptcha_theme_auto = '자동'; +$lang->recaptcha_theme_light = '밝은 색상'; +$lang->recaptcha_theme_dark = '어두운 색상'; +$lang->recaptcha_size = '캡챠 크기'; +$lang->recaptcha_size_normal = '일반'; +$lang->recaptcha_size_compact = '작게'; +$lang->recaptcha_target_devices = '적용 대상 기기'; +$lang->recaptcha_target_actions = '적용 대상 액션'; +$lang->recaptcha_target_document = '글 쓰기'; +$lang->recaptcha_target_comment = '댓글 쓰기'; +$lang->recaptcha_target_users = '적용 대상 유저'; +$lang->recaptcha_target_guest = '비회원만'; +$lang->recaptcha_target_everyone = '모든 사용자'; +$lang->recaptcha_target_frequency = '캡챠 사용 빈도'; +$lang->recaptcha_target_first_time_only = '최초 1회만 사용'; +$lang->recaptcha_target_every_time = '매번 사용'; +$lang->msg_recaptcha_not_configured = '스팸방지 CAPTCHA가 올바르게 설정되지 않았습니다.'; +$lang->msg_recaptcha_connection_error = '스팸방지 CAPTCHA 서버에 접속하는 도중 오류가 발생했습니다.'; +$lang->msg_recaptcha_server_error = '스팸방지 CAPTCHA 서버와 통신하는 도중 오류가 발생했습니다.'; +$lang->msg_recaptcha_invalid_response = '스팸방지 기능을 체크해 주십시오.'; +$lang->msg_recaptcha_keys_not_set = 'CAPTCHA Site Key 및 Secret Key를 입력하여 주십시오.'; +$lang->msg_recaptcha_test_success = 'CAPTCHA 테스트를 성공적으로 통과했습니다.'; diff --git a/modules/spamfilter/queries/insertDeniedIP.xml b/modules/spamfilter/queries/insertDeniedIP.xml index 700844ae3..7e388a2f1 100644 --- a/modules/spamfilter/queries/insertDeniedIP.xml +++ b/modules/spamfilter/queries/insertDeniedIP.xml @@ -1,10 +1,12 @@ - - - - - - - - + +
                + + + + + + + + diff --git a/modules/spamfilter/queries/insertDeniedWord.xml b/modules/spamfilter/queries/insertDeniedWord.xml index acebcc0b6..86022d71f 100644 --- a/modules/spamfilter/queries/insertDeniedWord.xml +++ b/modules/spamfilter/queries/insertDeniedWord.xml @@ -1,10 +1,14 @@ - - -
                - - - - - - + + +
                + + + + + + + + + + diff --git a/modules/spamfilter/queries/updateDeniedIPAttributes.xml b/modules/spamfilter/queries/updateDeniedIPAttributes.xml new file mode 100644 index 000000000..32076f11a --- /dev/null +++ b/modules/spamfilter/queries/updateDeniedIPAttributes.xml @@ -0,0 +1,11 @@ + + +
                + + + + + + + + diff --git a/modules/spamfilter/queries/updateDeniedIPHit.xml b/modules/spamfilter/queries/updateDeniedIPHit.xml new file mode 100644 index 000000000..cd1771900 --- /dev/null +++ b/modules/spamfilter/queries/updateDeniedIPHit.xml @@ -0,0 +1,14 @@ + + +
                + + + + + + + + + + + \ No newline at end of file diff --git a/modules/spamfilter/queries/updateDeniedWordAttributes.xml b/modules/spamfilter/queries/updateDeniedWordAttributes.xml new file mode 100644 index 000000000..b3ffcf137 --- /dev/null +++ b/modules/spamfilter/queries/updateDeniedWordAttributes.xml @@ -0,0 +1,12 @@ + + +
                + + + + + + + + + diff --git a/modules/spamfilter/ruleset/insertConfig.xml b/modules/spamfilter/ruleset/insertConfig.xml deleted file mode 100644 index 904889876..000000000 --- a/modules/spamfilter/ruleset/insertConfig.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/modules/spamfilter/schemas/spamfilter_denied_ip.xml b/modules/spamfilter/schemas/spamfilter_denied_ip.xml index 4911300fb..0d364a679 100644 --- a/modules/spamfilter/schemas/spamfilter_denied_ip.xml +++ b/modules/spamfilter/schemas/spamfilter_denied_ip.xml @@ -1,5 +1,8 @@
                - - + + + + +
                diff --git a/modules/spamfilter/schemas/spamfilter_denied_word.xml b/modules/spamfilter/schemas/spamfilter_denied_word.xml index 749f30d45..546d90de5 100644 --- a/modules/spamfilter/schemas/spamfilter_denied_word.xml +++ b/modules/spamfilter/schemas/spamfilter_denied_word.xml @@ -1,6 +1,9 @@ - + + + +
                diff --git a/modules/spamfilter/schemas/spamfilter_log.xml b/modules/spamfilter/schemas/spamfilter_log.xml index 4c4e7f18a..478560aaf 100644 --- a/modules/spamfilter/schemas/spamfilter_log.xml +++ b/modules/spamfilter/schemas/spamfilter_log.xml @@ -1,5 +1,5 @@ - +
                diff --git a/modules/spamfilter/spamfilter.admin.controller.php b/modules/spamfilter/spamfilter.admin.controller.php index 9e4762e1f..eaf4d79f1 100644 --- a/modules/spamfilter/spamfilter.admin.controller.php +++ b/modules/spamfilter/spamfilter.admin.controller.php @@ -5,29 +5,28 @@ * @author NAVER (developers@xpressengine.com) * @brief The admin controller class of the spamfilter module */ -class spamfilterAdminController extends spamfilter +class SpamfilterAdminController extends Spamfilter { /** * @brief Initialization */ - function init() + public function init() { } - function procSpamfilterAdminInsertConfig() + public function procSpamfilterAdminInsertConfig() { + // Get current config + $config = ModuleModel::getModuleConfig('spamfilter') ?: new stdClass; + // Get the default information - $args = Context::gets('limits', 'limits_interval', 'limits_count', 'check_trackback', 'ipv4_block_range', 'ipv6_block_range', 'display_keyword', 'custom_message'); + $args = Context::gets('limits', 'limits_interval', 'limits_count', 'blocked_actions', 'ipv4_block_range', 'ipv6_block_range', 'except_ip', 'custom_message'); // Set default values if($args->limits != 'Y') { $args->limits = 'N'; } - if($args->check_trackback != 'Y') - { - $args->check_trackback = 'N'; - } if(!preg_match('#^/(\d+)$#', $args->ipv4_block_range, $matches) || $matches[1] > 32 || $matches[1] < 16) { $args->ipv4_block_range = ''; @@ -36,12 +35,19 @@ class spamfilterAdminController extends spamfilter { $args->ipv6_block_range = ''; } + $args->except_ip = array_map('trim', preg_split('/[\n,]/', trim($args->except_ip ?? ''), -1, \PREG_SPLIT_NO_EMPTY)); $args->limits_interval = intval($args->limits_interval); $args->limits_count = intval($args->limits_count); + $args->blocked_actions = array_values($args->blocked_actions ?? []); + $args->custom_message = escape(utf8_trim($args->custom_message)); + foreach ($args as $key => $val) + { + $config->$key = $val; + } // Create and insert the module Controller object $oModuleController = getController('module'); - $moduleConfigOutput = $oModuleController->insertModuleConfig('spamfilter', $args); + $moduleConfigOutput = $oModuleController->insertModuleConfig('spamfilter', $config); if(!$moduleConfigOutput->toBool()) { return $moduleConfigOutput; @@ -52,7 +58,81 @@ class spamfilterAdminController extends spamfilter $this->setRedirectUrl($returnUrl); } - function procSpamfilterAdminInsertDeniedIP() + public function procSpamfilterAdminInsertConfigCaptcha() + { + // Get current config + $config = ModuleModel::getModuleConfig('spamfilter') ?: new stdClass; + + // Get updated values + $vars = Context::getRequestVars(); + if (!isset($vars->target_devices) || !is_array($vars->target_devices)) + { + $vars->target_devices = []; + } + if (!isset($vars->target_actions) || !is_array($vars->target_actions)) + { + $vars->target_actions = []; + } + + // Check values + if (!isset($config->captcha)) + { + $config->captcha = new stdClass; + } + $config->captcha->type = in_array($vars->captcha_type, ['recaptcha', 'turnstile']) ? $vars->captcha_type : 'none'; + $config->captcha->site_key = escape(utf8_trim($vars->site_key)); + $config->captcha->secret_key = escape(utf8_trim($vars->secret_key)); + if ($config->captcha->type !== 'none' && (!$config->captcha->site_key || !$config->captcha->secret_key)) + { + return new BaseObject(-1, 'msg_recaptcha_keys_not_set'); + } + + $config->captcha->theme = escape(utf8_trim($vars->captcha_theme)); + $config->captcha->size = escape(utf8_trim($vars->captcha_size)); + $config->captcha->target_devices = [ + 'pc' => in_array('pc', $vars->target_devices) ? true : false, + 'mobile' => in_array('mobile', $vars->target_devices) ? true : false, + ]; + $config->captcha->target_actions = [ + 'signup' => in_array('signup', $vars->target_actions) ? true : false, + 'login' => in_array('login', $vars->target_actions) ? true : false, + 'recovery' => in_array('recovery', $vars->target_actions) ? true : false, + 'document' => in_array('document', $vars->target_actions) ? true : false, + 'comment' => in_array('comment', $vars->target_actions) ? true : false, + ]; + $config->captcha->target_users = escape(utf8_trim($vars->target_users)) ?: 'non_members'; + $config->captcha->target_frequency = escape(utf8_trim($vars->target_frequency)) ?: 'first_time_only'; + + // Insert new config + $output = getController('module')->insertModuleConfig('spamfilter', $config); + if(!$output->toBool()) + { + return $output; + } + + $this->setMessage('success_updated'); + $returnUrl = Context::get('success_return_url') ? Context::get('success_return_url') : getNotEncodedUrl('', 'module', 'admin', 'act', 'dispSpamfilterAdminConfigCaptcha'); + $this->setRedirectUrl($returnUrl); + } + + public function procSpamfilterAdminSubmitCaptchaTest() + { + $response = Context::get('g-recaptcha-response') ?? Context::get('cf-turnstile-response'); + + try + { + SpamfilterModel::checkCaptchaResponse($response); + } + catch (Exception $e) + { + return new BaseObject(-1, $e->getMessage()); + } + + $this->setMessage('msg_recaptcha_test_success'); + $this->setRedirectUrl(getNotEncodedUrl('', 'module', 'admin', 'act', 'dispSpamfilterAdminConfigCaptchaTest')); + } + + public function procSpamfilterAdminInsertDeniedIP() { //스팸IP 추가 $ipaddress_list = Context::get('ipaddress_list'); @@ -66,32 +146,41 @@ class spamfilterAdminController extends spamfilter $this->setMessage(lang('success_registed').$message_fail); } - $returnUrl = Context::get('success_return_url') ? Context::get('success_return_url') : getNotEncodedUrl('', 'module', 'admin', 'act', 'dispSpamfilterAdminDeniedIPList'); $this->setRedirectUrl($returnUrl); } - function procSpamfilterAdminInsertDeniedWord() + public function procSpamfilterAdminUpdateDeniedIP() { - //스팸 키워드 추가 - $word_list = Context::get('word_list'); - if($word_list) + $ipaddress = Context::get('ipaddress'); + if (!$ipaddress) { - $output = $this->insertWord($word_list); - if(!$output->toBool() && !$output->get('fail_list')) return $output; - - if($output->get('fail_list')) $message_fail = ''.sprintf(lang('msg_faillist'),$output->get('fail_list')).''; - $this->setMessage(lang('success_registed').$message_fail); + throw new Rhymix\Framework\Exceptions\InvalidRequest; } - $returnUrl = Context::get('success_return_url') ? Context::get('success_return_url') : getNotEncodedUrl('', 'module', 'admin', 'act', 'dispSpamfilterAdminDeniedWordList'); - $this->setRedirectUrl($returnUrl); + $args = new \stdClass; + $args->ipaddress = $ipaddress; + + $except_member = Context::get('except_member'); + if (!empty($except_member)) + { + $args->except_member = $except_member === 'Y' ? 'Y' : 'N'; + } + else + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + + $output = executeQuery('spamfilter.updateDeniedIPAttributes', $args); + if (!$output->toBool()) + { + return $output; + } + + Rhymix\Framework\Cache::delete('spamfilter:denied_ip_list'); } - /** - * @brief Delete the banned IP - */ - function procSpamfilterAdminDeleteDeniedIP() + public function procSpamfilterAdminDeleteDeniedIP() { $ipAddressList = Context::get('ipaddress'); if($ipAddressList) $this->deleteIP($ipAddressList); @@ -102,10 +191,62 @@ class spamfilterAdminController extends spamfilter return $this->setRedirectUrl($returnUrl); } - /** - * @brief Delete the prohibited Word - */ - function procSpamfilterAdminDeleteDeniedWord() + public function procSpamfilterAdminInsertDeniedWord() + { + //스팸 키워드 추가 + $word_list = Context::get('word_list'); + $enable_description = Context::get('enable_description') ?? 'N'; + if($word_list) + { + $output = $this->insertWord($word_list, $enable_description); + if(!$output->toBool() && !$output->get('fail_list')) return $output; + + if($output->get('fail_list')) $message_fail = ''.sprintf(lang('msg_faillist'),$output->get('fail_list')).''; + $this->setMessage(lang('success_registed').$message_fail); + } + + $returnUrl = Context::get('success_return_url') ? Context::get('success_return_url') : getNotEncodedUrl('', 'module', 'admin', 'act', 'dispSpamfilterAdminDeniedWordList'); + $this->setRedirectUrl($returnUrl); + } + + public function procSpamfilterAdminUpdateDeniedWord() + { + $word = Context::get('word'); + if (!$word) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + + $args = new \stdClass; + $args->word = $word; + + $except_member = Context::get('except_member'); + if (!empty($except_member)) + { + $args->except_member = $except_member === 'Y' ? 'Y' : 'N'; + } + + $filter_html = Context::get('filter_html'); + if (!empty($filter_html)) + { + $args->filter_html = $filter_html === 'Y' ? 'Y' : 'N'; + } + + if (!isset($args->except_member) && !isset($args->filter_html)) + { + throw new Rhymix\Framework\Exceptions\InvalidRequest; + } + + $output = executeQuery('spamfilter.updateDeniedWordAttributes', $args); + if (!$output->toBool()) + { + return $output; + } + + Rhymix\Framework\Cache::delete('spamfilter:denied_word_list'); + } + + public function procSpamfilterAdminDeleteDeniedWord() { $wordList = Context::get('word'); $this->deleteWord($wordList); @@ -120,20 +261,23 @@ class spamfilterAdminController extends spamfilter * @brief Delete IP * Remove the IP address which was previously registered as a spammers */ - function deleteIP($ipaddress) + public function deleteIP($ipaddress) { if(!$ipaddress) return; $args = new stdClass; $args->ipaddress = $ipaddress; - return executeQuery('spamfilter.deleteDeniedIP', $args); + $output = executeQuery('spamfilter.deleteDeniedIP', $args); + + Rhymix\Framework\Cache::delete('spamfilter:denied_ip_list'); + return $output; } /** * @brief Register the spam word * The post, which contains the newly registered spam word, should be considered as a spam */ - function insertWord($word_list) + public function insertWord($word_list, $enable_description = 'Y') { if (!is_array($word_list)) { @@ -148,25 +292,38 @@ class spamfilterAdminController extends spamfilter { continue; } - + if ($enable_description === 'Y' && preg_match('/^(.+?)#(.+)$/', $word, $matches)) + { + $word = trim($matches[1]); + $description = trim($matches[2]); + } + else + { + $description = null; + } + if (mb_strlen($word, 'UTF-8') < 2 || mb_strlen($word, 'UTF-8') > 180) { throw new Rhymix\Framework\Exception('msg_invalid_word'); } - + $args = new stdClass; $args->word = $word; + $args->description = $description; + $args->is_regexp = preg_match('#^/.+/$#', $word) ? 'Y' : 'N'; $output = executeQuery('spamfilter.insertDeniedWord', $args); if (!$output->toBool()) { $fail_list .= $args->word . '
                '; } } - + if ($output) { $output->add('fail_list', $fail_list); } + + Rhymix\Framework\Cache::delete('spamfilter:denied_word_list'); return $output; } @@ -174,12 +331,15 @@ class spamfilterAdminController extends spamfilter * @brief Remove the spam word * Remove the word which was previously registered as a spam word */ - function deleteWord($word) + public function deleteWord($word) { if(!$word) return; $args = new stdClass; $args->word = $word; - return executeQuery('spamfilter.deleteDeniedWord', $args); + $output = executeQuery('spamfilter.deleteDeniedWord', $args); + + Rhymix\Framework\Cache::delete('spamfilter:denied_word_list'); + return $output; } } /* End of file spamfilter.admin.controller.php */ diff --git a/modules/spamfilter/spamfilter.admin.view.php b/modules/spamfilter/spamfilter.admin.view.php index 992a27d28..402d7ba3e 100644 --- a/modules/spamfilter/spamfilter.admin.view.php +++ b/modules/spamfilter/spamfilter.admin.view.php @@ -5,7 +5,7 @@ * @author NAVER (developers@xpressengine.com) * @brief The admin view class of the spamfilter module */ -class spamfilterAdminView extends spamfilter +class SpamfilterAdminView extends Spamfilter { /** * @brief Initialization @@ -21,9 +21,16 @@ class spamfilterAdminView extends spamfilter */ function dispSpamfilterAdminDeniedIPList() { + // Get sort index + $sort_index = Context::get('sort_index'); + if (!in_array($sort_index, array('regdate', 'latest_hit', 'hit'))) + { + $sort_index = 'regdate'; + } + // Get the list of denied IP addresses and words $oSpamFilterModel = getModel('spamfilter'); - $ip_list = $oSpamFilterModel->getDeniedIPList(); + $ip_list = $oSpamFilterModel->getDeniedIPList($sort_index); Context::set('ip_list', $ip_list); $security = new Security(); @@ -39,9 +46,16 @@ class spamfilterAdminView extends spamfilter */ function dispSpamfilterAdminDeniedWordList() { + // Get sort index + $sort_index = Context::get('sort_index'); + if (!in_array($sort_index, array('regdate', 'latest_hit', 'hit'))) + { + $sort_index = 'hit'; + } + // Get the list of denied IP addresses and words $oSpamFilterModel = getModel('spamfilter'); - $word_list = $oSpamFilterModel->getDeniedWordList(); + $word_list = $oSpamFilterModel->getDeniedWordList($sort_index); Context::set('word_list', $word_list); $security = new Security(); @@ -56,13 +70,36 @@ class spamfilterAdminView extends spamfilter */ function dispSpamfilterAdminConfigBlock() { - // Get configurations (using module model object) - $oModuleModel = getModel('module'); - $config = $oModuleModel->getModuleConfig('spamfilter'); + $config = ModuleModel::getModuleConfig('spamfilter'); Context::set('config', $config); $this->setTemplateFile('config_block'); } + + /** + * @brief Configure CAPTCHA + */ + function dispSpamfilterAdminConfigCaptcha() + { + $config = ModuleModel::getModuleConfig('spamfilter'); + Context::set('config', $config); + + $this->setTemplateFile('config_captcha'); + } + + /** + * @brief CAPTCHA Test + */ + public function dispSpamfilterAdminConfigCaptchaTest() + { + $config = ModuleModel::getModuleConfig('spamfilter'); + Context::set('config', $config); + + $captcha = SpamfilterModel::getCaptcha(); + Context::set('captcha', $captcha); + + $this->setTemplateFile('captcha_test'); + } } /* End of file spamfilter.admin.view.php */ /* Location: ./modules/spamfilter/spamfilter.admin.view.php */ diff --git a/modules/spamfilter/spamfilter.class.php b/modules/spamfilter/spamfilter.class.php index d55425133..d84fc609e 100644 --- a/modules/spamfilter/spamfilter.class.php +++ b/modules/spamfilter/spamfilter.class.php @@ -5,120 +5,169 @@ * @author NAVER (developers@xpressengine.com) * @brief The parent class of the spamfilter module */ -class spamfilter extends ModuleObject +class Spamfilter extends ModuleObject { /** * @brief Additional tasks required to accomplish during the installation */ - function moduleInstall() + public function moduleInstall() { - // Register action forward (to use in administrator mode) - $oModuleController = getController('module'); - // 2007.12.7 The triggers which try to perform spam filtering when new posts/comments/trackbacks are registered - $oModuleController->insertTrigger('document.insertDocument', 'spamfilter', 'controller', 'triggerInsertDocument', 'before'); - $oModuleController->insertTrigger('comment.insertComment', 'spamfilter', 'controller', 'triggerInsertComment', 'before'); - $oModuleController->insertTrigger('trackback.insertTrackback', 'spamfilter', 'controller', 'triggerInsertTrackback', 'before'); - // 2008-12-17 Add a spamfilter for post modification actions - $oModuleController->insertTrigger('comment.updateComment', 'spamfilter', 'controller', 'triggerInsertComment', 'before'); - $oModuleController->insertTrigger('document.updateDocument', 'spamfilter', 'controller', 'triggerInsertDocument', 'before'); - // 2013-11-14 The trigger which try to perform spam filtering when new message are registered - $oModuleController->insertTrigger('communication.sendMessage', 'spamfilter', 'controller', 'triggerSendMessage', 'before'); + } /** * @brief A method to check if the installation has been successful */ - function checkUpdate() + public function checkUpdate() { - $oDB = &DB::getInstance(); - $oModuleModel = getModel('module'); - // 2007.12.7 The triggers which try to perform spam filtering when new posts/comments/trackbacks are registered - if(!$oModuleModel->getTrigger('document.insertDocument', 'spamfilter', 'controller', 'triggerInsertDocument', 'before')) return true; - if(!$oModuleModel->getTrigger('comment.insertComment', 'spamfilter', 'controller', 'triggerInsertComment', 'before')) return true; - if(!$oModuleModel->getTrigger('trackback.insertTrackback', 'spamfilter', 'controller', 'triggerInsertTrackback', 'before')) return true; - // 2008-12-17 Add a spamfilter for post modification actions - if(!$oModuleModel->getTrigger('comment.updateComment', 'spamfilter', 'controller', 'triggerInsertComment', 'before')) return true; - if(!$oModuleModel->getTrigger('document.updateDocument', 'spamfilter', 'controller', 'triggerInsertDocument', 'before')) return true; - // 2013-11-14 The trigger which try to perform spam filtering when new message are registered - if(!$oModuleModel->getTrigger('communication.sendMessage', 'spamfilter', 'controller', 'triggerSendMessage', 'before')) return true; - - /** - * Add the hit count field (hit) - */ + $oDB = DB::getInstance(); if(!$oDB->isColumnExists('spamfilter_denied_word', 'hit')) return true; if(!$oDB->isColumnExists('spamfilter_denied_word', 'latest_hit')) return true; - + if(!$oDB->isColumnExists('spamfilter_denied_word', 'except_member')) return true; + if(!$oDB->isColumnExists('spamfilter_denied_word', 'filter_html')) return true; + if(!$oDB->isColumnExists('spamfilter_denied_word', 'is_regexp')) return true; + if(!$oDB->isColumnExists('spamfilter_denied_word', 'description')) return true; + if(!$oDB->isColumnExists('spamfilter_denied_ip', 'hit')) return true; + if(!$oDB->isColumnExists('spamfilter_denied_ip', 'latest_hit')) return true; + if(!$oDB->isColumnExists('spamfilter_denied_ip', 'except_member')) return true; if(!$oDB->isColumnExists('spamfilter_denied_ip', 'description')) return true; - - if(!$oModuleModel->getTrigger('document.manage', 'spamfilter', 'controller', 'triggerManageDocument', 'before')) + + $config = ModuleModel::getModuleConfig('spamfilter') ?: new stdClass; + if (!isset($config->captcha)) { return true; } - + return false; } /** * @brief Execute update */ - function moduleUpdate() + public function moduleUpdate() { - $oDB = &DB::getInstance(); - $oModuleModel = getModel('module'); - $oModuleController = getController('module'); - // 2007.12.7 The triggers which try to perform spam filtering when new posts/comments/trackbacks are registered - if(!$oModuleModel->getTrigger('document.insertDocument', 'spamfilter', 'controller', 'triggerInsertDocument', 'before')) - $oModuleController->insertTrigger('document.insertDocument', 'spamfilter', 'controller', 'triggerInsertDocument', 'before'); - if(!$oModuleModel->getTrigger('comment.insertComment', 'spamfilter', 'controller', 'triggerInsertComment', 'before')) - $oModuleController->insertTrigger('comment.insertComment', 'spamfilter', 'controller', 'triggerInsertComment', 'before'); - if(!$oModuleModel->getTrigger('trackback.insertTrackback', 'spamfilter', 'controller', 'triggerInsertTrackback', 'before')) - $oModuleController->insertTrigger('trackback.insertTrackback', 'spamfilter', 'controller', 'triggerInsertTrackback', 'before'); - // 2008-12-17 Add a spamfilter for post modification actions - if(!$oModuleModel->getTrigger('comment.updateComment', 'spamfilter', 'controller', 'triggerInsertComment', 'before')) - { - $oModuleController->insertTrigger('comment.updateComment', 'spamfilter', 'controller', 'triggerInsertComment', 'before'); - } - // 2008-12-17 Add a spamfilter for post modification actions - if(!$oModuleModel->getTrigger('document.updateDocument', 'spamfilter', 'controller', 'triggerInsertDocument', 'before')) - { - $oModuleController->insertTrigger('document.updateDocument', 'spamfilter', 'controller', 'triggerInsertDocument', 'before'); - } - // 2013-11-14 The trigger which try to perform spam filtering when new message are registered - if(!$oModuleModel->getTrigger('communication.sendMessage', 'spamfilter', 'controller', 'triggerSendMessage', 'before')) - { - $oModuleController->insertTrigger('communication.sendMessage', 'spamfilter', 'controller', 'triggerSendMessage', 'before'); - } - - /** - * Add the hit count field (hit) - */ + $oDB = DB::getInstance(); if(!$oDB->isColumnExists('spamfilter_denied_word', 'hit')) { - $oDB->addColumn('spamfilter_denied_word','hit','number',12,0,true); + $oDB->addColumn('spamfilter_denied_word', 'hit', 'number', null, 0, true, 'word'); $oDB->addIndex('spamfilter_denied_word','idx_hit', 'hit'); } if(!$oDB->isColumnExists('spamfilter_denied_word', 'latest_hit')) { - $oDB->addColumn('spamfilter_denied_word','latest_hit','date'); + $oDB->addColumn('spamfilter_denied_word', 'latest_hit', 'date', null, null, false, 'hit'); $oDB->addIndex('spamfilter_denied_word','idx_latest_hit', 'latest_hit'); } - + if(!$oDB->isColumnExists('spamfilter_denied_word', 'except_member')) + { + $oDB->addColumn('spamfilter_denied_word', 'except_member', 'char', 1, 'N', true, 'latest_hit'); + } + if(!$oDB->isColumnExists('spamfilter_denied_word', 'filter_html')) + { + $oDB->addColumn('spamfilter_denied_word', 'filter_html', 'char', 1, 'N', true, 'except_member'); + } + if(!$oDB->isColumnExists('spamfilter_denied_word', 'is_regexp')) + { + $oDB->addColumn('spamfilter_denied_word', 'is_regexp', 'char', 1, 'N', true, 'filter_html'); + $oDB->query('UPDATE spamfilter_denied_word SET is_regexp = ? WHERE word LIKE ?', ['Y', '/%/']); + } + if(!$oDB->isColumnExists('spamfilter_denied_word', 'description')) + { + $oDB->addColumn('spamfilter_denied_word', 'description', 'varchar', 191, null, false, 'is_regexp'); + } + if(!$oDB->isColumnExists('spamfilter_denied_ip', 'hit')) + { + $oDB->addColumn('spamfilter_denied_ip', 'hit', 'number', null, 0, true, 'ipaddress'); + $oDB->addIndex('spamfilter_denied_ip','idx_hit', 'hit'); + } + if(!$oDB->isColumnExists('spamfilter_denied_ip', 'latest_hit')) + { + $oDB->addColumn('spamfilter_denied_ip', 'latest_hit', 'date', null, null, false, 'hit'); + $oDB->addIndex('spamfilter_denied_ip','idx_latest_hit', 'latest_hit'); + } + if(!$oDB->isColumnExists('spamfilter_denied_ip', 'except_member')) + { + $oDB->addColumn('spamfilter_denied_ip', 'except_member', 'char', 1, 'N', true, 'latest_hit'); + } if(!$oDB->isColumnExists('spamfilter_denied_ip', 'description')) { - $oDB->addColumn('spamfilter_denied_ip','description','varchar', 250); + $oDB->addColumn('spamfilter_denied_ip', 'description', 'varchar', 191, null, false, 'except_member'); } - - if(!$oModuleModel->getTrigger('document.manage', 'spamfilter', 'controller', 'triggerManageDocument', 'before')) + + $config = ModuleModel::getModuleConfig('spamfilter') ?: new stdClass; + if (!isset($config->captcha)) { - $oModuleController->insertTrigger('document.manage', 'spamfilter', 'controller', 'triggerManageDocument', 'before'); + $config = is_object($config) ? $config : new stdClass; + $recaptcha_config = AddonModel::getAddonConfig('recaptcha'); + if ($recaptcha_config) + { + $config->captcha = $this->_importRecaptchaConfig($recaptcha_config); + } + else + { + $config->captcha = new stdClass; + $config->captcha->type = 'none'; + } + + $output = ModuleController::getInstance()->insertModuleConfig($this->module, $config); + if (!$output->toBool()) + { + return $output; + } } } /** - * @brief Re-generate the cache file + * Import configuration from reCAPTCHA addon. */ - function recompileCache() + protected function _importRecaptchaConfig($config) { + $output = new stdClass; + $output->type = 'none'; + if (!isset($config->site_key) || !isset($config->secret_key)) + { + return $output; + } + + if ($config->use_pc === 'Y' || $config->use_mobile === 'Y') + { + $output->type = 'recaptcha'; + } + $output->site_key = $config->site_key; + $output->secret_key = $config->secret_key; + $output->theme = $config->theme; + $output->size = $config->size; + $output->target_devices = [ + 'pc' => $config->use_pc === 'Y', + 'mobile' => $config->use_mobile === 'Y', + ]; + $output->target_users = $config->target_users; + $output->target_frequency = $config->target_frequency; + $output->target_actions = []; + foreach (['signup', 'login', 'recovery', 'document', 'comment'] as $action) + { + $output->target_actions[$action] = ($config->{'use_' . $action} === 'Y') ? true : false; + } + $output->target_modules = []; + foreach ($config->mid_list as $mid) + { + $module_srl = ModuleModel::getModuleInfoByMid($mid)->module_srl; + $output->target_modules[$module_srl] = true; + } + $output->target_modules_type = ($config->xe_run_method === 'run_selected') ? '+' : '-'; + + $oAddonAdminController = AddonAdminController::getInstance(); + if ($output->target_devices['pc']) + { + $oAddonAdminController->doDeactivate('recaptcha', 0, 'pc'); + $oAddonAdminController->makeCacheFile(0, 'pc'); + } + if ($output->target_devices['mobile']) + { + $oAddonAdminController->doDeactivate('recaptcha', 0, 'mobile'); + $oAddonAdminController->makeCacheFile(0, 'mobile'); + } + + return $output; } } /* End of file spamfilter.class.php */ diff --git a/modules/spamfilter/spamfilter.controller.php b/modules/spamfilter/spamfilter.controller.php index ae8a8615a..ceea7c0f0 100644 --- a/modules/spamfilter/spamfilter.controller.php +++ b/modules/spamfilter/spamfilter.controller.php @@ -5,8 +5,19 @@ * @author NAVER (developers@xpressengine.com) * @brief The controller class for the spamfilter module */ -class spamfilterController extends spamfilter +class SpamfilterController extends Spamfilter { + /** + * List of actions to use CAPTCHA. + */ + protected static $_captcha_actions = array( + 'signup' => '/^(?:disp|proc)Member(?:SignUp|Insert$)/i', + 'login' => '/^(?:disp|proc)MemberLogin(?:Form)?/i', + 'recovery' => '/^(?:disp|proc)Member(?:FindAccount|ResendAuthMail)/i', + 'document' => '/^(?:disp|proc)Board(Write|InsertDocument)/i', + 'comment' => '/^(?:disp|proc)Board(Content|InsertComment|ModifyComment|ReplyComment)/i', + ); + /** * @brief Initialization */ @@ -27,7 +38,11 @@ class spamfilterController extends spamfilter */ function triggerInsertDocument(&$obj) { - if($_SESSION['avoid_log']) return; + if (!empty($_SESSION['avoid_log'])) + { + return; + } + // Check the login status, login information, and permission $is_logged = Context::get('is_logged'); $logged_info = Context::get('logged_info'); @@ -39,21 +54,35 @@ class spamfilterController extends spamfilter if($grant->manager) return; } - $oFilterModel = getModel('spamfilter'); // Check if the IP is prohibited - $output = $oFilterModel->isDeniedIP(); - if(!$output->toBool()) return $output; + $output = SpamfilterModel::isDeniedIP(); + if(!$output->toBool()) + { + $config = SpamfilterModel::getConfig(); + if (!isset($config->blocked_actions) || in_array('document', $config->blocked_actions)) + { + return $output; + } + } + // Check if there is a ban on the word - $text = ''; - if($is_logged) + $filter_targets = [$obj->title, $obj->content, $obj->tags ?? '']; + if(!$is_logged) { - $text = $obj->title . ' ' . $obj->content . ' ' . $obj->tags; + $filter_targets[] = $obj->nick_name; + $filter_targets[] = $obj->homepage; } - else + foreach ($obj as $key => $val) { - $text = $obj->title . ' ' . $obj->content . ' ' . $obj->nick_name . ' ' . $obj->homepage . ' ' . $obj->tags; + if (preg_match('/^extra_vars\d+$/', $key) && !empty($val)) + { + foreach (is_array($val) ? $val : explode('|@|', $val) as $fragment) + { + $filter_targets[] = $fragment; + } + } } - $output = $oFilterModel->isDeniedWord($text); + $output = SpamfilterModel::isDeniedWord(implode("\n", $filter_targets)); if(!$output->toBool()) { return $output; @@ -61,7 +90,7 @@ class spamfilterController extends spamfilter // Check the specified time beside the modificaiton time if($obj->document_srl == 0) { - $output = $oFilterModel->checkLimited(); + $output = SpamfilterModel::checkLimited(); if(!$output->toBool()) return $output; } // Save a log @@ -73,7 +102,11 @@ class spamfilterController extends spamfilter */ function triggerInsertComment(&$obj) { - if($_SESSION['avoid_log']) return; + if (!empty($_SESSION['avoid_log'])) + { + return; + } + // Check the login status, login information, and permission $is_logged = Context::get('is_logged'); $logged_info = Context::get('logged_info'); @@ -85,10 +118,17 @@ class spamfilterController extends spamfilter if($grant->manager) return; } - $oFilterModel = getModel('spamfilter'); // Check if the IP is prohibited - $output = $oFilterModel->isDeniedIP(); - if(!$output->toBool()) return $output; + $output = SpamfilterModel::isDeniedIP(); + if(!$output->toBool()) + { + $config = SpamfilterModel::getConfig(); + if (!isset($config->blocked_actions) || in_array('comment', $config->blocked_actions)) + { + return $output; + } + } + // Check if there is a ban on the word if($is_logged) { @@ -96,14 +136,14 @@ class spamfilterController extends spamfilter } else { - $text = $obj->content . ' ' . $obj->nick_name . ' ' . $obj->homepage; + $text = $obj->content . ' ' . $obj->nick_name . ' ' . $obj->homepage; } - $output = $oFilterModel->isDeniedWord($text); + $output = SpamfilterModel::isDeniedWord($text); if(!$output->toBool()) return $output; // If the specified time check is not modified if(!$obj->__isupdate) { - $output = $oFilterModel->checkLimited(); + $output = SpamfilterModel::checkLimited(); if(!$output->toBool()) return $output; } unset($obj->__isupdate); @@ -111,41 +151,6 @@ class spamfilterController extends spamfilter $this->insertLog(); } - /** - * @brief Inspect the trackback creation time and IP - */ - function triggerInsertTrackback(&$obj) - { - if($_SESSION['avoid_log']) return; - - $oFilterModel = getModel('spamfilter'); - // Confirm if the trackbacks have been added more than once to your document - $output = $oFilterModel->isInsertedTrackback($obj->document_srl); - if(!$output->toBool()) return $output; - - // Check if the IP is prohibited - $output = $oFilterModel->isDeniedIP(); - if(!$output->toBool()) return $output; - - // Check if there is a ban on the word - $text = $obj->blog_name . ' ' . $obj->title . ' ' . $obj->excerpt . ' ' . $obj->url; - $output = $oFilterModel->isDeniedWord($text); - if(!$output->toBool()) return $output; - - // Start Filtering - $oTrackbackController = getController('trackback'); - if (is_object($oTrackbackController) && method_exists($oTrackbackController, 'deleteTrackbackSender')) - { - // In case the title and the blog name are indentical, investigate the IP address of the last 6 hours, delete and ban it. - if($obj->title == $obj->excerpt) - { - $oTrackbackController->deleteTrackbackSender(60*60*6, \RX_CLIENT_IP, $obj->url, $obj->blog_name, $obj->title, $obj->excerpt); - $this->insertIP(\RX_CLIENT_IP, 'AUTO-DENIED : trackback.insertTrackback'); - return new BaseObject(-1, 'msg_alert_trackback_denied'); - } - } - } - /** * @brief IP registration * The registered IP address is considered as a spammer @@ -158,14 +163,14 @@ class spamfilterController extends spamfilter } $fail_list = ''; $output = null; - + foreach ($ipaddress_list as $ipaddress) { if ($ipaddress === '') { continue; } - + $args = new stdClass; if (preg_match('@^(.+?)(?://|#)(.*)$@', $ipaddress, $matches)) { @@ -177,52 +182,127 @@ class spamfilterController extends spamfilter $args->ipaddress = $ipaddress; $args->description = $description; } - + if (!Rhymix\Framework\Filters\IpFilter::validateRange($args->ipaddress)) { return new BaseObject(-1, 'msg_invalid_ip'); } - + $output = executeQuery('spamfilter.insertDeniedIP', $args); if (!$output->toBool()) { $fail_list .= $args->ipaddress . '
                '; } } - + if ($output) { $output->add('fail_list', $fail_list); } - + + Rhymix\Framework\Cache::delete('spamfilter:denied_ip_list'); return $output; } + /** + * Block voting from a spam IP. + */ + function triggerVote(&$obj) + { + if (!empty($_SESSION['avoid_log'])) + { + return; + } + + if ($this->user->isAdmin() || (Context::get('grant')->manager ?? false)) + { + return; + } + + $config = SpamfilterModel::getConfig(); + if ($obj->point > 0 && isset($config->blocked_actions) && !in_array('vote_up', $config->blocked_actions)) + { + return; + } + if ($obj->point < 0 && isset($config->blocked_actions) && !in_array('vote_down', $config->blocked_actions)) + { + return; + } + + $output = SpamfilterModel::isDeniedIP(); + if (!$output->toBool()) + { + return $output; + } + } + + /** + * Block reporting from a spam IP. + */ + function triggerDeclare(&$obj) + { + if (!empty($_SESSION['avoid_log'])) + { + return; + } + + if ($this->user->isAdmin() || (Context::get('grant')->manager ?? false)) + { + return; + } + + $config = SpamfilterModel::getConfig(); + if (isset($config->blocked_actions) && !in_array('declare', $config->blocked_actions)) + { + return; + } + + $output = SpamfilterModel::isDeniedIP(); + if (!$output->toBool()) + { + return $output; + } + } + /** * @brief The routine process to check the time it takes to store a message, when writing it, and to ban IP/word */ function triggerSendMessage(&$obj) { - if($_SESSION['avoid_log']) return; + if($this->user->isAdmin() || !empty($_SESSION['avoid_log'])) + { + return; + } - $logged_info = Context::get('logged_info'); - if($logged_info->is_admin == 'Y') return; + if(isset($obj->use_spamfilter) && $obj->use_spamfilter === false) + { + return; + } - $oFilterModel = getModel('spamfilter'); // Check if the IP is prohibited - $output = $oFilterModel->isDeniedIP(); - if(!$output->toBool()) return $output; + $output = SpamfilterModel::isDeniedIP(); + if(!$output->toBool()) + { + $config = SpamfilterModel::getConfig(); + if (!isset($config->blocked_actions) || in_array('message', $config->blocked_actions)) + { + return $output; + } + } + // Check if there is a ban on the word $text = $obj->title . ' ' . $obj->content; - $output = $oFilterModel->isDeniedWord($text); + $output = SpamfilterModel::isDeniedWord($text); if(!$output->toBool()) return $output; + // Check the specified time - $output = $oFilterModel->checkLimited(TRUE); + $output = SpamfilterModel::checkLimited(TRUE); if(!$output->toBool()) return $output; + // Save a log $this->insertLog(); } - + /** * @brief while document manager is running, stop filter */ @@ -230,7 +310,49 @@ class spamfilterController extends spamfilter { $this->setAvoidLog(); } - + + /** + * Trigger to check CAPTCHA. + */ + function triggerCheckCaptcha(&$obj) + { + $config = ModuleModel::getModuleConfig('spamfilter'); + if (!SpamfilterModel::isCaptchaEnabled()) + { + return; + } + + $target_actions = []; + foreach (['signup', 'login', 'recovery', 'document', 'comment'] as $action) + { + if ($config->captcha->target_actions[$action]) + { + if (preg_match(self::$_captcha_actions[$action], $obj->act) || ($action === 'comment' && (!$obj->act || $obj->act === 'dispBoardContent') && Context::get('document_srl'))) + { + $target_actions[$action] = true; + } + } + } + + if (count($target_actions)) + { + $captcha_class = 'Rhymix\\Modules\\Spamfilter\\Captcha\\' . $config->captcha->type; + $captcha_class::init($config->captcha); + + if (strncasecmp('proc', $obj->act, 4) === 0) + { + $captcha_class::check(); + } + else + { + $captcha = new $captcha_class(); + $captcha->setTargetActions($target_actions); + $captcha->addScripts(); + Context::set('captcha', $captcha); + } + } + } + /** * @brief Log registration * Register the newly accessed IP address in the log. In case the log interval is withing a certain time, diff --git a/modules/spamfilter/spamfilter.model.php b/modules/spamfilter/spamfilter.model.php index f5b8d6c11..c7f73f217 100644 --- a/modules/spamfilter/spamfilter.model.php +++ b/modules/spamfilter/spamfilter.model.php @@ -5,32 +5,23 @@ * @author NAVER (developers@xpressengine.com) * @brief The Model class of the spamfilter module */ -class spamfilterModel extends spamfilter +class SpamfilterModel extends Spamfilter { - /** - * @brief Initialization - */ - function init() - { - } - /** * @brief Return the user setting values of the Spam filter module */ - function getConfig() + public static function getConfig() { - // Get configurations (using the module model object) - $oModuleModel = getModel('module'); - return $oModuleModel->getModuleConfig('spamfilter'); + return ModuleModel::getModuleConfig('spamfilter') ?: new stdClass; } /** * @brief Return the list of registered IP addresses which were banned */ - function getDeniedIPList() + public static function getDeniedIPList($sort_index = 'regdate') { $args = new stdClass(); - $args->sort_index = "regdate"; + $args->sort_index = $sort_index; $args->page = Context::get('page')?Context::get('page'):1; $output = executeQueryArray('spamfilter.getDeniedIPList', $args); if(!$output->data) return array(); @@ -40,32 +31,47 @@ class spamfilterModel extends spamfilter /** * @brief Check if the ipaddress is in the list of banned IP addresses */ - function isDeniedIP() + public static function isDeniedIP() { - $ip_list = $this->getDeniedIPList(); - if(!count($ip_list)) return new BaseObject(); - - $ip_ranges = array(); + $ip_list = Rhymix\Framework\Cache::get('spamfilter:denied_ip_list'); + if ($ip_list === null) + { + $ip_list = self::getDeniedIPList(); + Rhymix\Framework\Cache::set('spamfilter:denied_ip_list', $ip_list); + } + if (!count($ip_list)) + { + return new BaseObject(); + } + + $is_logged = Context::get('is_logged'); foreach ($ip_list as $ip_range) { - $ip_ranges[] = $ip_range->ipaddress; + if (!empty($ip_range->except_member) && $ip_range->except_member === 'Y' && $is_logged) + { + continue; + } + + if (Rhymix\Framework\Filters\IpFilter::inRange(\RX_CLIENT_IP, $ip_range->ipaddress)) + { + $args = new stdClass(); + $args->ipaddress = $ip_range->ipaddress; + executeQuery('spamfilter.updateDeniedIPHit', $args); + + return new BaseObject(-1, 'msg_alert_registered_denied_ip'); + } } - - if (Rhymix\Framework\Filters\IpFilter::inRanges(\RX_CLIENT_IP, $ip_ranges)) - { - return new BaseObject(-1, 'msg_alert_registered_denied_ip'); - } - + return new BaseObject(); } /** * @brief Return the list of registered Words which were banned */ - function getDeniedWordList() + public static function getDeniedWordList($sort_index = 'hit') { $args = new stdClass(); - $args->sort_index = "hit"; + $args->sort_index = $sort_index; $output = executeQueryArray('spamfilter.getDeniedWordList', $args); return $output->data ?: array(); } @@ -73,23 +79,41 @@ class spamfilterModel extends spamfilter /** * @brief Check if the text, received as a parameter, is banned or not */ - function isDeniedWord($text) + public static function isDeniedWord($text) { - $word_list = $this->getDeniedWordList(); - if(!count($word_list)) return new BaseObject(); + $word_list = Rhymix\Framework\Cache::get('spamfilter:denied_word_list'); + if ($word_list === null) + { + $word_list = self::getDeniedWordList(); + Rhymix\Framework\Cache::set('spamfilter:denied_word_list', $word_list); + } + if (!count($word_list)) + { + return new BaseObject(); + } + + $is_logged = Context::get('is_logged'); + $fulltext = strtolower(utf8_trim(utf8_normalize_spaces($text))); + $plaintext = htmlspecialchars_decode(strip_tags($fulltext, '')); - $text = strtolower(utf8_trim(utf8_normalize_spaces(htmlspecialchars_decode(strip_tags($text, ''))))); foreach ($word_list as $word_item) { + if (!empty($word_item->except_member) && $word_item->except_member === 'Y' && $is_logged) + { + continue; + } + + $target = (!empty($word_item->filter_html) && $word_item->filter_html === 'Y') ? 'fulltext' : 'plaintext'; $word = $word_item->word; $hit = false; + if (preg_match('#^/.+/$#', $word)) { - $hit = preg_match($word . 'iu', $text, $matches) ? $matches[0] : false; + $hit = preg_match($word . 'iu', $$target, $matches) ? $matches[0] : false; } if ($hit === false) { - $hit = (strpos($text, strtolower($word)) !== false) ? $word : false; + $hit = (strpos($$target, strtolower($word)) !== false) ? $word : false; } if ($hit !== false) { @@ -97,14 +121,13 @@ class spamfilterModel extends spamfilter $args->word = $word; executeQuery('spamfilter.updateDeniedWordHit', $args); - $config = $this->getConfig(); + $config = self::getConfig(); if($config->custom_message) { if(preg_match('/^\\$user_lang->[a-zA-Z0-9]+$/', $config->custom_message)) { - getController('module')->replaceDefinedLangCode($config->custom_message); - $custom_message = htmlspecialchars($config->custom_message); + $custom_message = escape(Context::replaceUserLang($config->custom_message), false); } else { @@ -120,7 +143,7 @@ class spamfilterModel extends spamfilter { $custom_message = sprintf($custom_message, escape($hit, false)); } - + return new BaseObject(-1, $custom_message); } } @@ -131,15 +154,23 @@ class spamfilterModel extends spamfilter /** * @brief Check the specified time */ - function checkLimited($isMessage = FALSE) + public static function checkLimited($isMessage = FALSE) { - $config = $this->getConfig(); + $config = self::getConfig(); if($config->limits != 'Y') return new BaseObject(); $limit_count = $config->limits_count ?: 3; $interval = $config->limits_interval ?: 10; - $count = $this->getLogCount($interval); + if (!empty($config->except_ip)) + { + if (Rhymix\Framework\Filters\IpFilter::inRanges(\RX_CLIENT_IP, $config->except_ip)) + { + return new BaseObject(); + } + } + + $count = self::getLogCount($interval); // Ban the IP address if the interval is exceeded if($count>=$limit_count) @@ -152,8 +183,8 @@ class spamfilterModel extends spamfilter { $suffix = $config->ipv6_block_range ?: ''; } - - $oSpamFilterController = getController('spamfilter'); + + $oSpamFilterController = SpamfilterController::getInstance(); $oSpamFilterController->insertIP(\RX_CLIENT_IP . $suffix, 'AUTO-DENIED : Over limit'); return new BaseObject(-1, 'msg_alert_registered_denied_ip'); } @@ -178,10 +209,101 @@ class spamfilterModel extends spamfilter return new BaseObject(); } + /** + * Check if CAPTCHA is enabled + * + * @return bool + */ + public static function isCaptchaEnabled($target_action = null) + { + $config = ModuleModel::getModuleConfig('spamfilter'); + $user = Context::get('logged_info'); + if (!isset($config) || empty($config->captcha) || empty($config->captcha->type) || empty($config->captcha->site_key) || empty($config->captcha->secret_key)) + { + return false; + } + if ($user->is_admin === 'Y') + { + return false; + } + if ($config->captcha->target_users !== 'everyone' && $user->member_srl) + { + return false; + } + if ($config->captcha->target_frequency !== 'every_time' && isset($_SESSION['recaptcha_authenticated']) && $_SESSION['recaptcha_authenticated']) + { + return false; + } + if (!$config->captcha->target_devices[Mobile::isFromMobilePhone() ? 'mobile' : 'pc']) + { + return false; + } + if ($target_action && !$config->captcha->target_actions[$target_action]) + { + return false; + } + return true; + } + + /** + * Get a CAPTCHA instance. + * + * @return ?object + */ + public static function getCaptcha($target_action = null) + { + $config = ModuleModel::getModuleConfig('spamfilter'); + if (!isset($config) || empty($config->captcha) || empty($config->captcha->type) || empty($config->captcha->site_key) || empty($config->captcha->secret_key)) + { + return null; + } + + $captcha_class = 'Rhymix\\Modules\\Spamfilter\\Captcha\\' . $config->captcha->type; + if (!class_exists($captcha_class)) + { + return null; + } + + $captcha_class::init($config->captcha); + $captcha = new $captcha_class(); + if ($target_action) + { + $captcha->setTargetActions([$target_action => true]); + } + $captcha->addScripts(); + return $captcha; + } + + /** + * Check the CAPTCHA. + * + * This method will throw an exception if the CAPTCHA is invalid. + * + * @param ?string $response + * @return void + */ + public static function checkCaptchaResponse(?string $response = null): void + { + $config = ModuleModel::getModuleConfig('spamfilter'); + if (!isset($config) || empty($config->captcha) || empty($config->captcha->type) || empty($config->captcha->site_key) || empty($config->captcha->secret_key)) + { + throw new Exception('msg_recaptcha_not_configured'); + } + + $captcha_class = 'Rhymix\\Modules\\Spamfilter\\Captcha\\' . $config->captcha->type; + if (!class_exists($captcha_class)) + { + throw new Exception('msg_recaptcha_not_configured'); + } + + $captcha_class::init($config->captcha); + $captcha_class::check($response); + } + /** * @brief Check if the trackbacks have already been registered to a particular article */ - function isInsertedTrackback($document_srl) + public static function isInsertedTrackback($document_srl) { $oTrackbackModel = getModel('trackback'); if (is_object($oTrackbackModel) && method_exists($oTrackbackModel, 'getTrackbackCountByIPAddress')) @@ -198,7 +320,7 @@ class spamfilterModel extends spamfilter /** * @brief Return the number of logs recorded within the interval for the specified IPaddress */ - function getLogCount($time = 60, $ipaddress='') + public static function getLogCount($time = 60, $ipaddress='') { if(!$ipaddress) $ipaddress = \RX_CLIENT_IP; diff --git a/modules/spamfilter/tpl/captcha_test.html b/modules/spamfilter/tpl/captcha_test.html new file mode 100644 index 000000000..20afe9a9f --- /dev/null +++ b/modules/spamfilter/tpl/captcha_test.html @@ -0,0 +1,32 @@ + + +
                + + + + +
                +

                {$XE_VALIDATOR_MESSAGE}

                +
                + +
                + +
                + {$captcha|noescape} +
                +
                +
                +
                + +
                +
                + +
                +

                {$lang->msg_recaptcha_not_configured}

                +
                + + +
                + + + diff --git a/modules/spamfilter/tpl/config_block.html b/modules/spamfilter/tpl/config_block.html index fcc73d30e..d94daa11b 100644 --- a/modules/spamfilter/tpl/config_block.html +++ b/modules/spamfilter/tpl/config_block.html @@ -1,64 +1,69 @@
                - -
                + - +
                +

                {$XE_VALIDATOR_MESSAGE}

                +
                - - +

                + + +

                +

                + {$lang->unit_sec}   + {$lang->unit_write_count} +

                {$lang->cmd_interval_help}

                - +
                -
                - +

                {$lang->about_custom_message}

                -
                - -
                - {$lang->unit_sec} -

                {$lang->cmd_limits_interval_help}

                -
                -
                -
                - -
                - {$lang->unit_write_count} -

                {$lang->cmd_limits_count_help}

                -
                -
                @@ -85,6 +90,13 @@

                {$lang->cmd_block_range_help}

                +
                + +
                + +

                {$lang->cmd_spamfilter_except_ip_help}

                +
                +
                diff --git a/modules/spamfilter/tpl/config_captcha.html b/modules/spamfilter/tpl/config_captcha.html new file mode 100644 index 000000000..831d9a859 --- /dev/null +++ b/modules/spamfilter/tpl/config_captcha.html @@ -0,0 +1,126 @@ + + +
                + + + + +
                +

                {$XE_VALIDATOR_MESSAGE}

                +
                +
                + +
                + +

                {$lang->about_captcha_position}

                +
                +
                +
                + +
                + +
                +
                +
                + +
                + +
                +
                +
                + +
                + +
                +
                +
                + +
                + +
                +
                +
                + +
                + + +
                +
                +
                + +
                + + + + + +
                +
                +
                + +
                + + +
                +
                +
                + +
                + + +
                +
                +
                +
                + +
                +
                + +
                + + + diff --git a/modules/spamfilter/tpl/css/spamfilter_admin.css b/modules/spamfilter/tpl/css/spamfilter_admin.css index 74c6fef0b..9b9723405 100644 --- a/modules/spamfilter/tpl/css/spamfilter_admin.css +++ b/modules/spamfilter/tpl/css/spamfilter_admin.css @@ -1,5 +1,10 @@ span.is_regexp { display: inline-block; margin-left: 4px; - color: red; -} \ No newline at end of file + background: #888; + color: #fff; + font-size: 10px; + line-height: 12px; + padding: 2px 4px; + border-radius: 2px; +} diff --git a/modules/spamfilter/tpl/denied_ip_list.html b/modules/spamfilter/tpl/denied_ip_list.html index 26d1c664e..de9bdac77 100644 --- a/modules/spamfilter/tpl/denied_ip_list.html +++ b/modules/spamfilter/tpl/denied_ip_list.html @@ -1,10 +1,5 @@
                -
                @@ -18,7 +13,10 @@ IP {$lang->description} - {$lang->regdate} + {$lang->cmd_spamfilter_except_member} + {$lang->latest_hit} + {$lang->hit} + {$lang->regdate} @@ -26,11 +24,14 @@ {$ip_info->ipaddress} {$ip_info->description} + {$ip_info->except_member} + {zdate($ip_info->latest_hit,'Y-m-d H:i')}- + {$ip_info->hit} {zdate($ip_info->regdate,'Y-m-d')} - {$lang->no_data} + {$lang->no_data} diff --git a/modules/spamfilter/tpl/denied_word_list.html b/modules/spamfilter/tpl/denied_word_list.html index 5d6870d35..eb51e9bdd 100644 --- a/modules/spamfilter/tpl/denied_word_list.html +++ b/modules/spamfilter/tpl/denied_word_list.html @@ -1,10 +1,5 @@
                - @@ -17,15 +12,21 @@ {$lang->word} - {$lang->latest_hit} - {$lang->hit} - {$lang->regdate} + {$lang->description} + {$lang->cmd_spamfilter_except_member} + {$lang->cmd_spamfilter_filter_html} + {$lang->latest_hit} + {$lang->hit} + {$lang->regdate} - {$word_info->word} [{$lang->msg_denied_word_is_regexp}] + {$word_info->word} {$lang->cmd_spamfilter_is_regexp} + {$word_info->description} + {$word_info->except_member} + {$word_info->filter_html} {zdate($word_info->latest_hit,'Y-m-d H:i')}- {$word_info->hit} {zdate($word_info->regdate,'Y-m-d')} @@ -43,6 +44,7 @@ +

                {$lang->about_denied_word}

                diff --git a/modules/spamfilter/tpl/header.html b/modules/spamfilter/tpl/header.html index d4a7b9f11..95206b739 100644 --- a/modules/spamfilter/tpl/header.html +++ b/modules/spamfilter/tpl/header.html @@ -6,3 +6,10 @@

                {$XE_VALIDATOR_MESSAGE}

                + diff --git a/modules/spamfilter/tpl/js/recaptcha.js b/modules/spamfilter/tpl/js/recaptcha.js new file mode 100644 index 000000000..d8dd25698 --- /dev/null +++ b/modules/spamfilter/tpl/js/recaptcha.js @@ -0,0 +1,60 @@ + +function reCaptchaCallback() { + var recaptcha_config = $("#recaptcha-config"); + var recaptcha_instances = $(".g-recaptcha"); + var recaptcha_instance_id = 1; + var recaptcha_targets = String(recaptcha_config.data("targets")).split(","); + + if (recaptcha_instances.length === 0) { + var autoinsert_candidates = $("form").filter(function() { + var actinput = $("input[name='act']", this); + if (actinput.length && actinput.val()) { + var act = String(actinput.val()); + if (act.match(/^procMemberInsert$/i) && recaptcha_targets.indexOf("signup") > -1) { + return true; + } + if (act.match(/^procMemberLogin$/i) && recaptcha_targets.indexOf("login") > -1) { + return true; + } + if (act.match(/^procMember(FindAccount|ResendAuthMail)$/i) && recaptcha_targets.indexOf("recovery") > -1) { + return true; + } + if (act.match(/^proc[A-Z][a-zA-Z0-9_]+InsertDocument$/i) && recaptcha_targets.indexOf("document") > -1) { + return true; + } + if (act.match(/^proc[A-Z][a-zA-Z0-9_]+InsertComment$/i) && recaptcha_targets.indexOf("comment") > -1) { + return true; + } + } + var procfilter = $(this).attr("onsubmit"); + if (procfilter && procfilter.match(/procFilter\b.+\binsert/i) && (recaptcha_targets.indexOf("document") > -1 || recaptcha_targets.indexOf("comment") > -1)) { + return true; + } + return false; + }); + autoinsert_candidates.each(function() { + var new_instance = $('
                '); + new_instance.attr("id", "recaptcha-instance-" + recaptcha_instance_id++); + var autoinsert_point = $(this).find("button[type='submit'],input[type='submit']").parent(); + if (autoinsert_point.size()) { + new_instance.insertBefore(autoinsert_point); + } else { + new_instance.appendTo($(this)); + } + }); + var recaptcha_instances = $(".g-recaptcha"); + } + + recaptcha_instances.each(function() { + var instance = $(this); + var theme = recaptcha_config.data("theme"); + if (theme === 'auto') { + theme = getColorScheme(); + } + grecaptcha.render(instance.attr("id"), { + sitekey: recaptcha_config.data("sitekey"), + size: recaptcha_config.data("size"), + theme: theme + }); + }); +} diff --git a/modules/spamfilter/tpl/js/spamfilter_admin.js b/modules/spamfilter/tpl/js/spamfilter_admin.js index 7f56490f5..eba33db93 100644 --- a/modules/spamfilter/tpl/js/spamfilter_admin.js +++ b/modules/spamfilter/tpl/js/spamfilter_admin.js @@ -17,3 +17,45 @@ function doDeleteDeniedWord(word) { fo_obj.act.value = "procSpamfilterAdminDeleteDeniedWord"; fo_obj.submit(); } + +/** + * Toggle attributes. + */ +$(function() { + + $('.denied_ip_toggle_except_member').on('click', function(e) { + e.preventDefault(); + var that = $(this); + var new_value = that.text() === 'Y' ? 'N' : 'Y'; + exec_json('spamfilter.procSpamfilterAdminUpdateDeniedIP', { + ipaddress: that.data('ipaddress'), + except_member: new_value + }, function() { + that.text(new_value); + }); + }); + + $('.denied_word_toggle_except_member').on('click', function(e) { + e.preventDefault(); + var that = $(this); + var new_value = that.text() === 'Y' ? 'N' : 'Y'; + exec_json('spamfilter.procSpamfilterAdminUpdateDeniedWord', { + word: that.data('word'), + except_member: new_value + }, function() { + that.text(new_value); + }); + }); + + $('.denied_word_toggle_filter_html').on('click', function(e) { + e.preventDefault(); + var that = $(this); + var new_value = that.text() === 'Y' ? 'N' : 'Y'; + exec_json('spamfilter.procSpamfilterAdminUpdateDeniedWord', { + word: that.data('word'), + filter_html: new_value + }, function() { + that.text(new_value); + }); + }); +}); diff --git a/modules/spamfilter/tpl/js/turnstile.js b/modules/spamfilter/tpl/js/turnstile.js new file mode 100644 index 000000000..39a7d4fbf --- /dev/null +++ b/modules/spamfilter/tpl/js/turnstile.js @@ -0,0 +1,60 @@ + +function turnstileCallback() { + var recaptcha_config = $("#turnstile-config"); + var recaptcha_instances = $(".turnstile-captcha"); + var recaptcha_instance_id = 1; + var recaptcha_targets = String(recaptcha_config.data("targets")).split(","); + + if (recaptcha_instances.length === 0) { + var autoinsert_candidates = $("form").filter(function() { + var actinput = $("input[name='act']", this); + if (actinput.length && actinput.val()) { + var act = String(actinput.val()); + if (act.match(/^procMemberInsert$/i) && recaptcha_targets.indexOf("signup") > -1) { + return true; + } + if (act.match(/^procMemberLogin$/i) && recaptcha_targets.indexOf("login") > -1) { + return true; + } + if (act.match(/^procMember(FindAccount|ResendAuthMail)$/i) && recaptcha_targets.indexOf("recovery") > -1) { + return true; + } + if (act.match(/^proc[A-Z][a-zA-Z0-9_]+InsertDocument$/i) && recaptcha_targets.indexOf("document") > -1) { + return true; + } + if (act.match(/^proc[A-Z][a-zA-Z0-9_]+InsertComment$/i) && recaptcha_targets.indexOf("comment") > -1) { + return true; + } + } + var procfilter = $(this).attr("onsubmit"); + if (procfilter && procfilter.match(/procFilter\b.+\binsert/i) && (recaptcha_targets.indexOf("document") > -1 || recaptcha_targets.indexOf("comment") > -1)) { + return true; + } + return false; + }); + autoinsert_candidates.each(function() { + var new_instance = $('
                '); + new_instance.attr("id", "turnstile-instance-" + recaptcha_instance_id++); + var autoinsert_point = $(this).find("button[type='submit'],input[type='submit']").parent(); + if (autoinsert_point.size()) { + new_instance.insertBefore(autoinsert_point); + } else { + new_instance.appendTo($(this)); + } + }); + var recaptcha_instances = $(".turnstile-captcha"); + } + + recaptcha_instances.each(function() { + var instance = $(this); + var theme = recaptcha_config.data("theme"); + if (theme === 'auto') { + theme = getColorScheme(); + } + grecaptcha.render(`#${instance.attr("id")}`, { + sitekey: recaptcha_config.data("sitekey"), + size: recaptcha_config.data("size"), + theme: theme + }); + }); +} diff --git a/modules/tag/conf/info.xml b/modules/tag/conf/info.xml index 9f255b47a..c9b1ebba8 100644 --- a/modules/tag/conf/info.xml +++ b/modules/tag/conf/info.xml @@ -1,6 +1,6 @@ - 꼬리표 + 태그 标签 タグ Tag @@ -9,7 +9,7 @@ Теги 標籤 Etiket - 꼬리표 관리 + 태그 관리 기능입니다. 标签管理模块。 タグ管理用のモジュールです。 Module for managing tags. @@ -18,8 +18,8 @@ Модуль для управления тегами. 標籤管理模組。 Etiketleri yönetme modülü. - 1.7 - 2013-11-27 + RX_VERSION + RX_CORE content diff --git a/modules/tag/conf/module.xml b/modules/tag/conf/module.xml new file mode 100644 index 000000000..588556821 --- /dev/null +++ b/modules/tag/conf/module.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + 태그 + Tag + 标签 + 標籤 + タグ + Tag + Etiqueta + Теги + Etiket + + + diff --git a/modules/tag/lang/en.php b/modules/tag/lang/en.php new file mode 100644 index 000000000..7cec28be4 --- /dev/null +++ b/modules/tag/lang/en.php @@ -0,0 +1,8 @@ +tag_default_config = 'Default Config'; +$lang->tag_separators = 'Tag Separators'; +$lang->tag_separator_comma = 'Comma (foo, bar)'; +$lang->tag_separator_hash = 'Hash (#foo #bar)'; +$lang->tag_separator_space = 'Space (foo bar)'; +$lang->tag_separator_help = 'If you select more than one type of separator, all of them will be recognized. For example, "#foo,bar Rhymix"'; diff --git a/modules/tag/lang/ko.php b/modules/tag/lang/ko.php new file mode 100644 index 000000000..d2a18c58c --- /dev/null +++ b/modules/tag/lang/ko.php @@ -0,0 +1,8 @@ +tag_default_config = '태그 기본 설정'; +$lang->tag_separators = '태그 구분 방법'; +$lang->tag_separator_comma = '쉼표(foo, bar)'; +$lang->tag_separator_hash = '해시(#foo #bar)'; +$lang->tag_separator_space = '공백'; +$lang->tag_separator_help = '두 가지 이상 선택할 경우 모두 인식합니다. 예: "#foo,bar Rhymix" 입력시 해시태그와 쉼표, 공백 모두 구분자로 인식할 수 있습니다.'; diff --git a/modules/tag/queries/getAllTagList.xml b/modules/tag/queries/getAllTagList.xml index c57852419..eb1a911bf 100644 --- a/modules/tag/queries/getAllTagList.xml +++ b/modules/tag/queries/getAllTagList.xml @@ -1,4 +1,4 @@ - + diff --git a/modules/tag/queries/getTagList.xml b/modules/tag/queries/getTagList.xml index 22323044c..b5d20d5fa 100644 --- a/modules/tag/queries/getTagList.xml +++ b/modules/tag/queries/getTagList.xml @@ -13,7 +13,8 @@ - + + diff --git a/modules/tag/schemas/tags.xml b/modules/tag/schemas/tags.xml index 452e567eb..2064202b6 100644 --- a/modules/tag/schemas/tags.xml +++ b/modules/tag/schemas/tags.xml @@ -2,6 +2,6 @@ - +
                diff --git a/modules/tag/tag.admin.controller.php b/modules/tag/tag.admin.controller.php index 50d947ce5..4766dd574 100644 --- a/modules/tag/tag.admin.controller.php +++ b/modules/tag/tag.admin.controller.php @@ -5,8 +5,36 @@ * @author NAVER (developers@xpressengine.com) * @brief admin controller class of the tag module */ -class tagAdminController extends tag +class TagAdminController extends Tag { + /** + * Save admin config. + */ + public function procTagAdminInsertConfig() + { + $config = new stdClass; + $vars = Context::getRequestVars(); + + $config->separators = []; + foreach ($vars->separators ?? [] as $val) + { + if (in_array($val, ['comma', 'hash', 'space'])) + { + $config->separators[] = $val; + } + } + + $oModuleController = ModuleController::getInstance(); + $output = $oModuleController->insertModuleConfig($this->module, $config); + if (!$output->toBool()) + { + return $output; + } + + $this->setMessage('success_registed'); + $this->setRedirectUrl(Context::get('success_return_url')); + } + /** * @brief Delete all tags for a particular module */ diff --git a/modules/tag/tag.admin.view.php b/modules/tag/tag.admin.view.php new file mode 100644 index 000000000..f3d30292a --- /dev/null +++ b/modules/tag/tag.admin.view.php @@ -0,0 +1,13 @@ +module) ?: new stdClass; + Context::set('tag_config', $config); + + $this->setTemplatePath($this->module_path . 'tpl'); + $this->setTemplateFile('config'); + } +} diff --git a/modules/tag/tag.class.php b/modules/tag/tag.class.php index 987049bb6..6e44ad449 100644 --- a/modules/tag/tag.class.php +++ b/modules/tag/tag.class.php @@ -5,91 +5,48 @@ * @author NAVER (developers@xpressengine.com) * @brief high class of the tag module */ -class tag extends ModuleObject +class Tag extends ModuleObject { /** * @brief Implement if additional tasks are necessary when installing */ - function moduleInstall() + public function moduleInstall() { - $oModuleController = getController('module'); - $oDB = &DB::getInstance(); - $oDB->addIndex("tags","idx_tag", array("document_srl","tag")); - // 2007. 10. 17 document.insertDocument, updateDocument, deleteDocument trigger property for - $oModuleController->insertTrigger('document.insertDocument', 'tag', 'controller', 'triggerArrangeTag', 'before'); - $oModuleController->insertTrigger('document.insertDocument', 'tag', 'controller', 'triggerInsertTag', 'after'); - $oModuleController->insertTrigger('document.updateDocument', 'tag', 'controller', 'triggerArrangeTag', 'before'); - $oModuleController->insertTrigger('document.updateDocument', 'tag', 'controller', 'triggerInsertTag', 'after'); - $oModuleController->insertTrigger('document.deleteDocument', 'tag', 'controller', 'triggerDeleteTag', 'after'); - // 2007. 10. 17 modules are deleted when you delete all registered triggers that add tag - $oModuleController->insertTrigger('module.deleteModule', 'tag', 'controller', 'triggerDeleteModuleTags', 'after'); } /** * @brief a method to check if successfully installed */ - function checkUpdate() + public function checkUpdate() { - $oModuleModel = getModel('module'); - $oDB = &DB::getInstance(); - // 2007. 10. 17 trigger registration, if registered upset - if(!$oModuleModel->getTrigger('document.insertDocument', 'tag', 'controller', 'triggerArrangeTag', 'before')) return true; - if(!$oModuleModel->getTrigger('document.insertDocument', 'tag', 'controller', 'triggerInsertTag', 'after')) return true; - if(!$oModuleModel->getTrigger('document.updateDocument', 'tag', 'controller', 'triggerArrangeTag', 'before')) return true; - if(!$oModuleModel->getTrigger('document.updateDocument', 'tag', 'controller', 'triggerInsertTag', 'after')) return true; - if(!$oModuleModel->getTrigger('document.deleteDocument', 'tag', 'controller', 'triggerDeleteTag', 'after')) return true; - // 2007. 10. 17 modules are deleted when you delete all registered triggers that add tag - if(!$oModuleModel->getTrigger('module.deleteModule', 'tag', 'controller', 'triggerDeleteModuleTags', 'after')) return true; - // tag in the index column of the table tag - if(!$oDB->isIndexExists("tags","idx_tag")) return true; - if(!$oModuleModel->getTrigger('document.moveDocumentModule', 'tag', 'controller', 'triggerMoveDocument', 'after')) return true; - + // Convert unnecessary composite index into a single-column index. + $oDB = DB::getInstance(); + $index = $oDB->getIndexInfo('tags', 'idx_tag'); + if (!$index || count($index->columns) > 1) + { + return true; + } return false; } /** * @brief Execute update */ - function moduleUpdate() + public function moduleUpdate() { - $oModuleModel = getModel('module'); - $oModuleController = getController('module'); - $oDB = &DB::getInstance(); - // 2007. 10. 17 document.insertDocument, updateDocument, deleteDocument trigger property for - if(!$oModuleModel->getTrigger('document.insertDocument', 'tag', 'controller', 'triggerArrangeTag', 'before')) - $oModuleController->insertTrigger('document.insertDocument', 'tag', 'controller', 'triggerArrangeTag', 'before'); - - if(!$oModuleModel->getTrigger('document.insertDocument', 'tag', 'controller', 'triggerInsertTag', 'after')) - $oModuleController->insertTrigger('document.insertDocument', 'tag', 'controller', 'triggerInsertTag', 'after'); - - if(!$oModuleModel->getTrigger('document.updateDocument', 'tag', 'controller', 'triggerArrangeTag', 'before')) - $oModuleController->insertTrigger('document.updateDocument', 'tag', 'controller', 'triggerArrangeTag', 'before'); - - if(!$oModuleModel->getTrigger('document.updateDocument', 'tag', 'controller', 'triggerInsertTag', 'after')) - $oModuleController->insertTrigger('document.updateDocument', 'tag', 'controller', 'triggerInsertTag', 'after'); - - if(!$oModuleModel->getTrigger('document.deleteDocument', 'tag', 'controller', 'triggerDeleteTag', 'after')) - $oModuleController->insertTrigger('document.deleteDocument', 'tag', 'controller', 'triggerDeleteTag', 'after'); - // 2007. 10. 17 modules are deleted when you delete all registered triggers that add tag - if(!$oModuleModel->getTrigger('module.deleteModule', 'tag', 'controller', 'triggerDeleteModuleTags', 'after')) - $oModuleController->insertTrigger('module.deleteModule', 'tag', 'controller', 'triggerDeleteModuleTags', 'after'); - // tag in the index column of the table tag - if(!$oDB->isIndexExists("tags","idx_tag")) - $oDB->addIndex("tags","idx_tag", array("document_srl","tag")); - - if(!$oModuleModel->getTrigger('document.moveDocumentModule', 'tag', 'controller', 'triggerMoveDocument', 'after')) + // Convert unnecessary composite index into a single-column index. + $oDB = DB::getInstance(); + $index = $oDB->getIndexInfo('tags', 'idx_tag'); + if (!$index || count($index->columns) > 1) { - $oModuleController->insertTrigger('document.moveDocumentModule', 'tag', 'controller', 'triggerMoveDocument', 'after'); + if ($index) + { + $oDB->dropIndex('tags', 'idx_tag'); + } + $oDB->addIndex('tags', 'idx_tag', ['tag']); } } - - /** - * @brief Re-generate the cache file - */ - function recompileCache() - { - } } /* End of file tag.class.php */ /* Location: ./modules/tag/tag.class.php */ diff --git a/modules/tag/tag.controller.php b/modules/tag/tag.controller.php index ce4b35603..69797f56f 100644 --- a/modules/tag/tag.controller.php +++ b/modules/tag/tag.controller.php @@ -5,81 +5,82 @@ * @author NAVER (developers@xpressengine.com) * @brief tag module's controller class */ -class tagController extends tag +class TagController extends Tag { /** - * @brief Initialization - */ - function init() - { - } - - /** - * @brief , (Comma) to clean up the tags attached to the trigger + * Parse tags. */ function triggerArrangeTag(&$obj) { - if(!$obj->tags) return; - // tags by variable - $arranged_tag_list = array(); - $tag_list = explode(',', $obj->tags); - foreach($tag_list as $tag) + if (empty($obj->tags)) { - $tag = utf8_trim(utf8_normalize_spaces($tag)); - if($tag) - { - $arranged_tag_list[$tag] = $tag; - } + $obj->tags = ''; + return; } - if(!count($arranged_tag_list)) + + $tag_list = tagModel::splitString($obj->tags ?? ''); + if (count($tag_list)) { - $obj->tags = null; + $obj->tags = implode(', ', $tag_list); } else { - $obj->tags = implode(',', $arranged_tag_list); + $obj->tags = ''; } } /** - * @brief Input trigger tag - * Enter a Tag to delete that article and then re-enter all the tags using a method + * Replace all tags belonging to a document with a new list of tags. + * + * @param object $obj + * @return BaseObject */ function triggerInsertTag(&$obj) { - $module_srl = $obj->module_srl; - $document_srl = $obj->document_srl; - $tags = $obj->tags; - if(!$document_srl) return; - // Remove all tags that article + if (!$obj->document_srl) + { + return new BaseObject; + } + + // Remove all existing tags. $output = $this->triggerDeleteTag($obj); - if(!$output->toBool()) return $output; + if(!$output->toBool()) + { + return $output; + } + // Re-enter the tag $args = new stdClass(); - $args->module_srl = $module_srl; - $args->document_srl = $document_srl; + $args->module_srl = $obj->module_srl; + $args->document_srl = $obj->document_srl; - $tag_list = explode(',', $tags); - foreach($tag_list as $tag) + $tag_list = tagModel::splitString($obj->tags ?? ''); + foreach ($tag_list as $tag) { - $args->tag = utf8_trim(utf8_normalize_spaces($tag)); - if(!$args->tag) continue; + $args->tag = $tag; $output = executeQuery('tag.insertTag', $args); - if(!$output->toBool()) return $output; + if(!$output->toBool()) + { + return $output; + } } } /** - * @brief Delete the tag trigger a specific article - * document_srl delete tag belongs to + * Delete all tags belonging to a document. + * + * @param object $obj + * @return BaseObject */ function triggerDeleteTag(&$obj) { - $document_srl = $obj->document_srl; - if(!$document_srl) return; + if (!$obj->document_srl) + { + return new BaseObject; + } $args = new stdClass(); - $args->document_srl = $document_srl; + $args->document_srl = $obj->document_srl; return executeQuery('tag.deleteTag', $args); } @@ -88,13 +89,15 @@ class tagController extends tag */ function triggerDeleteModuleTags(&$obj) { - $module_srl = $obj->module_srl; - if(!$module_srl) return; + if (!$obj->module_srl) + { + return; + } $oTagController = getAdminController('tag'); - return $oTagController->deleteModuleTags($module_srl); + return $oTagController->deleteModuleTags($obj->module_srl); } - + function triggerMoveDocument($obj) { executeQuery('tag.updateTagModule', $obj); diff --git a/modules/tag/tag.model.php b/modules/tag/tag.model.php index 682799058..78f24b094 100644 --- a/modules/tag/tag.model.php +++ b/modules/tag/tag.model.php @@ -5,41 +5,98 @@ * @author NAVER (developers@xpressengine.com) * @brief tag model class of the module */ -class tagModel extends tag +class TagModel extends Tag { /** - * @brief Initialization + * Separator regexp cache */ - function init() + protected static $_separator_list = null; + protected static $_separator_regexp = null; + + /** + * Generate and cache separator list and regexp. + */ + protected static function _generateSeparatorConfig() { + $config = ModuleModel::getModuleConfig('tag'); + if (isset($config->separators) && count($config->separators)) + { + self::$_separator_list = $config->separators; + } + else + { + self::$_separator_list = ['comma', 'hash']; + } + + $regexp = '/['; + $regexp_map = [ + 'comma' => ',', + 'hash' => '#', + 'space' => '\\s', + ]; + foreach (self::$_separator_list as $separator) + { + $regexp .= $regexp_map[$separator]; + } + $regexp .= ']+/'; + + self::$_separator_regexp = $regexp; + } + + /** + * Split a string of tags into an array. + * + * @param string $str + * @return array + */ + public static function splitString(string $str): array + { + if (!isset(self::$_separator_list)) + { + self::_generateSeparatorConfig(); + } + + // Clean up the input string. + $str = trim(utf8_normalize_spaces(utf8_clean($str))); + if ($str === '') + { + return []; + } + + // Split the input string and collect non-empty fragments. + $fragments = preg_split(self::$_separator_regexp, $str, -1, PREG_SPLIT_NO_EMPTY); + $tags = []; + foreach ($fragments as $fragment) + { + $fragment = trim($fragment); + if ($fragment !== '') + { + $tags[strtolower($fragment)] = $fragment; + } + } + + // Return a list of valid fragments with no duplicates. + return array_values(array_unique($tags)); } /** * @brief Imported Tag List * Many of the specified module in order to extract the number of tags */ - function getTagList($obj) + public static function getTagList($obj) { - if($obj->mid) + if(!empty($obj->mid)) { - $oModuleModel = getModel('module'); - $obj->module_srl = $oModuleModel->getModuleSrlByMid($obj->mid); + $obj->module_srl = ModuleModel::getModuleSrlByMid($obj->mid); unset($obj->mid); } // Module_srl passed the array may be a check whether the array $args = new stdClass; - if(is_array($obj->module_srl)) - { - $args->module_srl = implode(',', $obj->module_srl); - } - else - { - $args->module_srl = $obj->module_srl; - } - - $args->list_count = $obj->list_count; - $args->count = $obj->sort_index; + $args->module_srl = $obj->module_srl; + $args->list_count = $obj->list_count ?? null; + $args->sort_index = $obj->sort_index ?? null; + $args->order_type = $obj->order_type ?? null; $output = executeQueryArray('tag.getTagList', $args); if(!$output->toBool()) return $output; diff --git a/modules/tag/tpl/config.html b/modules/tag/tpl/config.html new file mode 100644 index 000000000..faffd857c --- /dev/null +++ b/modules/tag/tpl/config.html @@ -0,0 +1,50 @@ + + +
                +

                {$lang->tag}

                +
                + + + + + + + + + +
                +

                {$XE_VALIDATOR_MESSAGE}

                +
                + +
                +
                + +
                + + + +

                + {$lang->tag_separator_help} +

                +
                +
                +
                + +
                + +
                + + diff --git a/modules/trash/conf/info.xml b/modules/trash/conf/info.xml index d8afb2793..a04dcc1bb 100644 --- a/modules/trash/conf/info.xml +++ b/modules/trash/conf/info.xml @@ -1,13 +1,14 @@ 휴지통 - trash + Recycle Bin ゴミ箱 回收桶 문서, 댓글 등을 완전히 삭제하지 않고 복구 가능한 상태로 만들어 관리합니다. ドミュメンと、コメントなどを完全に削除せずに復旧可能な状態に作って管理します。 - 1.7 - 2013-11-27 + Recycle Bin is a logical reservoir for deleted items, such as posts and comments, which enables the administrators to restore the deleted items. + RX_VERSION + RX_CORE content diff --git a/modules/trash/conf/module.xml b/modules/trash/conf/module.xml index 18b3013a1..0607b2277 100644 --- a/modules/trash/conf/module.xml +++ b/modules/trash/conf/module.xml @@ -11,7 +11,7 @@ - Trash + Recycle Bin 휴지통 Trash ゴミ箱 diff --git a/modules/trash/lang/en.php b/modules/trash/lang/en.php index d10bc7fda..1bf7b8bde 100644 --- a/modules/trash/lang/en.php +++ b/modules/trash/lang/en.php @@ -2,7 +2,7 @@ $lang->cmd_trash = 'Recycle Bin'; $lang->cmd_restore_all = 'Restore All'; $lang->in_trash = 'Recycle Bin'; -$lang->trash_date = 'Deleted date'; +$lang->trash_date = 'Deleted Date'; $lang->trash_description = 'Description'; $lang->success_trashed = 'Moved to Recycle Bin.'; $lang->empty_trash_all = 'Empty Recycle Bin'; @@ -12,10 +12,18 @@ $lang->fail_empty = 'Failed to empty Recycle Bin.'; $lang->success_restore = 'Restored successfully.'; $lang->fail_restore = 'Failed to restore.'; $lang->origin_module_type = 'Type'; -$lang->remove_all_trash_item = 'Remove all trash items. After execute could not be recovered.'; +$lang->remove_all_trash_item = 'Remove all trash items. After execution, the items can not be recovered.'; $lang->title = 'Subject'; $lang->content = 'Content'; $lang->trasher = 'Deleter'; $lang->origin_info = 'Document Info'; $lang->delete_info = 'Trash Info'; $lang->cmd_restore = 'Restore'; +$lang->cmd_trash_list = 'Recycle Bin Items'; +$lang->cmd_trash_type = 'Type Selection'; +$lang->trash_warning = 'Caution!'; +$lang->trash_nick_name = 'Deleted By'; +$lang->search_target_trash_list['title'] = 'Title'; +$lang->search_target_trash_list['user_id'] = 'User ID'; +$lang->search_target_trash_list['nick_name'] = 'Nickname'; +$lang->search_target_trash_list['trash_ipaddress'] = 'IP Address'; diff --git a/modules/trash/model/TrashVO.php b/modules/trash/model/TrashVO.php index f7b62dbe7..8c9e27c41 100644 --- a/modules/trash/model/TrashVO.php +++ b/modules/trash/model/TrashVO.php @@ -24,8 +24,8 @@ class TrashVO } function getTitle() { - if(empty($this->title)) return $lang->untitle; - return htmlspecialchars($this->title, ENT_COMPAT | ENT_HTML401, 'UTF-8', false); + if(empty($this->title)) return lang('untitle'); + return escape($this->title, false); } function setTitle($title) { @@ -90,7 +90,7 @@ class TrashVO } function getNickName() { - return htmlspecialchars($this->nickName, ENT_COMPAT | ENT_HTML401, 'UTF-8', false); + return escape($this->nickName, false); } function setNickName($nickName) { diff --git a/modules/trash/queries/getTrashAllList.xml b/modules/trash/queries/getTrashAllList.xml index 67ab0e2ba..11c4be618 100644 --- a/modules/trash/queries/getTrashAllList.xml +++ b/modules/trash/queries/getTrashAllList.xml @@ -1,4 +1,4 @@ - +
                diff --git a/modules/trash/schemas/trash.xml b/modules/trash/schemas/trash.xml index ee69509da..97c03cec2 100644 --- a/modules/trash/schemas/trash.xml +++ b/modules/trash/schemas/trash.xml @@ -1,10 +1,10 @@
                - + - - + +
                diff --git a/modules/trash/tpl/trash_list.html b/modules/trash/tpl/trash_list.html index 48e9224f5..6cabb10ec 100644 --- a/modules/trash/tpl/trash_list.html +++ b/modules/trash/tpl/trash_list.html @@ -54,13 +54,20 @@ var no_text_comment = '{$lang->no_text_comment}'; {$lang->document}{$lang->comment} - {$lang->no_text_comment} - - - {$module_list[$oTrashVO->unserializedObject['module_srl']]->browser_title} - - - {$oTrashVO->getTitle()} - + + + {$module_list[$oTrashVO->unserializedObject['module_srl']]->browser_title} - + + {$oTrashVO->getTitle()} + + + + {$lang->no_text_comment} + + {$lang->no_text_document} + + + {$oTrashVO->unserializedObject['nick_name']} {$oTrashVO->unserializedObject['ipaddress']} @@ -122,10 +129,10 @@ var no_text_comment = '{$lang->no_text_comment}'; - + {$lang->cmd_cancel} diff --git a/modules/trash/tpl/trash_view.html b/modules/trash/tpl/trash_view.html index b1292c1a9..00d87c3f4 100644 --- a/modules/trash/tpl/trash_view.html +++ b/modules/trash/tpl/trash_view.html @@ -53,7 +53,7 @@ {$lang->content} - {$oOrigin->content} + {$oOrigin->content|noescape} diff --git a/modules/trash/trash.admin.controller.php b/modules/trash/trash.admin.controller.php index b863e4613..5064dbed4 100644 --- a/modules/trash/trash.admin.controller.php +++ b/modules/trash/trash.admin.controller.php @@ -8,23 +8,20 @@ * @package /modules/trash * @version 0.1 */ -class trashAdminController extends trash +class TrashAdminController extends Trash { /** * object insert to trash - * @param TrashVO $obj + * @param TrashVO $oTrashVO * @return Object */ - function insertTrash($obj) + function insertTrash($oTrashVO) { $logged_info = Context::get('logged_info'); - $oTrashVO = new TrashVO(); - $oTrashVO = &$obj; - if(!$oTrashVO->getTrashSrl()) $oTrashVO->setTrashSrl(getNextSequence()); if(!is_string($oTrashVO->getSerializedObject())) $oTrashVO->setSerializedObject(serialize($oTrashVO->getSerializedObject())); - $oTrashVO->setIpaddress($_SERVER['REMOTE_ADDR']); + $oTrashVO->setIpaddress(\RX_CLIENT_IP); $oTrashVO->setRemoverSrl($logged_info->member_srl); $oTrashVO->setRegdate(date('YmdHis')); @@ -127,7 +124,7 @@ class trashAdminController extends trash if(!$output2->toBool()) return $output; } } - + return new BaseObject(0, 'success_deleted'); } @@ -143,7 +140,7 @@ class trashAdminController extends trash if(is_array($trashSrlList)) { // begin transaction - $oDB = &DB::getInstance(); + $oDB = DB::getInstance(); $oDB->begin(); // eache restore method call in each classfile foreach($trashSrlList as $value) diff --git a/modules/trash/trash.admin.view.php b/modules/trash/trash.admin.view.php index 12a5fc2ae..2c898f3e3 100644 --- a/modules/trash/trash.admin.view.php +++ b/modules/trash/trash.admin.view.php @@ -8,7 +8,7 @@ * @package /modules/trash * @version 0.1 */ -class trashAdminView extends trash +class TrashAdminView extends Trash { /** * Initialization @@ -16,6 +16,10 @@ class trashAdminView extends trash */ function init() { + // 문서 및 댓글 모듈 lang 파일 로딩 + Context::loadLang('./modules/document/lang'); + Context::loadLang('./modules/comment/lang'); + // 템플릿 경로 지정 (board의 경우 tpl에 관리자용 템플릿 모아놓음) $template_path = sprintf("%stpl/",$this->module_path); $this->setTemplatePath($template_path); @@ -35,7 +39,7 @@ class trashAdminView extends trash $search_target = Context::get('search_target'); // /< search (title, contents ...) $search_keyword = Context::get('search_keyword'); // /< keyword to search - + switch($search_target) { case 'title': @@ -55,6 +59,7 @@ class trashAdminView extends trash $oTrashModel = getModel('trash'); $output = $oTrashModel->getTrashList($args); + Context::set('originModule', strval($args->originModule)); Context::set('trash_list', $output->data); Context::set('total_count', $output->total_count); Context::set('total_page', $output->total_page); @@ -89,10 +94,10 @@ class trashAdminView extends trash // 템플릿 파일 지정 $this->setTemplateFile('trash_list'); } - - + + // Trash View - sejin7940 - function dispTrashAdminView() + function dispTrashAdminView() { $trash_srl = Context::get('trash_srl'); @@ -106,11 +111,11 @@ class trashAdminView extends trash Context::set('oTrashVO',$output->data); Context::set('oOrigin',$originObject); - $oMemberModel = &getModel('member'); + $oMemberModel = getModel('member'); $remover_info = $oMemberModel->getMemberInfoByMemberSrl($output->data->getRemoverSrl()); Context::set('remover_info', $remover_info); - $oModuleModel = &getModel('module'); + $oModuleModel = getModel('module'); $module_info = $oModuleModel->getModuleInfoByModuleSrl($originObject->module_srl); Context::set('module_info', $module_info); @@ -118,7 +123,7 @@ class trashAdminView extends trash $args_extra = new stdClass; $args_extra->module_srl = $originObject->module_srl; $args_extra->document_srl = $originObject->document_srl; - $output_extra = executeQueryArray('trash.getDocumentExtraVars', $args_extra); + $output_extra = executeQueryArray('trash.getDocumentExtraVars', $args_extra); Context::set('oOriginExtraVars',$output_extra->data); } $this->setTemplateFile('trash_view'); diff --git a/modules/trash/trash.class.php b/modules/trash/trash.class.php index ea025f69a..372df1f6d 100644 --- a/modules/trash/trash.class.php +++ b/modules/trash/trash.class.php @@ -1,6 +1,6 @@ */ -require_once(_XE_PATH_.'modules/trash/model/TrashVO.php'); +require_once(RX_BASEDIR.'modules/trash/model/TrashVO.php'); /** * trash class @@ -10,7 +10,7 @@ require_once(_XE_PATH_.'modules/trash/model/TrashVO.php'); * @package /modules/trash * @version 0.1 */ -class trash extends ModuleObject +class Trash extends ModuleObject { /** * Implement if additional tasks are necessary when installing @@ -18,7 +18,7 @@ class trash extends ModuleObject */ function moduleInstall() { - + } /** @@ -36,7 +36,7 @@ class trash extends ModuleObject */ function moduleUpdate() { - + } } /* End of file trash.class.php */ diff --git a/modules/trash/trash.controller.php b/modules/trash/trash.controller.php index 809dda99c..3e0b9f0c1 100644 --- a/modules/trash/trash.controller.php +++ b/modules/trash/trash.controller.php @@ -8,7 +8,7 @@ * @package /modules/trash * @version 0.1 */ -class trashController extends trash +class TrashController extends Trash { } diff --git a/modules/trash/trash.model.php b/modules/trash/trash.model.php index 089eddd84..6c13d2015 100644 --- a/modules/trash/trash.model.php +++ b/modules/trash/trash.model.php @@ -8,7 +8,7 @@ * @package /modules/trash * @version 0.1 */ -class trashModel extends trash +class TrashModel extends Trash { private static $config = NULL; diff --git a/modules/trash/trash.view.php b/modules/trash/trash.view.php index 1562d133c..c5f359547 100644 --- a/modules/trash/trash.view.php +++ b/modules/trash/trash.view.php @@ -8,7 +8,7 @@ * @package /modules/trash * @version 0.1 */ -class trashView extends trash +class TrashView extends Trash { /** * Initialization diff --git a/modules/widget/conf/info.xml b/modules/widget/conf/info.xml index 98b0d06ee..13f9ad063 100644 --- a/modules/widget/conf/info.xml +++ b/modules/widget/conf/info.xml @@ -18,8 +18,8 @@ Модуль для управления виджетами. Widget 管理模組。 Widgetları yönetmek için kullanılan modüldür. - 1.7 - 2013-11-27 + RX_VERSION + RX_CORE construction diff --git a/modules/widget/conf/module.xml b/modules/widget/conf/module.xml index 08a43a33f..95a7e000f 100644 --- a/modules/widget/conf/module.xml +++ b/modules/widget/conf/module.xml @@ -6,7 +6,7 @@ - + @@ -14,11 +14,14 @@ - + + + + Widgets diff --git a/modules/widget/tpl/add_content_widget.html b/modules/widget/tpl/add_content_widget.html index ac8093ecd..6662411f2 100644 --- a/modules/widget/tpl/add_content_widget.html +++ b/modules/widget/tpl/add_content_widget.html @@ -16,7 +16,7 @@
                {$editor|noescape}
                - +
                -{@$suggestion_id = 0} +{@ $group = ''; $not_first = false; $suggestion_id = 0; } - {@$suggestion_id++} + {@ $suggestion_id++; }

                {$var->group}

                - {@$group = $var->group} + {@ $group = $var->group; } {@$not_first = true} @@ -53,21 +53,21 @@
                - + - + - + {@$inits = array_keys($var->init_options)} @@ -76,7 +76,7 @@

                @@ -84,7 +84,7 @@

                diff --git a/modules/widget/tpl/widget_generate_code_in_page.html b/modules/widget/tpl/widget_generate_code_in_page.html index 7c17c8019..d30ca6c56 100644 --- a/modules/widget/tpl/widget_generate_code_in_page.html +++ b/modules/widget/tpl/widget_generate_code_in_page.html @@ -5,10 +5,10 @@ + + + +
                  +
                • Hello <"world"> ('string') variable.jpg
                • +
                • Hello <"world"> ('string') variable.jpg
                • +
                diff --git a/tests/_data/template/v2contextual.html b/tests/_data/template/v2contextual.html new file mode 100644 index 000000000..e8c690844 --- /dev/null +++ b/tests/_data/template/v2contextual.html @@ -0,0 +1,29 @@ +@version(2) + + + {{ $var }} +

                + +

                + + {{ $var }} + +

                + + + + + +
                  +
                • {{ $var }}
                • +
                • {{ $var|noescape }}
                • +
                diff --git a/tests/_data/template/v2example.compiled.html b/tests/_data/template/v2example.compiled.html new file mode 100644 index 000000000..527c51341 --- /dev/null +++ b/tests/_data/template/v2example.compiled.html @@ -0,0 +1,93 @@ +config->version = 2; ?> + + +
                _v2_include('include', '^/common/tpl/refresh.html'); ?>
                +
                _v2_loadResource('^/common/js/plugins/ckeditor/'); ?>
                +_v2_loadResource('css/style.scss', 'print', '', []); ?> + +foo = 'FOOFOO<"FOO">BAR'; +?> +bar = ['Rhy', 'miX', 'is', 'da', 'BEST!']; +?> + + {{ $foo }} + + +
                + + get('foo')): ?> required="required"> + bar[3] === 'da'): ?> required="required" /> +
                + +baz))): ?> class="foobar"> +foo || $__Context->bar): ?> +

                Hello bar): ?>foo ?? ''; ?>

                +

                config->context === 'HTML' ? htmlspecialchars(implode('|', array_map(function($i) { return strtoupper($i); }, $__Context->bar)), \ENT_QUOTES, 'UTF-8', false) : $this->_v2_escape(implode('|', array_map(function($i) { return strtoupper($i); }, $__Context->bar))); ?>

                + +
                + + +_v2_initLoopVar("RANDOM_LOOP_ID", $__tmp_RANDOM_LOOP_ID); foreach ($__tmp_RANDOM_LOOP_ID as $__Context->k => $__Context->val): ?> +
                +nosuchvar)): ?> + unit tests are cool + k >= 2): ?>class="config->context === 'HTML' ? htmlspecialchars($__Context->val ?? '', \ENT_QUOTES, 'UTF-8', false) : $this->_v2_escape($__Context->val ?? ''); ?>"> + +
                +_v2_incrLoopVar($__loop_RANDOM_LOOP_ID); endforeach; $this->_v2_removeLoopVar($__loop_RANDOM_LOOP_ID); unset($__loop_RANDOM_LOOP_ID); else: ?>
                Nothing here...
                +_fragments[$__last_fragment_name] = ob_get_flush(); ?> + +_v2_include("include", $__filename, [(string)$__varname => $__var]); endforeach; })('incl/eachtest', $__Context->bar, 'var'); ?> +_v2_include("include", $__filename, [(string)$__varname => $__var]); endforeach; })('incl/eachtest', [], 'anything', 'incl/empty'); ?> + +_v2_isMobile()): ?> +

                The full class name is , config->context === 'HTML' ? htmlspecialchars(Rhymix\Framework\Push::class, \ENT_QUOTES, 'UTF-8', false) : $this->_v2_escape(Rhymix\Framework\Push::class); ?> really.

                + + +
                + _v2_buildAttribute('class', [ + 'a-1', + 'font-normal' => $__Context->foo, + 'text-blue' => false, + 'bg-gray-200' => true + ]); ?>> + _v2_buildAttribute('style', [ + 'border-radius: 0.25rem', + 'margin: 1rem' => Context::get('bar'), + 'padding: 2rem' => false, + ]); ?>> + _v2_buildAttribute('class', ['a' => false, 'b' => false]); ?>> + _v2_buildAttribute('style', ['a' => false, 'b' => false]); ?>> +
                + +suffix; + $__Context->employees = [ + ['name' => 'Alice', 'age' => 30], + ['name' => 'Bob', 'age' => 25], + ['name' => 'Charlie', 'age' => 35], + ]; + $__Context->suffix = '님'; + $__Context->names = array_map(function($e, $key = 'name') use ($suffix) { + return $e[$key] . $suffix; + }, $__Context->employees); + function convert_names(array $names = array()): array + { + return array_map(function($name) { + return ucfirst($name); + }, $names); + } +?> +
                + config->context === 'HTML' ? htmlspecialchars(implode(', ', convert_names($__Context->names)), \ENT_QUOTES, 'UTF-8', false) : $this->_v2_escape(implode(', ', convert_names($__Context->names))); ?> welcome! +
                + + + +config->context = 'CSS'; ?>> + body { background-color: config->context === 'HTML' ? htmlspecialchars('#ffffff', \ENT_QUOTES, 'UTF-8', false) : $this->_v2_escape('#ffffff'); ?>; } +config->context = 'HTML'; ?> diff --git a/tests/_data/template/v2example.executed.html b/tests/_data/template/v2example.executed.html new file mode 100644 index 000000000..61bd78c00 --- /dev/null +++ b/tests/_data/template/v2example.executed.html @@ -0,0 +1,72 @@ + + +
                +
                +
                + + + {{ $foo }} + + +
                + + + +
                + +
                +

                Hello FOOFOO<"FOO">BAR

                +

                RHY|MIX|IS|DA|BEST!

                +
                + +
                + unit tests are cool + +
                +
                + unit tests are cool + +
                +
                + unit tests are cool + +
                +
                + unit tests are cool + +
                +
                + unit tests are cool + +
                + +
                Rhy
                +
                miX
                +
                is
                +
                da
                +
                BEST!
                +
                Empty
                + +

                The full class name is Rhymix\Framework\Push, Rhymix\Framework\Push really.

                + +
                + + + + +
                + +
                + Alice님, Bob님, Charlie님 welcome! +
                + + + + diff --git a/tests/_data/template/v2example.fragment.html b/tests/_data/template/v2example.fragment.html new file mode 100644 index 000000000..b57162d11 --- /dev/null +++ b/tests/_data/template/v2example.fragment.html @@ -0,0 +1,21 @@ + +
                + unit tests are cool + +
                +
                + unit tests are cool + +
                +
                + unit tests are cool + +
                +
                + unit tests are cool + +
                +
                + unit tests are cool + +
                diff --git a/tests/_data/template/v2example.html b/tests/_data/template/v2example.html new file mode 100644 index 000000000..2575051fa --- /dev/null +++ b/tests/_data/template/v2example.html @@ -0,0 +1,93 @@ + + +@use('Rhymix\Framework\Push', 'Push') +
                @include('^/common/tpl/refresh.html')
                +
                @load('^/common/js/plugins/ckeditor/')
                + + +BAR'; +?> +@php + $bar = ['Rhy', 'miX', 'is', 'da', 'BEST!']; +@endphp +@verbatim + {{ $foo }} +@endverbatim + +
                + @csrf + get('foo'))> + +
                + +
                + @if ($foo || $bar) +

                Hello @if ($bar){$foo|noescape}@endif

                +

                {{ implode('|', array_map(function(\$i) { return strtoupper(\$i); }, $bar)) }}

                + @end +
                + +@fragment('rhymix') +@forelse (Context::get('bar') as $k => $val) +
                + @empty ($nosuchvar) + unit tests are cool + = 2)-->class="{$val}"> + @endempty +
                +@empty
                Nothing here...
                @end +@endfragment + +@each('incl/eachtest', $bar, 'var') +@each('incl/eachtest', [], 'anything', 'incl/empty') + +@desktop +

                The full class name is {get_class(new Push)|escape}, {Push::class} really.

                +@enddesktop + +
                + $foo, + 'text-blue' => false, + 'bg-gray-200' => true + ])> + Context::get('bar'), + 'padding: 2rem' => false, + ])> + false, 'b' => false])> + false, 'b' => false])> +
                + +@php + $employees = [ + ['name' => 'Alice', 'age' => 30], + ['name' => 'Bob', 'age' => 25], + ['name' => 'Charlie', 'age' => 35], + ]; + $suffix = '님'; + $names = array_map(function($e, $key = 'name') use ($suffix) { + return $e[$key] . $suffix; + }, $employees); + function convert_names(array $names = array()): array + { + return array_map(function($name) { + return ucfirst($name); + }, $names); + } +@endphp +
                + {{ implode(', ', convert_names($names)) }} welcome! +
                + + + + diff --git a/tests/_data/template/v2lang.executed1.html b/tests/_data/template/v2lang.executed1.html new file mode 100644 index 000000000..2b2595192 --- /dev/null +++ b/tests/_data/template/v2lang.executed1.html @@ -0,0 +1,3 @@ + +

                comment.foobarfoobar

                +

                document.foobarbazbaz

                diff --git a/tests/_data/template/v2lang.executed2.html b/tests/_data/template/v2lang.executed2.html new file mode 100644 index 000000000..233a90847 --- /dev/null +++ b/tests/_data/template/v2lang.executed2.html @@ -0,0 +1,3 @@ + +

                comment.foobarfoobar

                +

                member.foobarbazbaz

                diff --git a/tests/_data/template/v2lang.html b/tests/_data/template/v2lang.html new file mode 100644 index 000000000..0937c114c --- /dev/null +++ b/tests/_data/template/v2lang.html @@ -0,0 +1,4 @@ +@version(2) + +

                @lang('comment.foobarfoobar')

                +

                @lang('this.foobarbazbaz')

                diff --git a/tests/_data/template/v2loops.executed.html b/tests/_data/template/v2loops.executed.html new file mode 100644 index 000000000..df4257ee2 --- /dev/null +++ b/tests/_data/template/v2loops.executed.html @@ -0,0 +1,179 @@ +

                Pets

                + +
                  +
                • + A red dog + current index 0 of 5 + parent index 0 of 5 + first: bool(true) + last: bool(false) +
                • +
                • + A red cat + current index 1 of 5 + parent index 0 of 5 + first: bool(false) + last: bool(false) +
                • +
                • + A red rabbit + current index 2 of 5 + parent index 0 of 5 + first: bool(false) + last: bool(false) +
                • +
                • + A red panda + current index 3 of 5 + parent index 0 of 5 + first: bool(false) + last: bool(false) +
                • +
                • + A red otter + current index 4 of 5 + parent index 0 of 5 + first: bool(false) + last: bool(true) +
                • +
                • + A blue dog + current index 0 of 5 + parent index 1 of 5 + first: bool(true) + last: bool(false) +
                • +
                • + A blue cat + current index 1 of 5 + parent index 1 of 5 + first: bool(false) + last: bool(false) +
                • +
                • + A blue rabbit + current index 2 of 5 + parent index 1 of 5 + first: bool(false) + last: bool(false) +
                • +
                • + A blue panda + current index 3 of 5 + parent index 1 of 5 + first: bool(false) + last: bool(false) +
                • +
                • + A blue otter + current index 4 of 5 + parent index 1 of 5 + first: bool(false) + last: bool(true) +
                • +
                • + A yellow dog + current index 0 of 5 + parent index 2 of 5 + first: bool(true) + last: bool(false) +
                • +
                • + A yellow cat + current index 1 of 5 + parent index 2 of 5 + first: bool(false) + last: bool(false) +
                • +
                • + A yellow rabbit + current index 2 of 5 + parent index 2 of 5 + first: bool(false) + last: bool(false) +
                • +
                • + A yellow panda + current index 3 of 5 + parent index 2 of 5 + first: bool(false) + last: bool(false) +
                • +
                • + A yellow otter + current index 4 of 5 + parent index 2 of 5 + first: bool(false) + last: bool(true) +
                • +
                • + A black dog + current index 0 of 5 + parent index 3 of 5 + first: bool(true) + last: bool(false) +
                • +
                • + A black cat + current index 1 of 5 + parent index 3 of 5 + first: bool(false) + last: bool(false) +
                • +
                • + A black rabbit + current index 2 of 5 + parent index 3 of 5 + first: bool(false) + last: bool(false) +
                • +
                • + A black panda + current index 3 of 5 + parent index 3 of 5 + first: bool(false) + last: bool(false) +
                • +
                • + A black otter + current index 4 of 5 + parent index 3 of 5 + first: bool(false) + last: bool(true) +
                • +
                • + A white dog + current index 0 of 5 + parent index 4 of 5 + first: bool(true) + last: bool(false) +
                • +
                • + A white cat + current index 1 of 5 + parent index 4 of 5 + first: bool(false) + last: bool(false) +
                • +
                • + A white rabbit + current index 2 of 5 + parent index 4 of 5 + first: bool(false) + last: bool(false) +
                • +
                • + A white panda + current index 3 of 5 + parent index 4 of 5 + first: bool(false) + last: bool(false) +
                • +
                • + A white otter + current index 4 of 5 + parent index 4 of 5 + first: bool(false) + last: bool(true) +
                • +
                diff --git a/tests/_data/template/v2loops.html b/tests/_data/template/v2loops.html new file mode 100644 index 000000000..33581416d --- /dev/null +++ b/tests/_data/template/v2loops.html @@ -0,0 +1,22 @@ +@version(2) + +@php + $foo = ['red', 'blue', 'yellow', 'black', 'white']; + $bar = ['dog', 'cat', 'rabbit', 'panda', 'otter']; +@endphp + +

                Pets

                + +
                  + @foreach ($foo as $color) + @foreach ($bar as $animal) +
                • + A {{ $color }} {{ $animal }} + current index {{ $loop->index }} of {{ $loop->count }} + parent index {{ $loop->parent->index }} of {{ $loop->parent->count }} + first: @dump($loop->first) + last: @dump($loop->last) +
                • + @endforeach + @endforeach +
                diff --git a/tests/_data/template/v2pushstack.executed.html b/tests/_data/template/v2pushstack.executed.html new file mode 100644 index 000000000..a5fa010f4 --- /dev/null +++ b/tests/_data/template/v2pushstack.executed.html @@ -0,0 +1,7 @@ + +
                  +
                • Rhymix
                • +
                • XE
                • +
                • WordPress
                • +
                • Drupal
                • +
                diff --git a/tests/_data/template/v2pushstack.html b/tests/_data/template/v2pushstack.html new file mode 100644 index 000000000..3e1ccdb62 --- /dev/null +++ b/tests/_data/template/v2pushstack.html @@ -0,0 +1,35 @@ +@version(2) + + + +@pushOnce('cms') +
              • XE
              • +@endpushOnce + +@pushonce('cms') +
              • XE
              • +@endPushOnce + +@push('cms') +
              • WordPress
              • +@end + +@pushIf($foo, 'cms') +
              • Drupal
              • +@endPushIf + +@prependOnce('cms') +
              • Rhymix
              • +@endPrependOnce + +@prependonce('cms') +
              • Rhymix
              • +@endprependonce + +@prependIf(!$foo, 'cms') +
              • Joomla
              • +@end + +
                  + @stack('cms') +
                diff --git a/tests/_data/template/v2validation.executed.html b/tests/_data/template/v2validation.executed.html new file mode 100644 index 000000000..523c85c1a --- /dev/null +++ b/tests/_data/template/v2validation.executed.html @@ -0,0 +1,11 @@ + +
                +

                You have an error!

                +

                [Ref. #2]

                +
                + +
                +

                You have an error!

                +

                [Ref. #4]

                +
                + diff --git a/tests/_data/template/v2validation.html b/tests/_data/template/v2validation.html new file mode 100644 index 000000000..0533563fa --- /dev/null +++ b/tests/_data/template/v2validation.html @@ -0,0 +1,41 @@ +@version(2) + + +@error('foo/bar/baz/1') +
                +

                {$XE_VALIDATOR_MESSAGE}

                +

                [Ref. #1]

                +
                +@enderror + + +{@ $XE_VALIDATOR_ID = 'foo/bar/baz/1'} +{@ $XE_VALIDATOR_MESSAGE = 'You have an error!'} + + +@error('foo/bar/baz/3', 'foo/bar/baz/2', 'foo/bar/baz/1') +
                +

                {$XE_VALIDATOR_MESSAGE}

                +

                [Ref. #2]

                +
                +@enderror + + +@error('foo/bar/baz/6', 'foo/bar/baz/5', 'foo/bar/baz/4') +
                +

                {$XE_VALIDATOR_MESSAGE}

                +

                [Ref. #3]

                +
                +@enderror + + +@error +
                +

                {$XE_VALIDATOR_MESSAGE}

                +

                [Ref. #4]

                +
                +@enderror + + +{@ $XE_VALIDATOR_ID = null} +{@ $XE_VALIDATOR_MESSAGE = null} diff --git a/tests/_data/template/v2varscope.executed.html b/tests/_data/template/v2varscope.executed.html new file mode 100644 index 000000000..a7b9b776b --- /dev/null +++ b/tests/_data/template/v2varscope.executed.html @@ -0,0 +1,18 @@ + +
                +
                Rhymix Template
                +
                Context Variable
                +
                + +
                +
                Included #1
                +
                +
                + +
                +
                Included #2
                +
                +
                Included #3
                +
                +
                +
                diff --git a/tests/_data/template/v2varscope.html b/tests/_data/template/v2varscope.html new file mode 100644 index 000000000..0988f1c03 --- /dev/null +++ b/tests/_data/template/v2varscope.html @@ -0,0 +1,18 @@ +@version(2) + + + +
                + @include ('incl/scopetest1.html') +
                + +
                + @include ('incl/scopetest1.html', ['foobar' => 'Included #1']) +
                + +
                + +
                diff --git a/tests/_data/xml/xecompat.xml b/tests/_data/xml/xecompat.xml new file mode 100644 index 000000000..85d6479bf --- /dev/null +++ b/tests/_data/xml/xecompat.xml @@ -0,0 +1,25 @@ + + + 기본 레이아웃 + Default Layout + + 라이믹스 + Rhymix + + + + 로고 이미지 + Logo Image + + + 웹 폰트 + Web Font + + Noto Sans + + + Pretendard + + + + diff --git a/tests/_data/xml/xmlrpc.xml b/tests/_data/xml/xmlrpc.xml new file mode 100644 index 000000000..5f18960ee --- /dev/null +++ b/tests/_data/xml/xmlrpc.xml @@ -0,0 +1,20 @@ + + + + + + + + <![CDATA[제목]]> + 내용

                +

                내용

                ]]> +
                + + + + + look here + + +
                +
                \ No newline at end of file diff --git a/tests/_support/InstallHelper.php b/tests/_support/InstallHelper.php index 77cd8f985..c6b98f1ce 100644 --- a/tests/_support/InstallHelper.php +++ b/tests/_support/InstallHelper.php @@ -5,7 +5,7 @@ use Codeception\Util\FileSystem; class InstallHelper extends \Codeception\Module { - public function _before() + public function _before(\Codeception\TestInterface $test) { FileSystem::deleteDir('files'); } diff --git a/tests/install.suite.dist.yml b/tests/install.suite.dist.yml index 74b51dc7a..bc98ba3bd 100644 --- a/tests/install.suite.dist.yml +++ b/tests/install.suite.dist.yml @@ -4,25 +4,14 @@ modules: config: PhpBrowser: url: 'http://localhost:8000/' + curl: + CURLOPT_TIMEOUT: 180 DbDropTablesHelper: dsn: 'mysql:host=localhost;dbname=rhymix' - user: 'travis' - password: 'travis' + user: 'rhymix' + password: 'rhymix' Db: dsn: 'mysql:host=localhost;dbname=rhymix' - user: 'travis' - password: 'travis' + user: 'rhymix' + password: 'rhymix' cleanup: true -env: - travis: - modules: - config: - Db: - dsn: 'mysql:host=localhost;dbname=rhymix' - user: 'travis' - password: 'travis' - cleanup: true - DbDropTablesHelper: - dsn: 'mysql:host=localhost;dbname=rhymix' - user: 'travis' - password: 'travis' diff --git a/tests/install/AutoinstallCept.php b/tests/install/AutoinstallCept.php index 0ed9e9429..95483e7a8 100644 --- a/tests/install/AutoinstallCept.php +++ b/tests/install/AutoinstallCept.php @@ -3,7 +3,14 @@ use \Codeception\Configuration; $I = new InstallTester($scenario); -$config = (!$this->env) ? Configuration::suiteSettings('install', Configuration::config()) : Configuration::suiteEnvironments('install')[$this->env]; +if (isset($this->env)) +{ + $config = Configuration::suiteEnvironments('install')[$this->env]; +} +else +{ + $config = Configuration::suiteSettings('install', Configuration::config()); +} $db_config = $config['modules']['config']['Db']; @@ -11,7 +18,7 @@ $dsn = $db_config['dsn']; $dsn = preg_split('/[;:]/', $dsn); $db_type = array_shift($dsn); $dbinfo = [ - 'type' => (($db_type === 'mysql') ? 'mysqli' : $db_type), + 'type' => $db_type, 'user' => $db_config['user'], 'password' => $db_config['password'], 'port' => ((isset($db_config['port']) && $db_config['port'])?: 3306), diff --git a/tests/install/InstallCept.php b/tests/install/InstallCept.php index 41ce1a164..07145ca10 100644 --- a/tests/install/InstallCept.php +++ b/tests/install/InstallCept.php @@ -3,7 +3,14 @@ use \Codeception\Configuration; $I = new InstallTester($scenario); -$config = (!$this->env) ? Configuration::suiteSettings('install', Configuration::config()) : Configuration::suiteEnvironments('install')[$this->env]; +if (isset($this->env)) +{ + $config = Configuration::suiteEnvironments('install')[$this->env]; +} +else +{ + $config = Configuration::suiteSettings('install', Configuration::config()); +} $db_config = $config['modules']['config']['Db']; @@ -44,10 +51,10 @@ $I->click('#task-checklist-confirm'); // Step 3 : DB Setup $I->seeInCurrentUrl('act=dispInstallDBConfig'); -$I->seeElement('select[name="db_type"]'); +$I->seeElement('input[name="db_host"]'); $I->submitForm('#body', [ 'act' => 'procDBConfig', - 'db_type' => 'mysqli', + 'db_type' => 'mysql', 'db_host' => $dbinfo['host'], 'db_port' => $dbinfo['port'], 'db_user' => $dbinfo['user'], diff --git a/tests/unit/classes/ContextTest.php b/tests/unit/classes/ContextTest.php index 6b798ca6e..62ac6f95d 100644 --- a/tests/unit/classes/ContextTest.php +++ b/tests/unit/classes/ContextTest.php @@ -1,6 +1,6 @@ var2 = 'val2'; $this->assertEquals(Context::gets('var1','var2'), $data); $data->var3 = 'val3'; - $this->assertEquals(Context::getAll(), $data); + $this->assertEquals('val1', Context::getAll()->var1); + $this->assertEquals('val2', Context::getAll()->var2); + $this->assertEquals('val3', Context::getAll()->var3); } public function testAddGetBodyClass() @@ -58,10 +60,17 @@ class ContextTest extends \Codeception\TestCase\Test $this->assertEquals(Context::getBodyClass(), ' class="red green"'); Context::addBodyClass('blue'); $this->assertEquals(Context::getBodyClass(), ' class="red green blue"'); + Context::addBodyClass('yellow'); + $this->assertEquals(Context::getBodyClassList(), ['red', 'green', 'blue', 'yellow']); + + // remove class manually + Context::removeBodyClass('yellow'); + $this->assertEquals(Context::getBodyClassList(), ['red', 'green', 'blue']); // remove duplicated class Context::addBodyClass('red'); $this->assertEquals(Context::getBodyClass(), ' class="red green blue"'); + $this->assertEquals(Context::getBodyClassList(), ['red', 'green', 'blue']); } public function testSetRequestMethod() @@ -75,7 +84,7 @@ class ContextTest extends \Codeception\TestCase\Test Context::setRequestArguments(); $this->assertEquals('GET', Context::getRequestMethod()); $this->assertEquals('bar', Context::getRequestVars()->foo); - + $_SERVER['REQUEST_METHOD'] = 'GET'; $_GET = array('foo' => 'barrr', 'xe_js_callback' => 'callback12345'); $_POST = array(); @@ -86,7 +95,7 @@ class ContextTest extends \Codeception\TestCase\Test Context::setRequestArguments(); $this->assertEquals('JS_CALLBACK', Context::getRequestMethod()); $this->assertEquals('barrr', Context::getRequestVars()->foo); - + $_SERVER['REQUEST_METHOD'] = 'POST'; $_GET = $_REQUEST = array('foo' => 'bazz'); // Request method is POST but actual values are given as GET $_POST = array(); @@ -95,9 +104,9 @@ class ContextTest extends \Codeception\TestCase\Test Context::setRequestMethod(); Context::setRequestArguments(); $this->assertEquals('POST', Context::getRequestMethod()); - $this->assertNull(Context::getRequestVars()->foo); - $this->assertEquals('bazz', Context::get('foo')); // XE Compatibility behavior - + $this->assertNull(Context::getRequestVars()->foo ?? null); + $this->assertNull(Context::get('foo')); // This is different from XE behavior + $_SERVER['REQUEST_METHOD'] = 'POST'; $_GET = array(); $_POST = $_REQUEST = array('foo' => 'rhymixtest'); @@ -107,7 +116,7 @@ class ContextTest extends \Codeception\TestCase\Test Context::setRequestArguments(); $this->assertEquals('POST', Context::getRequestMethod()); $this->assertEquals('rhymixtest', Context::getRequestVars()->foo); - + $_SERVER['REQUEST_METHOD'] = 'POST'; $_GET = $_POST = $_REQUEST = array(); $GLOBALS['HTTP_RAW_POST_DATA'] = 'TestRhymix'; @@ -117,7 +126,7 @@ class ContextTest extends \Codeception\TestCase\Test Context::setRequestArguments(); $this->assertEquals('XMLRPC', Context::getRequestMethod()); $this->assertEquals('TestRhymix', Context::getRequestVars()->foo); - + $_SERVER['REQUEST_METHOD'] = 'POST'; $_GET = $_POST = $_REQUEST = array(); $GLOBALS['HTTP_RAW_POST_DATA'] = 'foo=JSON_TEST'; // Not actual JSON @@ -128,7 +137,7 @@ class ContextTest extends \Codeception\TestCase\Test Context::setRequestArguments(); $this->assertEquals('JSON', Context::getRequestMethod()); $this->assertEquals('JSON_TEST', Context::getRequestVars()->foo); - + Context::setRequestMethod('POST'); $_GET = $_POST = $_REQUEST = array(); unset($GLOBALS['HTTP_RAW_POST_DATA']); @@ -138,7 +147,7 @@ class ContextTest extends \Codeception\TestCase\Test Context::setRequestMethod(); Context::setRequestArguments(); $this->assertEquals('POST', Context::getRequestMethod()); - + Context::setRequestMethod('POST'); $_GET = array(); $_POST = $_REQUEST = array('foo' => 'legacy', '_rx_ajax_compat' => 'XMLRPC'); @@ -151,28 +160,38 @@ class ContextTest extends \Codeception\TestCase\Test $this->assertEquals('XMLRPC', Context::getRequestMethod()); $this->assertEquals('legacy', Context::getRequestVars()->foo); } - + public function testSetResponseMethod() { $this->assertEquals(Context::getResponseMethod(), 'HTML'); - + Context::setRequestMethod('JSON'); $this->assertEquals(Context::getResponseMethod(), 'JSON'); Context::setResponseMethod('WRONG_TYPE'); $this->assertEquals(Context::getResponseMethod(), 'HTML'); - + Context::setResponseMethod('XMLRPC'); $this->assertEquals(Context::getResponseMethod(), 'XMLRPC'); - + Context::setResponseMethod('HTML'); $this->assertEquals(Context::getResponseMethod(), 'HTML'); } + public function testDefaultPlugin() + { + $this->assertTrue(Context::isDefaultPlugin('board', 'module')); + $this->assertFalse(Context::isDefaultPlugin('board', 'widget')); + $this->assertTrue(Context::isDefaultPlugin('xedition', 'layout')); + $this->assertFalse(Context::isDefaultPlugin('xedition', 'addon')); + } + public function testBlacklistedPlugin() { $this->assertTrue(Context::isBlacklistedPlugin('autolang')); + $this->assertFalse(Context::isBlacklistedPlugin('autolang', 'module')); $this->assertFalse(Context::isBlacklistedPlugin('document')); + $this->assertTrue(Context::isBlacklistedPlugin('jquerycdn', 'addon')); } public function testReservedWord() diff --git a/tests/unit/classes/FileHandlerTest.php b/tests/unit/classes/FileHandlerTest.php index e5479d9d5..22f2ff0c1 100644 --- a/tests/unit/classes/FileHandlerTest.php +++ b/tests/unit/classes/FileHandlerTest.php @@ -1,6 +1,6 @@ baseurl = '/' . basename(dirname(dirname(dirname(__DIR__)))) . '/'; + $this->reservedCSS = HTMLDisplayHandler::$reservedCSS; + $this->reservedJS = HTMLDisplayHandler::$reservedJS; HTMLDisplayHandler::$reservedCSS = '/xxx$/'; HTMLDisplayHandler::$reservedJS = '/xxx$/'; FrontEndFileHandler::$minify = 'none'; FrontEndFileHandler::$concat = 'none'; + } - $this->specify("js (head)", function() { - $handler = new FrontEndFileHandler(); - $handler->loadFile(array('./common/js/js_app.js', 'head')); - $handler->loadFile(array('./common/js/common.js', 'body')); - $handler->loadFile(array('./common/js/common.js', 'head')); - $handler->loadFile(array('./common/js/xml_js_filter.js', 'body')); - $expected[] = array('file' => '/rhymix/common/js/js_app.js' . $this->_filemtime('common/js/js_app.js'), 'targetie' => null); - $expected[] = array('file' => '/rhymix/common/js/common.js' . $this->_filemtime('common/js/common.js'), 'targetie' => null); - $this->assertEquals($expected, $handler->getJsFileList()); - }); + public function _after() + { + HTMLDisplayHandler::$reservedCSS = $this->reservedCSS; + HTMLDisplayHandler::$reservedJS = $this->reservedJS; + } - $this->specify("js (body)", function() { - $handler = new FrontEndFileHandler(); - $handler->loadFile(array('./common/js/xml_handler.js', 'body')); - $handler->loadFile(array('./common/js/xml_js_filter.js', 'head')); - $expected[] = array('file' => '/rhymix/common/js/xml_handler.js' . $this->_filemtime('common/js/xml_handler.js'), 'targetie' => null); - $this->assertEquals($expected, $handler->getJsFileList('body')); - }); + public function _failed() + { + HTMLDisplayHandler::$reservedCSS = $this->reservedCSS; + HTMLDisplayHandler::$reservedJS = $this->reservedJS; + } - $this->specify("css and less", function() { - $handler = new FrontEndFileHandler(); - $handler->loadFile(array('./common/css/rhymix.less')); - $result = $handler->getCssFileList(true); - $this->assertRegexp('/\.rhymix\.less\.css\?\d+$/', $result[0]['file']); - $this->assertEquals('all', $result[0]['media']); - $this->assertEmpty($result[0]['targetie']); - }); + public function testJsHead() + { + $handler = new FrontEndFileHandler(); + $handler->loadFile(array('./common/js/js_app.js', 'head')); + $handler->loadFile(array('./common/js/common.js', 'body')); + $handler->loadFile(array('./common/js/common.js', 'head')); + $handler->loadFile(array('./common/js/xml_js_filter.js', 'body')); + $expected[] = array('file' => $this->baseurl . 'common/js/js_app.js' . $this->_filemtime('common/js/js_app.js'), 'attrs' => ''); + $expected[] = array('file' => $this->baseurl . 'common/js/common.js' . $this->_filemtime('common/js/common.js'), 'attrs' => ''); + $this->assertEquals($expected, $handler->getJsFileList()); + } - $this->specify("order (duplicate)", function() { - $handler = new FrontEndFileHandler(); - $handler->loadFile(array('./common/js/js_app.js', 'head', '', -100000)); - $handler->loadFile(array('./common/js/common.js', 'head', '', -100000)); - $handler->loadFile(array('./common/js/xml_handler.js', 'head', '', -100000)); - $handler->loadFile(array('./common/js/xml_js_filter.js', 'head', '', -100000)); - $handler->loadFile(array('./common/js/js_app.js', 'head', '', -100000)); - $handler->loadFile(array('./common/js/common.js', 'head', '', -100000)); - $handler->loadFile(array('./common/js/xml_handler.js', 'head', '', -100000)); - $handler->loadFile(array('./common/js/xml_js_filter.js', 'head', '', -100000)); - $expected[] = array('file' => '/rhymix/common/js/js_app.js' . $this->_filemtime('common/js/js_app.js'), 'targetie' => null); - $expected[] = array('file' => '/rhymix/common/js/common.js' . $this->_filemtime('common/js/common.js'), 'targetie' => null); - $expected[] = array('file' => '/rhymix/common/js/xml_handler.js' . $this->_filemtime('common/js/xml_handler.js'), 'targetie' => null); - $expected[] = array('file' => '/rhymix/common/js/xml_js_filter.js' . $this->_filemtime('common/js/xml_js_filter.js'), 'targetie' => null); - $this->assertEquals($expected, $handler->getJsFileList()); - }); + public function testJsBody() + { + $handler = new FrontEndFileHandler(); + $handler->loadFile(array('./common/js/xml_handler.js', 'body')); + $handler->loadFile(array('./common/js/xml_js_filter.js', 'head')); + $expected[] = array('file' => $this->baseurl . 'common/js/xml_handler.js' . $this->_filemtime('common/js/xml_handler.js'), 'attrs' => ''); + $this->assertEquals($expected, $handler->getJsFileList('body')); + } - $this->specify("order (redefine)", function() { - $handler = new FrontEndFileHandler(); - $handler->loadFile(array('./common/js/xml_handler.js', 'head', '', 1)); - $handler->loadFile(array('./common/js/js_app.js', 'head', '', -100000)); - $handler->loadFile(array('./common/js/common.js', 'head', '', -100000)); - $handler->loadFile(array('./common/js/xml_js_filter.js', 'head', '', -100000)); - $expected[] = array('file' => '/rhymix/common/js/js_app.js' . $this->_filemtime('common/js/js_app.js'), 'targetie' => null); - $expected[] = array('file' => '/rhymix/common/js/common.js' . $this->_filemtime('common/js/common.js'), 'targetie' => null); - $expected[] = array('file' => '/rhymix/common/js/xml_js_filter.js' . $this->_filemtime('common/js/xml_js_filter.js'), 'targetie' => null); - $expected[] = array('file' => '/rhymix/common/js/xml_handler.js' . $this->_filemtime('common/js/xml_handler.js'), 'targetie' => null); - $this->assertEquals($expected, $handler->getJsFileList()); - }); + public function testDefaultScss() + { + $handler = new FrontEndFileHandler(); + $handler->loadFile(array('./common/css/rhymix.scss')); + $result = $handler->getCssFileList(true); + $this->assertRegexp('/\.rhymix\.scss\.css\?t=\d+$/', $result[0]['file']); + $this->assertEquals('all', $result[0]['media']); + $this->assertTrue(empty($result[0]['targetie'])); + } - $this->specify("unload", function() { - $handler = new FrontEndFileHandler(); - $handler->loadFile(array('./common/js/js_app.js', 'head', '', -100000)); - $handler->loadFile(array('./common/js/common.js', 'head', '', -100000)); - $handler->loadFile(array('./common/js/xml_handler.js', 'head', '', -100000)); - $handler->loadFile(array('./common/js/xml_js_filter.js', 'head', '', -100000)); - $handler->unloadFile('./common/js/js_app.js', '', 'all'); - $expected[] = array('file' => '/rhymix/common/js/common.js' . $this->_filemtime('common/js/common.js'), 'targetie' => null); - $expected[] = array('file' => '/rhymix/common/js/xml_handler.js' . $this->_filemtime('common/js/xml_handler.js'), 'targetie' => null); - $expected[] = array('file' => '/rhymix/common/js/xml_js_filter.js' . $this->_filemtime('common/js/xml_js_filter.js'), 'targetie' => null); - $this->assertEquals($expected, $handler->getJsFileList()); - }); + public function testDuplicateOrder() + { + $handler = new FrontEndFileHandler(); + $handler->loadFile(array('./common/js/js_app.js', 'head', '', -100000)); + $handler->loadFile(array('./common/js/common.js', 'head', '', -100000)); + $handler->loadFile(array('./common/js/xml_handler.js', 'head', '', -100000)); + $handler->loadFile(array('./common/js/xml_js_filter.js', 'head', '', -100000)); + $handler->loadFile(array('./common/js/js_app.js', 'head', '', -100000)); + $handler->loadFile(array('./common/js/common.js', 'head', '', -100000)); + $handler->loadFile(array('./common/js/xml_handler.js', 'head', '', -100000)); + $handler->loadFile(array('./common/js/xml_js_filter.js', 'head', '', -100000)); + $expected[] = array('file' => $this->baseurl . 'common/js/js_app.js' . $this->_filemtime('common/js/js_app.js'), 'attrs' => ''); + $expected[] = array('file' => $this->baseurl . 'common/js/common.js' . $this->_filemtime('common/js/common.js'), 'attrs' => ''); + $expected[] = array('file' => $this->baseurl . 'common/js/xml_handler.js' . $this->_filemtime('common/js/xml_handler.js'), 'attrs' => ''); + $expected[] = array('file' => $this->baseurl . 'common/js/xml_js_filter.js' . $this->_filemtime('common/js/xml_js_filter.js'), 'attrs' => ''); + $this->assertEquals($expected, $handler->getJsFileList()); + } - $this->specify("target IE (js)", function() { - $handler = new FrontEndFileHandler(); - $handler->loadFile(array('./common/js/js_app.js', 'head', 'ie6')); - $handler->loadFile(array('./common/js/js_app.js', 'head', 'ie7')); - $handler->loadFile(array('./common/js/js_app.js', 'head', 'ie8')); - $expected[] = array('file' => '/rhymix/common/js/js_app.js' . $this->_filemtime('common/js/js_app.js'), 'targetie' => 'ie6'); - $expected[] = array('file' => '/rhymix/common/js/js_app.js' . $this->_filemtime('common/js/js_app.js'), 'targetie' => 'ie7'); - $expected[] = array('file' => '/rhymix/common/js/js_app.js' . $this->_filemtime('common/js/js_app.js'), 'targetie' => 'ie8'); - $this->assertEquals($expected, $handler->getJsFileList()); - }); + public function testRedefineOrder() + { + $handler = new FrontEndFileHandler(); + $handler->loadFile(array('./common/js/xml_handler.js', 'head', '', 1)); + $handler->loadFile(array('./common/js/js_app.js', 'head', '', -100000)); + $handler->loadFile(array('./common/js/common.js', 'head', '', -100000)); + $handler->loadFile(array('./common/js/xml_js_filter.js', 'head', '', -100000)); + $expected[] = array('file' => $this->baseurl . 'common/js/js_app.js' . $this->_filemtime('common/js/js_app.js'), 'attrs' => ''); + $expected[] = array('file' => $this->baseurl . 'common/js/common.js' . $this->_filemtime('common/js/common.js'), 'attrs' => ''); + $expected[] = array('file' => $this->baseurl . 'common/js/xml_js_filter.js' . $this->_filemtime('common/js/xml_js_filter.js'), 'attrs' => ''); + $expected[] = array('file' => $this->baseurl . 'common/js/xml_handler.js' . $this->_filemtime('common/js/xml_handler.js'), 'attrs' => ''); + $this->assertEquals($expected, $handler->getJsFileList()); + } - $this->specify("external file - schemaless", function() { - $handler = new FrontEndFileHandler(); - $handler->loadFile(array('http://external.host/js/script.js')); - $handler->loadFile(array('https://external.host/js/script.js')); - $handler->loadFile(array('//external.host/js/script1.js')); - $handler->loadFile(array('///external.host/js/script2.js')); + public function testUnload() + { + $handler = new FrontEndFileHandler(); + $handler->loadFile(array('./common/js/js_app.js', 'head', '', -100000)); + $handler->loadFile(array('./common/js/common.js', 'head', '', -100000)); + $handler->loadFile(array('./common/js/xml_handler.js', 'head', '', -100000)); + $handler->loadFile(array('./common/js/xml_js_filter.js', 'head', '', -100000)); + $handler->unloadFile('./common/js/js_app.js', '', 'all'); + $expected[] = array('file' => $this->baseurl . 'common/js/common.js' . $this->_filemtime('common/js/common.js'), 'attrs' => ''); + $expected[] = array('file' => $this->baseurl . 'common/js/xml_handler.js' . $this->_filemtime('common/js/xml_handler.js'), 'attrs' => ''); + $expected[] = array('file' => $this->baseurl . 'common/js/xml_js_filter.js' . $this->_filemtime('common/js/xml_js_filter.js'), 'attrs' => ''); + $this->assertEquals($expected, $handler->getJsFileList()); + } - $expected[] = array('file' => 'http://external.host/js/script.js', 'targetie' => null); - $expected[] = array('file' => 'https://external.host/js/script.js', 'targetie' => null); - $expected[] = array('file' => '//external.host/js/script1.js', 'targetie' => null); - $expected[] = array('file' => '//external.host/js/script2.js', 'targetie' => null); - $this->assertEquals($expected, $handler->getJsFileList()); - }); + public function testJsModule() + { + $handler = new FrontEndFileHandler(); + $handler->loadFile(array('./common/js/js_app.js', 'module')); + $handler->loadFile(array('./common/js/common.js', 'module')); + $expected[] = array('file' => $this->baseurl . 'common/js/js_app.js' . $this->_filemtime('common/js/js_app.js'), 'attrs' => ' type="module"'); + $expected[] = array('file' => $this->baseurl . 'common/js/common.js' . $this->_filemtime('common/js/common.js'), 'attrs' => ' type="module"'); + $this->assertEquals($expected, $handler->getJsFileList()); + } - $this->specify("external file - schemaless", function() { - $handler = new FrontEndFileHandler(); - $handler->loadFile(array('//external.host/js/script.js')); - $handler->loadFile(array('///external.host/js/script.js')); + public function testExternalFile1() + { + $handler = new FrontEndFileHandler(); + $handler->loadFile(array('http://external.host/js/script.js')); + $handler->loadFile(array('https://external.host/js/script.js')); + $handler->loadFile(array('//external.host/js/script1.js')); + $handler->loadFile(array('///external.host/js/script2.js')); - $expected[] = array('file' => '//external.host/js/script.js', 'targetie' => null); - $this->assertEquals($expected, $handler->getJsFileList()); - }); + $expected[] = array('file' => 'http://external.host/js/script.js', 'attrs' => ''); + $expected[] = array('file' => 'https://external.host/js/script.js', 'attrs' => ''); + $expected[] = array('file' => '//external.host/js/script1.js', 'attrs' => ''); + $expected[] = array('file' => '//external.host/js/script2.js', 'attrs' => ''); + $this->assertEquals($expected, $handler->getJsFileList()); + } - $this->specify("target IE (css)", function() { - $handler = new FrontEndFileHandler(); - $handler->loadFile(array('./common/css/common.css', null, 'ie6')); - $handler->loadFile(array('./common/css/common.css', null, 'ie7')); - $handler->loadFile(array('./common/css/common.css', null, 'ie8')); + public function testExternalFile2() + { + $handler = new FrontEndFileHandler(); + $handler->loadFile(array('//external.host/js/script.js')); + $handler->loadFile(array('///external.host/js/script.js')); - $expected[] = array('file' => '/rhymix/common/css/common.css', 'media'=>'all', 'targetie' => 'ie6'); - $expected[] = array('file' => '/rhymix/common/css/common.css','media'=>'all', 'targetie' => 'ie7'); - $expected[] = array('file' => '/rhymix/common/css/common.css', 'media'=>'all', 'targetie' => 'ie8'); - $this->assertEquals($expected, $handler->getCssFileList()); - }); + $expected[] = array('file' => '//external.host/js/script.js', 'attrs' => ''); + $this->assertEquals($expected, $handler->getJsFileList()); + } - $this->specify("media", function() { - $handler = new FrontEndFileHandler(); - $handler->loadFile(array('./common/css/common.css', 'all')); - $handler->loadFile(array('./common/css/common.css', 'screen')); - $handler->loadFile(array('./common/css/common.css', 'handled')); + public function testExternalFile3() + { + $handler = new FrontEndFileHandler(); + $handler->loadFile(array('http://external.host/css/style1.css')); + $handler->loadFile(array('https://external.host/css/style2.css')); + $handler->loadFile(array('https://external.host/css/style3.css?foo=bar&t=123')); - $expected[] = array('file' => '/rhymix/common/css/common.css', 'media'=>'all', 'targetie' => null); - $expected[] = array('file' => '/rhymix/common/css/common.css','media'=>'screen', 'targetie' => null); - $expected[] = array('file' => '/rhymix/common/css/common.css', 'media'=>'handled', 'targetie' => null); - $this->assertEquals($expected, $handler->getCssFileList()); - }); + $expected[] = array('file' => 'http://external.host/css/style1.css', 'media'=>'all'); + $expected[] = array('file' => 'https://external.host/css/style2.css', 'media'=>'all'); + $expected[] = array('file' => 'https://external.host/css/style3.css?foo=bar&t=123', 'media'=>'all'); + $this->assertEquals($expected, $handler->getCssFileList()); + } + public function testExternalFile4() + { + $handler = new FrontEndFileHandler(); + $handler->loadFile(array('//external.host/css/style.css')); + $handler->loadFile(array('///external.host/css2/style2.css')); + $handler->loadFile(array('//external.host/css/style3.css?foo=bar&t=123')); + + $expected[] = array('file' => '//external.host/css/style.css', 'media'=>'all'); + $expected[] = array('file' => '//external.host/css2/style2.css', 'media'=>'all'); + $expected[] = array('file' => '//external.host/css/style3.css?foo=bar&t=123', 'media'=>'all'); + $this->assertEquals($expected, $handler->getCssFileList()); + } + + public function testExternalFile5() + { + $handler = new FrontEndFileHandler(); + $handler->loadFile(array('https://fonts.googleapis.com/css?family=Montserrat&display=swap')); + $handler->loadFile(array('//fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;700&display=swap')); + + $expected[] = array('file' => 'https://fonts.googleapis.com/css?family=Montserrat&display=swap', 'media'=>'all'); + $expected[] = array('file' => '//fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;700&display=swap', 'media'=>'all'); + $this->assertEquals($expected, $handler->getCssFileList()); + } + + public function testPathConversion() + { + $handler = new FrontEndFileHandler(); + $handler->loadFile(array('./common/xeicon/xeicon.min.css')); + $result = $handler->getCssFileList(); + $this->assertEquals($this->baseurl . 'common/css/xeicon/xeicon.min.css' . $this->_filemtime('common/css/xeicon/xeicon.min.css'), $result[0]['file']); + $this->assertEquals('all', $result[0]['media']); + $this->assertTrue(empty($result[0]['targetie'])); + } + + public function testTargetie1() + { + $handler = new FrontEndFileHandler(); + $handler->loadFile(array('./common/js/js_app.js', 'head', 'ie6')); + $handler->loadFile(array('./common/js/js_app.js', 'head', 'ie7')); + $handler->loadFile(array('./common/js/js_app.js', 'head', 'ie8')); + + // All targetie attributes should be ignored since Rhymix 2.1 + // Since the 3 loadFile() are otherwise the same, only 1 will remain. + $expected[] = array( + 'file' => $this->baseurl . 'common/js/js_app.js' . $this->_filemtime('common/js/js_app.js'), + 'attrs' => '', + ); + $this->assertEquals($expected, $handler->getJsFileList()); + } + + public function testTargetie2() + { + $handler = new FrontEndFileHandler(); + $handler->loadFile(array('./common/css/common.css', null, 'ie6')); + $handler->loadFile(array('./common/css/common.css', null, 'ie7')); + $handler->loadFile(array('./common/css/common.css', null, 'ie8')); + + // All targetie attributes should be ignored since Rhymix 2.1 + // Since the 3 loadFile() are otherwise the same, only 1 will remain. + $expected[] = array('file' => $this->baseurl . 'common/css/common.css', 'media' => 'all'); + $this->assertEquals($expected, $handler->getCssFileList()); + } + + public function testMedia() + { + $handler = new FrontEndFileHandler(); + $handler->loadFile(array('./common/css/common.css', 'screen')); + $handler->loadFile(array('./common/css/common.css', 'print')); + $handler->loadFile(array('./common/css/common.css', 'handheld')); + $handler->loadFile(array('./common/css/common.css', true)); + + $expected[] = array('file' => $this->baseurl . 'common/css/common.css', 'media'=>'screen'); + $expected[] = array('file' => $this->baseurl . 'common/css/common.css', 'media'=>'print'); + $expected[] = array('file' => $this->baseurl . 'common/css/common.css', 'media'=>'handheld'); + $expected[] = array('file' => $this->baseurl . 'common/css/common.css', 'media'=>'all'); + $this->assertEquals($expected, $handler->getCssFileList()); + } + + public function testMinify() + { FrontEndFileHandler::$minify = 'all'; - $this->specify("minify (css)", function() { - $handler = new FrontEndFileHandler(); - $handler->loadFile(array('./common/css/rhymix.less')); - $result = $handler->getCssFileList(true); - $this->assertRegexp('/\.rhymix\.less\.min\.css\b/', $result[0]['file']); - $this->assertEquals('all', $result[0]['media']); - $this->assertEmpty($result[0]['targetie']); - }); - - $this->specify("minify (js)", function() { - $handler = new FrontEndFileHandler(); - $handler->loadFile(array('./common/js/common.js', 'head')); - $result = $handler->getJsFileList('head', true); - $this->assertRegexp('/minified\/common\.js\.common\.min\.js\?\d+$/', $result[0]['file']); - $this->assertEmpty($result[0]['targetie']); - }); - + $handler = new FrontEndFileHandler(); + $handler->loadFile(array('./common/css/rhymix.scss')); + $result = $handler->getCssFileList(true); + $this->assertRegexp('/\.rhymix\.scss\.min\.css\b/', $result[0]['file']); + $this->assertEquals('all', $result[0]['media']); + $this->assertTrue(empty($result[0]['targetie'])); + + $handler = new FrontEndFileHandler(); + $handler->loadFile(array('./common/js/common.js', 'head')); + $result = $handler->getJsFileList('head', true); + $this->assertRegexp('/minified\/common\.js\.common\.min\.js\?t=\d+$/', $result[0]['file']); + $this->assertTrue(empty($result[0]['targetie'])); + FrontEndFileHandler::$minify = 'none'; - + } + + public function testConcat() + { FrontEndFileHandler::$concat = 'css'; - - $this->specify("concat (css)", function() { - $handler = new FrontEndFileHandler(); - $handler->loadFile(array('./common/css/rhymix.less')); - $handler->loadFile(array('./common/css/bootstrap-responsive.css')); - $handler->loadFile(array('http://external.host/style.css')); - $handler->loadFile(array('./common/css/bootstrap.css', null, 'IE')); - $handler->loadFile(array('./tests/_data/formatter/concat.source1.css')); - $handler->loadFile(array('./tests/_data/formatter/concat.source2.css')); - $handler->loadFile(array('./tests/_data/formatter/concat.target1.css')); - $handler->loadFile(array('./tests/_data/formatter/concat.target2.css')); - $result = $handler->getCssFileList(true); - $this->assertEquals(4, count($result)); - $this->assertRegexp('/combined\/[0-9a-f]+\.css\?\d+$/', $result[0]['file']); - $this->assertEquals('http://external.host/style.css', $result[1]['file']); - $this->assertEquals('/rhymix/common/css/bootstrap.css' . $this->_filemtime('common/css/bootstrap.css'), $result[2]['file']); - $this->assertEquals('IE', $result[2]['targetie']); - $this->assertRegexp('/combined\/[0-9a-f]+\.css\?\d+$/', $result[3]['file']); - }); - + + $handler = new FrontEndFileHandler(); + $handler->loadFile(array('./common/css/rhymix.scss')); + $handler->loadFile(array('./common/css/bootstrap-responsive.css')); + $handler->loadFile(array('http://external.host/style.css')); + $handler->loadFile(array('./common/css/bootstrap.css', null, 'IE')); + $handler->loadFile(array('./tests/_data/formatter/concat.source1.css')); + $handler->loadFile(array('./tests/_data/formatter/concat.source2.css')); + $handler->loadFile(array('./tests/_data/formatter/concat.target1.css')); + $handler->loadFile(array('./tests/_data/formatter/concat.target2.css')); + $result = $handler->getCssFileList(true); + $this->assertEquals(3, count($result)); + $this->assertRegexp('/combined\/[0-9a-f]+\.css\?t=\d+$/', $result[0]['file']); + //$this->assertEquals($this->baseurl . 'common/css/bootstrap.css' . $this->_filemtime('common/css/bootstrap.css'), $result[1]['file']); + //$this->assertEquals('IE', $result[1]['targetie']); + $this->assertEquals('http://external.host/style.css', $result[1]['file']); + $this->assertRegexp('/combined\/[0-9a-f]+\.css\?t=\d+$/', $result[2]['file']); + FrontEndFileHandler::$concat = 'js'; - - $this->specify("concat (js)", function() { - $handler = new FrontEndFileHandler(); - $handler->loadFile(array('./common/js/common.js', 'head')); - $handler->loadFile(array('./common/js/debug.js', 'head')); - $handler->loadFile(array('./common/js/html5.js', 'head')); - $handler->loadFile(array('///external.host/js/script.js')); - $handler->loadFile(array('./tests/_data/formatter/concat.source1.js', 'head', 'lt IE 8')); - $handler->loadFile(array('./tests/_data/formatter/concat.source2.js', 'head', 'gt IE 7')); - $handler->loadFile(array('./tests/_data/formatter/concat.target1.js')); - $handler->loadFile(array('./tests/_data/formatter/concat.target2.js')); - $result = $handler->getJsFileList('head', true); - $this->assertEquals(3, count($result)); - $this->assertRegexp('/combined\/[0-9a-f]+\.js\?\d+$/', $result[0]['file']); - $this->assertEquals('//external.host/js/script.js', $result[1]['file']); - $this->assertRegexp('/combined\/[0-9a-f]+\.js\?\d+$/', $result[2]['file']); - }); - + + $handler = new FrontEndFileHandler(); + $handler->loadFile(array('./common/js/common.js', 'head')); + $handler->loadFile(array('./common/js/debug.js', 'head')); + $handler->loadFile(array('///external.host/js/script.js')); + $handler->loadFile(array('./tests/_data/formatter/concat.source1.js', 'head', 'lt IE 8')); + $handler->loadFile(array('./tests/_data/formatter/concat.source2.js', 'head', 'gt IE 7')); + $handler->loadFile(array('./tests/_data/formatter/concat.target1.js')); + $handler->loadFile(array('./tests/_data/formatter/concat.target2.js')); + $result = $handler->getJsFileList('head', true); + $this->assertEquals(3, count($result)); + $this->assertRegexp('/combined\/[0-9a-f]+\.js\?t=\d+$/', $result[0]['file']); + $this->assertEquals('//external.host/js/script.js', $result[1]['file']); + $this->assertRegexp('/combined\/[0-9a-f]+\.js\?t=\d+$/', $result[2]['file']); + FrontEndFileHandler::$concat = 'none'; - - $this->specify("external file", function() { - $handler = new FrontEndFileHandler(); - $handler->loadFile(array('http://external.host/css/style1.css')); - $handler->loadFile(array('https://external.host/css/style2.css')); - $handler->loadFile(array('https://external.host/css/style3.css?foo=bar&t=123')); + } - $expected[] = array('file' => 'http://external.host/css/style1.css', 'media'=>'all', 'targetie' => null); - $expected[] = array('file' => 'https://external.host/css/style2.css', 'media'=>'all', 'targetie' => null); - $expected[] = array('file' => 'https://external.host/css/style3.css?foo=bar&t=123', 'media'=>'all', 'targetie' => null); - $this->assertEquals($expected, $handler->getCssFileList()); - }); + public function testBlockedScripts() + { + HTMLDisplayHandler::$reservedCSS = $this->reservedCSS; + HTMLDisplayHandler::$reservedJS = $this->reservedJS; - $this->specify("external file - schemaless", function() { - $handler = new FrontEndFileHandler(); - $handler->loadFile(array('//external.host/css/style.css')); - $handler->loadFile(array('///external.host/css2/style2.css')); - $handler->loadFile(array('//external.host/css/style3.css?foo=bar&t=123')); - - $expected[] = array('file' => '//external.host/css/style.css', 'media'=>'all', 'targetie' => null); - $expected[] = array('file' => '//external.host/css2/style2.css', 'media'=>'all', 'targetie' => null); - $expected[] = array('file' => '//external.host/css/style3.css?foo=bar&t=123', 'media'=>'all', 'targetie' => null); - $this->assertEquals($expected, $handler->getCssFileList()); - }); - - $this->specify("path conversion", function() { - $handler = new FrontEndFileHandler(); - $handler->loadFile(array('./common/xeicon/xeicon.min.css')); - $result = $handler->getCssFileList(); - $this->assertEquals('/rhymix/common/css/xeicon/xeicon.min.css' . $this->_filemtime('common/css/xeicon/xeicon.min.css'), $result[0]['file']); - $this->assertEquals('all', $result[0]['media']); - $this->assertEmpty($result[0]['targetie']); - }); - - HTMLDisplayHandler::$reservedCSS = $reservedCSS; - HTMLDisplayHandler::$reservedJS = $reservedJS; - - $this->specify("blocked scripts", function() { - $handler = new FrontEndFileHandler(); - $handler->loadFile(array('./common/css/xe.min.css')); - $handler->loadFile(array('./common/js/common.js')); - $handler->loadFile(array('./common/js/xe.js')); - $handler->loadFile(array('./common/js/xe.min.js')); - $handler->loadFile(array('./common/js/xml2json.js')); - $handler->loadFile(array('./common/js/jquery.js')); - $handler->loadFile(array('./common/js/jquery-1.x.min.js')); - $handler->loadFile(array('./common/js/jquery-2.0.0.js')); - $handler->loadFile(array('./common/js/jQuery.min.js')); - $handler->loadFile(array('http://code.jquery.com/jquery-latest.js')); - $result = $handler->getCssFileList(); - $this->assertEquals(0, count($result)); - $result = $handler->getJsFileList(); - $this->assertEquals(1, count($result)); - $this->assertEquals('/rhymix/common/js/xml2json.js' . $this->_filemtime('common/js/xml2json.js'), $result[0]['file']); - }); + $handler = new FrontEndFileHandler(); + $handler->loadFile(array('./common/css/xe.min.css')); + $handler->loadFile(array('./common/js/common.js')); + $handler->loadFile(array('./common/js/xe.js')); + $handler->loadFile(array('./common/js/xe.min.js')); + $handler->loadFile(array('./common/js/xml2json.js')); + $handler->loadFile(array('./common/js/jquery.js')); + $handler->loadFile(array('./common/js/jquery-1.x.min.js')); + $handler->loadFile(array('./common/js/jquery-2.0.0.js')); + $handler->loadFile(array('./common/js/jQuery.min.js')); + $handler->loadFile(array('http://code.jquery.com/jquery-latest.js')); + $result = $handler->getCssFileList(); + $this->assertEquals(0, count($result)); + $result = $handler->getJsFileList(); + $this->assertEquals(1, count($result)); + $this->assertEquals($this->baseurl . 'common/js/xml2json.js' . $this->_filemtime('common/js/xml2json.js'), $result[0]['file']); } } diff --git a/tests/unit/classes/OldSecurityTest.php b/tests/unit/classes/OldSecurityTest.php index 85ed5e564..47ab356f6 100644 --- a/tests/unit/classes/OldSecurityTest.php +++ b/tests/unit/classes/OldSecurityTest.php @@ -1,8 +1,13 @@ _reset(); + } + + protected function _reset() { /** * Setup mock data @@ -40,13 +45,13 @@ class OldSecurityTest extends \Codeception\TestCase\Test $this->assertTrue(true); // normal string - one - $this->_before(); + $this->_reset(); $this->assertEquals('Hello, world', Context::get('content1')); $security->encodeHTML('content1'); $this->assertEquals('<strong>Hello, world</strong>', Context::get('content1')); // normal string - two - $this->_before(); + $this->_reset(); $this->assertEquals('Hello, world', Context::get('content1')); $this->assertEquals('Wow, >_< !', Context::get('content2')); $security->encodeHTML('content1','content2'); @@ -61,7 +66,7 @@ class OldSecurityTest extends \Codeception\TestCase\Test $this->assertEquals(Context::get('array1'), array('<span class="first">F</span>irst','Second','Third')); $security->encodeHTML('array1.2'); // affects only third element $this->assertEquals(Context::get('array1'), array('<span class="first">F</span>irst','Second','<b>T</b>hird')); - $this->_before(); // reset; + $this->_reset(); // reset; $this->assertEquals(Context::get('array1'), array('First','Second','Third')); $security->encodeHTML('array1.'); // affects all items $this->assertEquals(Context::get('array1'), array('<span class="first">F</span>irst','<u>S</u>econd','<b>T</b>hird')); @@ -74,7 +79,7 @@ class OldSecurityTest extends \Codeception\TestCase\Test $this->assertEquals(Context::get('array2'), array('elem1'=>'One 1','elem2'=>'Two 2','elem3'=>'Three 3')); $security->encodeHTML('array2.elem2'); // affects only 'elem2' $this->assertEquals(Context::get('array2'), array('elem1'=>'One 1','elem2'=>'Two <del>2</del>','elem3'=>'Three 3')); - $this->_before(); // reset; + $this->_reset(); // reset; $this->assertEquals(Context::get('array2'), array('elem1'=>'One 1','elem2'=>'Two 2','elem3'=>'Three 3')); $security->encodeHTML('array2.'); // affects all items $this->assertEquals(Context::get('array2'), array('elem1'=>'One <ins>1</ins>','elem2'=>'Two <del>2</del>','elem3'=>'Three <addr>3</addr>')); @@ -94,7 +99,7 @@ class OldSecurityTest extends \Codeception\TestCase\Test $security->encodeHTML('object1.prop3'); // affects only 'prop3' property $obj->prop3 = '<strong>Strong</strong> Baby'; $this->assertEquals(Context::get('object1'), $obj); - $this->_before(); // reset + $this->_reset(); // reset $obj->prop3 = 'Strong Baby'; $this->assertEquals(Context::get('object1'), $obj); $security->encodeHTML('object1.'); // affects all properties diff --git a/tests/unit/classes/TemplateHandlerTest.php b/tests/unit/classes/TemplateHandlerTest.php deleted file mode 100644 index 8d232eab3..000000000 --- a/tests/unit/classes/TemplateHandlerTest.php +++ /dev/null @@ -1,508 +0,0 @@ - 10">Link
                ', - '?>cond > 10){ ?> class="active">Link' - ), - // cond - array( - 'Link1say, hello', - '?>Link1cond){ ?>say, hello' - ), - // cond - array( - 'Link1Link2', - '?>Link1v==$__Context->k){ ?>Link2' - ), - // for loop - array( - '', - '?>
                  i=0;$__Context->i<$__Context->len;$__Context->i++){ ?>
                • Link
                ' - ), - // foreach loop - array( - '', - '?>
                  arr)foreach($__Context->arr as $__Context->key=>$__Context->val){ ?>
                • Link
                    arr2)foreach($__Context->arr2 as $__Context->key2=>$__Context->val2){ ?>
                ' - ), - // while loop - array( - '', - '?>
                  item=get_loop_item()){ ?>
                • Link
                ' - ), - // ~ - array( - 'LinkHello, world ', - '?>Linkcond){ ?>Hello, world ' - ), - // ~ - array( - 'LinkHello, {$world}', - '?>Linkcond){ ?>Hello, world ?>' - ), - // ~ ~ - array( - 'LinkHello, worldWow', - '?>Linkcond){ ?>Hello, worldWow' - ), - // ~ ~ ~ - array( - 'LinkHello, worldHaHaWow', - '?>Linkcond){ ?>Hello, worldcond2){ ?>HaHaWow' - ), - // ~ - array( - '
              • Repeat this
              • ', - PHP_EOL.'for($__Context->i=0;$__Context->i<$__Context->len;$__Context->i++){ ?>
              • Repeat this
              • ' - ), - // ~ - array( - '
              • item{$key} : {$val}
              • ', - PHP_EOL . 'if($__Context->arr)foreach($__Context->arr as $__Context->key=>$__Context->val){ ?>
              • itemkey ?> : val ?>
              • ' - ), - // ~ - array( - '{$v->text}', - PHP_EOL.'while($__Context->item=$__Context->list->getItem()){ ?>v->text ?>' - ), - // ~ ~ ~ ~ - array( - ' A BC', - '?>var){;'.PHP_EOL.'case "A": ?> ABC' - ), - // invalid block statement - array( - '', - '?>' - ), - // {@ ...PHP_CODE...} - array( - '{@$list_page = $page_no}', - '?>list_page = $__Context->page_no ?>' - ), - // %load_js_plugin - array( - '', - '?>' - ), - // #include - array( - '
                This is another dummy
                ', - '?>compile(\'tests/unit/classes/template\',\'sample.html\') ?>
                This is another dummy
                ' - ), - // - array( - '
                This is another dummy
                ', - '?>compile(\'tests/unit/classes\',\'sample.html\') ?>
                This is another dummy
                ' - ), - // - array( - '', - '?>' - ), - // - array( - '', - '?>' - ), - // - array( - '', - '?>' - ), - // - array( - '', - '?>compile(); ?>' - ), - // - array( - '', - '?>' - ), - // - array( - '', - '?>' - ), - // comment - array( - '', - '?>' - ), - // self-closing tag - array( - '', - PHP_EOL . 'if($__Context->foo){ ?>' - ), - // relative path1 - array( - '', - '?>' - ), - // relative path2 - array( - '', - '?>' - ), - // error case - array( - 'logo', - PHP_EOL . 'if($__Context->layout_info->logo_image){ ?>logo' - ), - // error case - ignore stylesheets - array( - '', - '?>' - ), - // error case - ignore json - array( - '', - '?>' - ), - // error case - inline javascript - array( - '
                ', - '?>
                ' - ), - // issue 103 - array( - '', - '?>' - ), - // issue 135 - array( - '

                {$key}

                Loop block {$val}
                ', - PHP_EOL . 'if($__Context->_m_list_all)foreach($__Context->_m_list_all as $__Context->key=>$__Context->val){ ?>

                key ?>

                Loop block val ?>
                ' - ), - // issue 136 - array( - '
                bar', - PHP_EOL . 'if($__Context->var==\'foo\'){ ?>
                bar' - ), - // issue 188 - array( - '
                Hello, world!
                ', - PHP_EOL . 'if($__Context->ii < $__Context->nn){;' . PHP_EOL . 'if($__Context->dummy)foreach($__Context->dummy as $__Context->k=>$__Context->v){ ?>
                Hello, world!
                ' - ), - // issue 190 - array( - '
                Hello, world!
                ', - PHP_EOL . 'if(!($__Context->i >= $__Context->n)){;' . PHP_EOL . 'if($__Context->dummy)foreach($__Context->dummy as $__Context->k=>$__Context->v){ ?>
                Hello, world!
                ' - ), - // issue 183 - array( - ''."\n".'
                {$vvv}
                CD
                ', - '?>vvvls)foreach($__Context->vvvls as $__Context->vvv){ ?>'."\n".'
                vvv ?>
                CD
                ' - ), - // issue 512 - ignores - array( - '
                {$lang->sl_show_topimgtext}
                ', - '?>
                sl_show_topimgtext ?>
                ' - ), - // issue 584 - array( - 'mobile', - PHP_EOL . 'if($__Context->oBodex->display_extra_images[\'mobile\'] && $__Context->arr_extra && $__Context->arr_extra->bodex->mobile){ ?>mobile' - ), - // issue 831 - array( - "
              • class=\"on\">", - "?>
              • act, array(\n'dispNmsAdminGroupList',\n'dispNmsAdminInsertGroup',\n'dispNmsAdminGroupInfo',\n'dispNmsAdminDeleteGroup'))){ ?>class=\"on\">" - ), - // issue 746 - array( - '', - '?>' - ), - // issue 696 - array( - '{@ eval(\'$val = $document_srl;\')}', - PHP_EOL . 'eval(\'$__Context->val = $__Context->document_srl;\') ?>' - ), - // https://github.com/xpressengine/xe-core/issues/1510 - array( - '', - PHP_EOL . 'if($__Context->foo->bar){ ?>' - ), - // https://github.com/xpressengine/xe-core/issues/1510 - array( - 'a!@#$%^&*()_-=[]{}?/', - PHP_EOL . 'if($__Context->foo->bar > 100){ ?>a!@#$%^&*()_-=[]{}?/' - ), - // https://github.com/xpressengine/xe-core/issues/1510 - array( - '', - PHP_EOL . 'if($__Context->foo->bar){ ?>' - ), - // https://github.com/xpressengine/xe-core/issues/1510 - array( - '', - PHP_EOL . 'if(!$__Context->module_info->title){ ?>' - ), - // https://github.com/xpressengine/xe-core/issues/1510 - array( - '', - PHP_EOL . 'if($__Context->mi->title){ ?>mi->use){ ?> class="tmp_class" src="/rhymix/tests/unit/classes/img/common/blank.gif" />' - ), - array( - ' alt', - '?> foo->bar){ ?>alt' - ), - array( - '' . "\n" . ' alt', - '?>' . PHP_EOL . ' foo->bar){ ?>alt' - ), - array( - 'asf ', - '?>asf ' - ), - array( - '', - '?>' - ), - array( - 'asdf src="../img/img.gif" asdf src asdf src="../img/img.gif" asdf', - '?>asdf src="../img/img.gif" asdf src asdf src="../img/img.gif" asdf' - ), - array( - 'asdf src="../img/img.gif" asdf', - '?>asdf src="../img/img.gif" asdf' - ), - // Rhymix improvements (PR #604) - array( - '{$_SERVER["REMOTE_ADDR"]}', - '?>' - ), - array( - '{escape($_COOKIE[$var], false)}', - '?>var], false) ?>' - ), - array( - '{$GLOBALS[$__Context->rhymix->rules]}', - '?>rhymix->rules] ?>' - ), - array( - '{$FOOBAR}', - '?>FOOBAR ?>' - ), - array( - '{RX_BASEDIR}', - '?>{RX_BASEDIR}' - ), - array( - '{\RX_BASEDIR}', - '?>' - ), - // Rhymix autoescape - array( - '{$foo}', - PHP_EOL . '$this->config->autoescape = \'on\';' . "\n" . 'echo ($this->config->autoescape === \'on\' ? htmlspecialchars($__Context->foo, ENT_QUOTES, \'UTF-8\', false) : ($__Context->foo)) ?>' - ), - array( - '{$foo}', - PHP_EOL . '$this->config->autoescape = \'off\';' . "\n" . 'echo ($this->config->autoescape === \'on\' ? htmlspecialchars($__Context->foo, ENT_QUOTES, \'UTF-8\', false) : ($__Context->foo)) ?>' - ), - array( - '{$foo|auto}', - PHP_EOL . '$this->config->autoescape = \'on\';' . "\n" . 'echo ($this->config->autoescape === \'on\' ? htmlspecialchars($__Context->foo, ENT_QUOTES, \'UTF-8\', false) : ($__Context->foo)) ?>' - ), - array( - '{$foo|auto}', - PHP_EOL . '$this->config->autoescape = \'off\';' . "\n" . 'echo ($this->config->autoescape === \'on\' ? htmlspecialchars($__Context->foo, ENT_QUOTES, \'UTF-8\', false) : ($__Context->foo)) ?>' - ), - array( - '{$foo|autoescape}', - PHP_EOL . '$this->config->autoescape = \'on\';' . "\n" . 'echo htmlspecialchars($__Context->foo, ENT_QUOTES, \'UTF-8\', false) ?>' - ), - array( - '{$foo|autoescape}', - PHP_EOL . '$this->config->autoescape = \'off\';' . "\n" . 'echo htmlspecialchars($__Context->foo, ENT_QUOTES, \'UTF-8\', false) ?>' - ), - array( - '{$foo|escape}', - PHP_EOL . '$this->config->autoescape = \'on\';' . "\n" . 'echo htmlspecialchars($__Context->foo, ENT_QUOTES, \'UTF-8\', true) ?>' - ), - array( - '{$foo|escape}', - PHP_EOL . '$this->config->autoescape = \'off\';' . "\n" . 'echo htmlspecialchars($__Context->foo, ENT_QUOTES, \'UTF-8\', true) ?>' - ), - array( - '{$foo|noescape}', - PHP_EOL . '$this->config->autoescape = \'on\';' . "\n" . 'echo $__Context->foo ?>' - ), - array( - '{$foo|noescape}', - PHP_EOL . '$this->config->autoescape = \'off\';' . "\n" . 'echo $__Context->foo ?>' - ), - // Rhymix filters - array( - '

                {$foo|escape}

                ', - '?>

                foo, ENT_QUOTES, \'UTF-8\', true) ?>

                ' - ), - array( - '

                {$foo|json}

                ', - '?>

                foo) ?>

                ' - ), - array( - '

                {$foo|urlencode}

                ', - '?>

                foo) ?>

                ' - ), - array( - '

                {$foo|lower|nl2br}

                ', - '?>

                foo)) ?>

                ' - ), - array( - '

                {$foo|join:/|upper}

                ', - '?>

                foo)) ?>

                ' - ), - array( - '

                {$foo|join:\||upper}

                ', - '?>

                foo)) ?>

                ' - ), - array( - '

                {$foo|join:$separator}

                ', - '?>

                separator, $__Context->foo) ?>

                ' - ), - array( - '

                {$foo|strip}

                ', - '?>

                foo) ?>

                ' - ), - array( - '

                {$foo|strip:
                }

                ', - '?>

                foo, \'
                \') ?>

                ' - ), - array( - '

                {$foo|strip:$mytags}

                ', - '?>

                foo, $__Context->mytags) ?>

                ' - ), - array( - '

                {$foo|strip:myfunc($mytags)}

                ', - '?>

                foo, myfunc($__Context->mytags)) ?>

                ' - ), - array( - '

                {$foo|trim|date}

                ', - '?>

                foo)), \'Y-m-d H:i:s\') ?>

                ' - ), - array( - '

                {$foo|date:His}

                ', - '?>

                foo), \'His\') ?>

                ' - ), - array( - '

                {$foo|format:2}

                ', - '?>

                foo, \'2\') ?>

                ' - ), - array( - '

                {$foo|date:His}

                ', - '?>

                foo), \'His\') ?>

                ' - ), - array( - '

                {$foo|link}

                ', - '?>

                foo . \'">\' . $__Context->foo . \'\' ?>

                ' - ), - array( - '

                {$foo|link:http://www.rhymix.org}

                ', - '?>

                \' . $__Context->foo . \'\' ?>

                ' - ), - array( - '

                {$foo|link:$url}

                ', - '?>

                url . \'">\' . $__Context->foo . \'\' ?>

                ' - ), - array( - '

                {$foo|link:$url}

                ', - PHP_EOL . '$this->config->autoescape = \'on\'; ?>

                config->autoescape === \'on\' ? htmlspecialchars($__Context->url, ENT_QUOTES, \'UTF-8\', false) : ($__Context->url)) . \'">\' . ($this->config->autoescape === \'on\' ? htmlspecialchars($__Context->foo, ENT_QUOTES, \'UTF-8\', false) : ($__Context->foo)) . \'\' ?>

                ' - ), - // Rhymix filters (reject malformed filters) - array( - '

                {$foo|dafuq}

                ', - '?>

                ' - ), - array( - '

                {$foo|4}

                ', - '?>

                foo|4 ?>

                ' - ), - array( - '

                {$foo|a+7|lower}

                ', - '?>

                foo|a+7) ?>

                ' - ), - array( - '

                {$foo|Filter}

                ', - '?>

                foo|Filter ?>

                ' - ), - array( - '

                {$foo|filter++}

                ', - '?>

                foo|filter++ ?>

                ' - ), - array( - '

                {$foo|filter:}

                ', - '?>

                foo|filter: ?>

                ' - ), - array( - '

                {$foo|$bar}

                ', - '?>

                foo|$__Context->bar ?>

                ' - ), - array( - '

                {$foo||bar}

                ', - '?>

                foo||bar ?>

                ' - ), - array( - '

                {htmlspecialchars($var, ENT_COMPAT | ENT_HTML401)}

                ', - '?>

                var, ENT_COMPAT | ENT_HTML401) ?>

                ' - ), - array( - '

                {$foo | $bar}

                ', - '?>

                foo | $__Context->bar ?>

                ' - ), - ); - - foreach ($tests as $test) - { - $tmpl = new TemplateHandlerWrapper; - $tmpl->init(__DIR__ . '/template', 'no_file.html'); - $result = $tmpl->parse($test[0]); - $this->assertEquals($this->prefix . $test[1], $result); - } - } - - public function testParseNoContent() - { - $tmpl = new TemplateHandlerWrapper; - $tmpl->init(__DIR__ . '/template', 'no_file.html'); - $result = $tmpl->parse($tpl); - - $this->assertEquals('', $result); - } - - public function testCompileDirect() - { - $tmpl = TemplateHandler::getInstance(); - $result = $tmpl->compileDirect(__DIR__ . '/template', 'sample.html'); - $result = trim($result); - - $this->assertEquals($result, $this->prefix.PHP_EOL.'if($__Context->has_blog){ ?>Taggon\'s blog'.PHP_EOL.''); - } -} - - -class TemplateHandlerWrapper extends \TemplateHandler { - private $inst; - - function __construct() { - $this->inst = parent::getInstance(); - } - - public function init($tpl_path, $tpl_filename, $tpl_file = '') { - call_user_func(array($this->inst, 'init'), $tpl_path, $tpl_filename, $tpl_file); - } - - public function parse($buff = null) { - return call_user_func(array($this->inst, 'parse'), $buff); - } -} diff --git a/tests/unit/classes/ValidatorTest.php b/tests/unit/classes/ValidatorTest.php index 0524985e2..85ff61ae8 100644 --- a/tests/unit/classes/ValidatorTest.php +++ b/tests/unit/classes/ValidatorTest.php @@ -1,20 +1,26 @@ init(); - + $ob_level = ob_get_level(); while (ob_get_level() > $ob_level) { ob_end_clean(); } } + public function _after() + { + Rhymix\Framework\Storage::deleteDirectory(__DIR__ . '/validator/ruleset', true); + } + + public function _failed() + { + Rhymix\Framework\Storage::deleteDirectory(__DIR__ . '/validator/ruleset', true); + } + public function testRequired() { $vd = new Validator(); @@ -187,20 +193,5 @@ class ValidatorTest extends \Codeception\TestCase\Test $js = $vd->getJsPath(); $this->assertEquals(trim(file_get_contents(__DIR__ . '/validator/condition.en.js')), trim(file_get_contents($js))); } - - protected function tearDown() - { - // remove cache directory - $cache_dir = __DIR__ . '/validator/ruleset'; - if(is_dir($cache_dir)) - { - $files = (array)glob($cache_dir.'/*'); - foreach($files as $file) - { - unlink($file); - } - rmdir($cache_dir); - } - } } diff --git a/tests/unit/classes/validator/condition.en.js b/tests/unit/classes/validator/condition.en.js index 159668a3e..5801af061 100644 --- a/tests/unit/classes/validator/condition.en.js +++ b/tests/unit/classes/validator/condition.en.js @@ -18,5 +18,5 @@ v.cast('ADD_MESSAGE',['invalid_alpha_number','%s의 형식이 잘못되었습니 v.cast('ADD_MESSAGE',['invalid_mid','%s의 형식이 잘못되었습니다. 첫 글자는 영문으로 시작해야 하며 \'영문+숫자+_\'로만 입력해야 합니다.']); v.cast('ADD_MESSAGE',['invalid_number','%s의 형식이 잘못되었습니다. 숫자로만 입력해야 합니다.']); v.cast('ADD_MESSAGE',['invalid_float','%s의 형식이 잘못되었습니다. 숫자로만 입력해야 합니다.']); -v.cast('ADD_MESSAGE',['invalid_extension','%s의 형식이 잘못되었습니다. *.* 나 *.jpg;*.gif; 처럼 입력해야 합니다.']); +v.cast('ADD_MESSAGE',['invalid_extension','%s의 형식이 잘못되었습니다. gif, jpg, png 등 쉼표로 구분하여 입력해야 합니다.']); })(jQuery); diff --git a/tests/unit/framework/CacheTest.php b/tests/unit/framework/CacheTest.php index d8997e903..c8d6f46bd 100644 --- a/tests/unit/framework/CacheTest.php +++ b/tests/unit/framework/CacheTest.php @@ -1,6 +1,6 @@ 'file')); - $this->assertTrue($driver instanceof Rhymix\Framework\Drivers\Cache\File); - + $driver = Rhymix\Framework\Cache::init(array('type' => 'dummy')); + $this->assertTrue($driver instanceof Rhymix\Framework\Drivers\Cache\Dummy); + $driver = Rhymix\Framework\Cache::init(array('type' => 'sqlite')); $this->assertTrue($driver instanceof Rhymix\Framework\Drivers\Cache\SQLite); - + $driver = Rhymix\Framework\Cache::init(array()); $this->assertTrue($driver instanceof Rhymix\Framework\Drivers\Cache\Dummy); } - + public function testGetSupportedDrivers() { $drivers = Rhymix\Framework\Cache::getSupportedDrivers(); $this->assertTrue(is_array($drivers)); $this->assertContains('dummy', $drivers); - $this->assertContains('file', $drivers); + $this->assertFalse(in_array('file', $drivers)); $this->assertContains('sqlite', $drivers); } - + public function testGetDriverName() { $driver = Rhymix\Framework\Cache::init(array('type' => 'dummy')); $this->assertEquals('dummy', Rhymix\Framework\Cache::getDriverName()); - + $driver = Rhymix\Framework\Cache::init(array('type' => 'sqlite')); $this->assertEquals('sqlite', Rhymix\Framework\Cache::getDriverName()); } - + public function testGetDriverInstance() { $driver = Rhymix\Framework\Cache::getDriverInstance('dummy'); $this->assertTrue($driver instanceof Rhymix\Framework\Drivers\Cache\Dummy); - + $driver = Rhymix\Framework\Cache::getDriverInstance(); $this->assertTrue($driver instanceof Rhymix\Framework\Drivers\Cache\File); } - + public function testGetPrefix() { $prefix = Rhymix\Framework\Cache::getPrefix(); $this->assertEquals(\RX_VERSION . ':', $prefix); } - + public function testGetSet() { $value = true; $this->assertTrue(Rhymix\Framework\Cache::set('foobar1', $value)); $this->assertTrue(Rhymix\Framework\Cache::get('foobar1')); - + $value = false; $this->assertTrue(Rhymix\Framework\Cache::set('foobar2', $value)); $this->assertFalse(Rhymix\Framework\Cache::get('foobar2')); - + $value = 1756234; $this->assertTrue(Rhymix\Framework\Cache::set('foobar3', $value)); $this->assertEquals($value, Rhymix\Framework\Cache::get('foobar3')); - + $value = 'Rhymix is a PHP CMS.'; $this->assertTrue(Rhymix\Framework\Cache::set('foobar4', $value)); $this->assertEquals($value, Rhymix\Framework\Cache::get('foobar4')); - + $value = array('foo' => 'bar', 'rhy' => 'mix'); $this->assertTrue(Rhymix\Framework\Cache::set('foobar:subkey:5', $value)); $this->assertEquals($value, Rhymix\Framework\Cache::get('foobar:subkey:5')); - + $value = (object)array('foo' => 'bar', 'rhy' => 'mix'); $this->assertTrue(Rhymix\Framework\Cache::set('foobar:subkey:6', $value)); $this->assertEquals($value, Rhymix\Framework\Cache::get('foobar:subkey:6')); - + $this->assertNull(Rhymix\Framework\Cache::get('foobar7')); $this->assertNull(Rhymix\Framework\Cache::get('foobar:subkey:8')); } - + public function testDeleteAndExists() { Rhymix\Framework\Cache::set('foo', 'FOO'); Rhymix\Framework\Cache::set('bar', 'BAR'); - + $this->assertTrue(Rhymix\Framework\Cache::delete('foo')); $this->assertFalse(Rhymix\Framework\Cache::delete('foo')); $this->assertFalse(Rhymix\Framework\Cache::exists('foo')); $this->assertTrue(Rhymix\Framework\Cache::exists('bar')); } - + public function testIncrDecr() { Rhymix\Framework\Cache::init(array('type' => 'sqlite')); Rhymix\Framework\Cache::set('foo', 'foo'); Rhymix\Framework\Cache::set('bar', 42); $prefix = Rhymix\Framework\Cache::getPrefix(); - + $this->assertEquals(1, Rhymix\Framework\Cache::getDriverInstance()->incr($prefix . 'foo', 1)); + $this->assertEquals(8, Rhymix\Framework\Cache::getDriverInstance()->incr($prefix . 'foo', 7)); + $this->assertEquals(-7, Rhymix\Framework\Cache::decr('foo', 15)); $this->assertEquals(45, Rhymix\Framework\Cache::getDriverInstance()->incr($prefix . 'bar', 3)); - $this->assertEquals(-1, Rhymix\Framework\Cache::getDriverInstance()->decr($prefix . 'foo', 2)); - $this->assertEquals(49, Rhymix\Framework\Cache::getDriverInstance()->decr($prefix . 'bar', -4)); + $this->assertEquals(60, Rhymix\Framework\Cache::incr('bar', 15)); + $this->assertEquals(20, Rhymix\Framework\Cache::getDriverInstance()->incr($prefix . 'bar', -40)); } - + public function testClearAll() { $this->assertTrue(Rhymix\Framework\Cache::set('foo', 'foo')); @@ -123,30 +126,30 @@ class CacheTest extends \Codeception\TestCase\Test $this->assertTrue(Rhymix\Framework\Cache::clearAll()); $this->assertFalse(Rhymix\Framework\Cache::exists('foo')); } - + public function testCacheGroups() { Rhymix\Framework\Cache::init(array('type' => 'sqlite')); $prefix = Rhymix\Framework\Cache::getPrefix(); - + $this->assertTrue(Rhymix\Framework\Cache::set('foobar:subkey:1234', 'rhymix')); $this->assertTrue(Rhymix\Framework\Cache::exists('foobar:subkey:1234')); $this->assertEquals('rhymix', Rhymix\Framework\Cache::get('foobar:subkey:1234')); $this->assertEquals('rhymix', Rhymix\Framework\Cache::getDriverInstance()->get($prefix . 'foobar#0:subkey:1234')); $this->assertEquals(0, Rhymix\Framework\Cache::getGroupVersion('foobar')); - + $this->assertTrue(Rhymix\Framework\Cache::clearGroup('foobar')); $this->assertFalse(Rhymix\Framework\Cache::exists('foobar:subkey:1234')); $this->assertTrue(Rhymix\Framework\Cache::set('foobar:subkey:1234', 'rhymix')); $this->assertEquals('rhymix', Rhymix\Framework\Cache::getDriverInstance()->get($prefix . 'foobar#1:subkey:1234')); $this->assertEquals(1, Rhymix\Framework\Cache::getGroupVersion('foobar')); } - + public function testGetRealKey() { Rhymix\Framework\Cache::init(array('type' => 'sqlite')); $prefix = Rhymix\Framework\Cache::getPrefix(); - + $this->assertEquals($prefix . 'foo', Rhymix\Framework\Cache::getRealKey('foo')); $this->assertEquals($prefix . 'bar#0:2016', Rhymix\Framework\Cache::getRealKey('bar:2016')); Rhymix\Framework\Cache::clearGroup('bar'); @@ -154,34 +157,34 @@ class CacheTest extends \Codeception\TestCase\Test Rhymix\Framework\Cache::clearGroup('bar'); $this->assertEquals($prefix . 'bar#2:2016', Rhymix\Framework\Cache::getRealKey('bar:2016')); } - + public function testCompatibility() { Rhymix\Framework\Cache::init(array('type' => 'sqlite')); $ch = \CacheHandler::getInstance(); $this->assertTrue($ch instanceof \CacheHandler); $this->assertTrue($ch->isSupport()); - + $this->assertEquals('rhymix', $ch->getCacheKey('rhymix')); $this->assertEquals('rhymix:123:456', $ch->getCacheKey('rhymix:123:456')); - + $this->assertTrue($ch->put('rhymix', 'foo bar buzz')); $this->assertEquals('foo bar buzz', $ch->get('rhymix')); $this->assertTrue($ch->isValid('rhymix')); $this->assertTrue($ch->delete('rhymix')); $this->assertFalse($ch->get('rhymix')); $this->assertFalse($ch->isValid('rhymix')); - + $this->assertEquals('rhymix:123:456', $ch->getGroupKey('rhymix', '123:456')); $this->assertTrue($ch->put('rhymix:123:456', 'rhymix rules!')); $this->assertEquals('rhymix rules!', $ch->get('rhymix:123:456')); $this->assertEquals(0, Rhymix\Framework\Cache::getGroupVersion('rhymix')); - + $this->assertTrue($ch->invalidateGroupKey('rhymix')); $this->assertTrue($ch->put('rhymix:123:456', 'rhymix rules!')); $this->assertEquals('rhymix rules!', $ch->get('rhymix:123:456')); $this->assertEquals(1, Rhymix\Framework\Cache::getGroupVersion('rhymix')); - + $this->assertTrue($ch->truncate()); $this->assertFalse($ch->get('rhymix:123:456')); } diff --git a/tests/unit/framework/CalendarTest.php b/tests/unit/framework/CalendarTest.php index 19388aec6..44759ca82 100644 --- a/tests/unit/framework/CalendarTest.php +++ b/tests/unit/framework/CalendarTest.php @@ -1,6 +1,6 @@ assertEquals($target_201508, Rhymix\Framework\Calendar::getMonthCalendar(8, 2015)); $this->assertEquals($target_201603, Rhymix\Framework\Calendar::getMonthCalendar(3, 2016)); } diff --git a/tests/unit/framework/ConfigTest.php b/tests/unit/framework/ConfigTest.php index b9ef136d2..ebc3fc5a7 100644 --- a/tests/unit/framework/ConfigTest.php +++ b/tests/unit/framework/ConfigTest.php @@ -1,6 +1,6 @@ assertTrue(version_compare(Rhymix\Framework\Config::get('config_version'), '2.0', '>=')); $this->assertTrue(is_array(Rhymix\Framework\Config::get('db.master'))); $this->assertNotEmpty(Rhymix\Framework\Config::get('db.master.host')); - + Rhymix\Framework\Config::set('foo.bar', $rand = mt_rand()); $this->assertEquals(array('bar' => $rand), Rhymix\Framework\Config::get('foo')); $this->assertEquals($rand, Rhymix\Framework\Config::get('foo.bar')); - + $var = array('foo' => 'bar'); $serialized = "array(\n\t'foo' => 'bar',\n)"; $this->assertEquals($serialized, Rhymix\Framework\Config::serialize($var)); diff --git a/tests/unit/framework/DBTest.php b/tests/unit/framework/DBTest.php new file mode 100644 index 000000000..730b25aed --- /dev/null +++ b/tests/unit/framework/DBTest.php @@ -0,0 +1,272 @@ +setDebugComment(false); + } + + public function testGetInstance() + { + $oDB = Rhymix\Framework\DB::getInstance(); + $this->assertTrue($oDB instanceof Rhymix\Framework\DB); + $this->assertEquals($oDB, \DB::getInstance()); + $this->assertTrue(\DB::getInstance() instanceof Rhymix\Framework\DB); + $this->assertTrue($oDB->getHandle() instanceof Rhymix\Framework\Helpers\DBHelper); + } + + public function testConnectDisconnect() + { + $oDB = Rhymix\Framework\DB::getInstance('master'); + $this->assertTrue(is_object($oDB->getHandle())); + + $oDB->disconnect(); + $this->assertTrue(is_null($oDB->getHandle())); + $this->assertFalse($oDB->isConnected()); + + $oDB->connect(config('db.master')); + $this->assertTrue(is_object($oDB->getHandle())); + $this->assertTrue($oDB->isConnected()); + + $oDB = Rhymix\Framework\DB::getInstance('master'); + $this->assertTrue(is_object($oDB->getHandle())); + } + + public function testCompatProperties() + { + $oDB = Rhymix\Framework\DB::getInstance(); + $this->assertEquals('mysql', $oDB->db_type); + $this->assertEquals($oDB->getHandle()->getAttribute(\PDO::ATTR_SERVER_VERSION), $oDB->db_version); + $this->assertEquals(Rhymix\Framework\Config::get('db.master.prefix'), $oDB->prefix); + $this->assertTrue($oDB->use_prepared_statements); + $this->assertNull($oDB->some_nonexistent_property); + } + + public function testPrepare() + { + $oDB = Rhymix\Framework\DB::getInstance(); + $prefix = Rhymix\Framework\Config::get('db.master.prefix'); + + $stmt = $oDB->prepare('SELECT * FROM documents WHERE document_srl = ?'); + $this->assertTrue($stmt instanceof Rhymix\Framework\Helpers\DBStmtHelper); + if ($prefix) + { + $this->assertEquals('SELECT * FROM `' . $prefix . 'documents` AS `documents` WHERE document_srl = ?', $stmt->queryString); + } + else + { + $this->assertEquals('SELECT * FROM documents WHERE document_srl = ?', $stmt->queryString); + } + + $this->assertTrue($stmt->execute([123])); + $this->assertTrue($stmt->execute([456])); + $this->assertTrue($stmt->execute([789])); + $this->assertTrue(is_array($stmt->fetchAll())); + $this->assertTrue($stmt->closeCursor()); + } + + public function testQuery() + { + $oDB = Rhymix\Framework\DB::getInstance(); + $prefix = Rhymix\Framework\Config::get('db.master.prefix'); + + $stmt = $oDB->query('SELECT * FROM documents WHERE document_srl = 123'); + $this->assertTrue($stmt instanceof Rhymix\Framework\Helpers\DBStmtHelper); + if ($prefix) + { + $this->assertEquals('SELECT * FROM `' . $prefix . 'documents` AS `documents` WHERE document_srl = 123', $stmt->queryString); + } + else + { + $this->assertEquals('SELECT * FROM documents WHERE document_srl = 123', $stmt->queryString); + } + + $this->assertTrue(is_array($stmt->fetchAll())); + $this->assertTrue($stmt->closeCursor()); + + $stmt = $oDB->query('SELECT * FROM documents WHERE document_srl = ?', [123]); + $this->assertTrue($stmt instanceof Rhymix\Framework\Helpers\DBStmtHelper); + if ($prefix) + { + $this->assertEquals('SELECT * FROM `' . $prefix . 'documents` AS `documents` WHERE document_srl = ?', $stmt->queryString); + } + else + { + $this->assertEquals('SELECT * FROM documents WHERE document_srl = ?', $stmt->queryString); + } + + $this->assertTrue(is_array($stmt->fetchAll())); + $this->assertTrue($stmt->closeCursor()); + + $stmt = $oDB->query('SELECT * FROM documents WHERE document_srl = ? AND status = ?', 123, 'PUBLIC'); + $this->assertTrue($stmt instanceof Rhymix\Framework\Helpers\DBStmtHelper); + if ($prefix) + { + $this->assertEquals('SELECT * FROM `' . $prefix . 'documents` AS `documents` WHERE document_srl = ? AND status = ?', $stmt->queryString); + } + else + { + $this->assertEquals('SELECT * FROM documents WHERE document_srl = ? AND status = ?', $stmt->queryString); + } + + $this->assertTrue(is_array($stmt->fetchAll())); + $this->assertTrue($stmt->closeCursor()); + } + + public function testTransaction() + { + $oDB = Rhymix\Framework\DB::getInstance(); + $this->assertEquals(0, $oDB->getTransactionLevel()); + $this->assertEquals(1, $oDB->beginTransaction()); + $this->assertEquals(1, $oDB->getTransactionLevel()); + $this->assertEquals(2, $oDB->begin()); + $this->assertEquals(2, $oDB->getTransactionLevel()); + $this->assertEquals(1, $oDB->rollback()); + $this->assertEquals(1, $oDB->getTransactionLevel()); + $this->assertEquals(0, $oDB->commit()); + $this->assertEquals(0, $oDB->getTransactionLevel()); + } + + public function testAddPrefixes() + { + $oDB = Rhymix\Framework\DB::getInstance(); + $prefix = Rhymix\Framework\Config::get('db.master.prefix'); + + $source = 'SELECT a, b, c FROM documents JOIN comments ON documents.document_srl = comment.document_srl WHERE documents.member_srl = ?'; + $target = 'SELECT a, b, c FROM `' . $prefix . 'documents` AS `documents` JOIN `' . $prefix . 'comments` AS `comments` ' . + 'ON documents.document_srl = comment.document_srl WHERE documents.member_srl = ?'; + $this->assertEquals($target, $oDB->addPrefixes($source)); + + $source = 'SELECT a AS aa FROM documents as foo JOIN bar ON documents.a = bar.a'; + $target = 'SELECT a AS aa FROM `' . $prefix . 'documents` AS `foo` JOIN `' . $prefix . 'bar` AS `bar` ON documents.a = bar.a'; + $this->assertEquals($target, $oDB->addPrefixes($source)); + + $source = 'INSERT INTO documents (a, b, c) VALUES (?, ?, ?)'; + $target = 'INSERT INTO `' . $prefix . 'documents` (a, b, c) VALUES (?, ?, ?)'; + $this->assertEquals($target, $oDB->addPrefixes($source)); + + $source = 'INSERT INTO documents (a, b, c) SELECT d, e, f FROM old_documents WHERE g = ?'; + $target = 'INSERT INTO `' . $prefix . 'documents` (a, b, c) SELECT d, e, f FROM `' . $prefix . 'old_documents` AS `old_documents` WHERE g = ?'; + $this->assertEquals($target, $oDB->addPrefixes($source)); + + $source = 'UPDATE documents SET a = ?, b = ? WHERE c = ?'; + $target = 'UPDATE `' . $prefix . 'documents` SET a = ?, b = ? WHERE c = ?'; + $this->assertEquals($target, $oDB->addPrefixes($source)); + + $source = 'DELETE FROM documents WHERE d = ?'; + $target = 'DELETE FROM `' . $prefix . 'documents` WHERE d = ?'; + $this->assertEquals($target, $oDB->addPrefixes($source)); + + $source = 'WITH cte AS (SELECT * FROM documents) SELECT * FROM cte WHERE document_srl = ?'; + $target = 'WITH cte AS (SELECT * FROM `' . $prefix . 'documents` AS `documents`) SELECT * FROM `cte` WHERE document_srl = ?'; + $this->assertEquals($target, $oDB->addPrefixes($source)); + + $source = 'WITH RECURSIVE cte AS (SELECT * FROM documents INNER JOIN `cte`) SELECT * FROM cte JOIN member on cte.member_srl = member.member_srl'; + $target = 'WITH RECURSIVE cte AS (SELECT * FROM `' . $prefix . 'documents` AS `documents` INNER JOIN `cte`) SELECT * FROM `cte` JOIN `rx_member` AS `member` on cte.member_srl = member.member_srl'; + $this->assertEquals($target, $oDB->addPrefixes($source)); + + $source = 'WITH RECURSIVE `cte` AS (SELECT * FROM tbl INNER JOIN cte AS h) SELECT * FROM cte WHERE a = ?'; + $target = 'WITH RECURSIVE `cte` AS (SELECT * FROM `' . $prefix . 'tbl` AS `tbl` INNER JOIN `cte` AS `h`) SELECT * FROM `cte` WHERE a = ?'; + $this->assertEquals($target, $oDB->addPrefixes($source)); + + $source = 'INSERT INTO documents (a, b, c) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE b = ?, c = ?'; + $target = 'INSERT INTO `' . $prefix . 'documents` (a, b, c) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE b = ?, c = ?'; + $this->assertEquals($target, $oDB->addPrefixes($source)); + + $source = "LOAD DATA LOCAL INFILE '/tmp/foo.csv' INTO TABLE foo_table FIELDS TERMINATED BY ',' ENCLOSED BY '\"' LINES TERMINATED BY '\\n' (a, b, c)"; + $target = "LOAD DATA LOCAL INFILE '/tmp/foo.csv' INTO TABLE `" . $prefix . "foo_table` FIELDS TERMINATED BY ',' ENCLOSED BY '\"' LINES TERMINATED BY '\\n' (a, b, c)"; + $this->assertEquals($target, $oDB->addPrefixes($source)); + + $source = 'ALTER TABLE documents ADD INDEX idx_foo (a, b)'; + $target = 'ALTER TABLE `' . $prefix . 'documents` ADD INDEX idx_foo (a, b)'; + $this->assertEquals($target, $oDB->addPrefixes($source)); + + $source = 'TRUNCATE TABLE documents'; + $target = 'TRUNCATE TABLE `' . $prefix . 'documents`'; + $this->assertEquals($target, $oDB->addPrefixes($source)); + + $source = 'DROP TABLE documents'; + $target = 'DROP TABLE `' . $prefix . 'documents`'; + $this->assertEquals($target, $oDB->addPrefixes($source)); + + $source = 'update documents set a = ?, b = ? where c = ?'; + $this->assertEquals($source, $oDB->addPrefixes($source)); + + $source = 'delete from documents where d = ?'; + $this->assertEquals($source, $oDB->addPrefixes($source)); + } + + public function testIsTableColumnIndexExists() + { + $oDB = Rhymix\Framework\DB::getInstance(); + $this->assertTrue($oDB->isTableExists('documents')); + $this->assertTrue($oDB->isColumnExists('documents', 'document_srl')); + $this->assertTrue($oDB->isIndexExists('documents', 'idx_regdate')); + $this->assertFalse($oDB->isTableExists('nxdocuments')); + $this->assertFalse($oDB->isColumnExists('documents', 'document_nx')); + $this->assertFalse($oDB->isIndexExists('documents', 'idx_regex')); + } + + public function testGetColumnInfo() + { + $oDB = Rhymix\Framework\DB::getInstance(); + $info = $oDB->getColumnInfo('documents', 'document_srl'); + $this->assertTrue(is_object($info)); + $this->assertEquals('document_srl', $info->name); + $this->assertEquals('bigint', $info->dbtype); + $this->assertEquals('bignumber', $info->xetype); + $this->assertNull($info->default_value); + $this->assertTrue($info->notnull); + } + + public function testGetIndexInfo() + { + $oDB = Rhymix\Framework\DB::getInstance(); + + $info = $oDB->getIndexInfo('member', 'idx_nick_name'); + $this->assertTrue(is_object($info)); + $this->assertFalse($info->is_unique); + $this->assertEquals(1, count($info->columns)); + $this->assertEquals('nick_name', $info->columns[0]->name); + $this->assertNull($info->columns[0]->size); + $this->assertNotNull($info->columns[0]->cardinality); + + $info = $oDB->getIndexInfo('module_extra_vars', 'unique_module_vars'); + $this->assertTrue(is_object($info)); + $this->assertTrue($info->is_unique); + $this->assertEquals(2, count($info->columns)); + $this->assertEquals('module_srl', $info->columns[0]->name); + $this->assertEquals('name', $info->columns[1]->name); + $this->assertNull($info->columns[1]->size); + + $info = $oDB->getIndexInfo('documents', 'idx_nonexistent'); + $this->assertNull($info); + } + + public function testIsValidOldPassword() + { + $oDB = Rhymix\Framework\DB::getInstance(); + $password = 'foobar^\'1233243'; + $saved1 = '*AA82FF6C7930626A138D0CF3E42D9581D60A85BB'; + $saved2 = '5567cb961d0e218b'; + $saved3 = str_replace('A', 'E', $saved1); + $saved4 = str_replace('6', '9', $saved2); + $this->assertTrue($oDB->isValidOldPassword($password, $saved1)); + $this->assertTrue($oDB->isValidOldPassword($password, $saved2)); + $this->assertFalse($oDB->isValidOldPassword($password, $saved3)); + $this->assertFalse($oDB->isValidOldPassword($password, $saved4)); + } + + public function testAddQuotes() + { + $oDB = Rhymix\Framework\DB::getInstance(); + $string = 'hello world \' or 1 = 1'; + $result = 'hello world \\\' or 1 = 1'; + $this->assertEquals($result, $oDB->addQuotes($string)); + $this->assertEquals('foobar', $oDB->addQuotes('foobar')); + $this->assertEquals('123.45', $oDB->addQuotes(123.45)); + $this->assertEquals('-12345', $oDB->addQuotes(-12345)); + } +} diff --git a/tests/unit/framework/DateTimeTest.php b/tests/unit/framework/DateTimeTest.php index 694c766c1..3da9ccfbd 100644 --- a/tests/unit/framework/DateTimeTest.php +++ b/tests/unit/framework/DateTimeTest.php @@ -1,153 +1,220 @@ old_timezone = @date_default_timezone_get(); date_default_timezone_set('Etc/GMT-3'); } - + public function _after() { + // Restore the default and internal timezone. + Rhymix\Framework\Config::set('locale.default_timezone', 'Asia/Seoul'); + Rhymix\Framework\Config::set('locale.internal_timezone', 10800); + Context::set('_default_timezone', $GLOBALS['_time_zone'] = 'Asia/Seoul'); + // Restore the old timezone. - date_default_timezone_set($old_timezone); + date_default_timezone_set($this->old_timezone); } - + public function testZgap() { // Test zgap() when the current user's time zone is different from the system default. $_SESSION['RHYMIX']['timezone'] = 'Etc/UTC'; $this->assertEquals(-10800, zgap()); - + // Test zgap() when the current user's time zone is the same as the system default. unset($_SESSION['RHYMIX']['timezone']); $this->assertEquals(21600, zgap()); } - + public function testZtime() { $timestamp = 1454000000; - + // Test ztime() when the internal time zone is different from the default time zone. Rhymix\Framework\Config::set('locale.internal_timezone', 10800); $this->assertEquals($timestamp, ztime('20160128195320')); - + // Test ztime() when the internal time zone is the same as the default time zone. Rhymix\Framework\Config::set('locale.internal_timezone', 32400); $this->assertEquals($timestamp, ztime('20160129015320')); - + + // Test ztime() with alternative formats. + $this->assertEquals(1576555660, ztime('2019-12-17 13:07:40')); + $this->assertEquals(1576555660, ztime('2019-12-17T13:07:40+09:00')); + $this->assertEquals(1576555660, ztime('2019-12-17T02:07:40-02:00')); + $this->assertEquals(1576555640, ztime('20191217130720')); + $this->assertEquals(1576555620, ztime('201912171307')); + // Restore the internal timezone. Rhymix\Framework\Config::set('locale.internal_timezone', 10800); } - + + public function testZtimeDateOnly() + { + $timestamp = -271641600; + + // Test ztime() when the internal time zone is different from the default time zone. + Rhymix\Framework\Config::set('locale.internal_timezone', 10800); + $this->assertEquals($timestamp, ztime('19610524')); + + // Test ztime() when the internal time zone is the same as the default time zone. + Rhymix\Framework\Config::set('locale.internal_timezone', 32400); + $this->assertEquals($timestamp, ztime('19610524')); + + // Restore the internal timezone. + Rhymix\Framework\Config::set('locale.internal_timezone', 10800); + } + public function testZdate() { $expected = '2016-01-29 01:53:20'; - + // Test zdate() when the internal time zone is different from the default time zone. Rhymix\Framework\Config::set('locale.internal_timezone', 10800); $this->assertEquals($expected, zdate('20160128195320')); - + $this->assertEquals($expected, zdate('2016-01-28 19:53:20')); + $this->assertEquals($expected, zdate('2016-01-28T09:53:20-07:00')); + $this->assertEquals($expected, zdate('2016-01-28 21:23:20+04:30')); + // Test zdate() when the internal time zone is the same as the default time zone. Rhymix\Framework\Config::set('locale.internal_timezone', 32400); $this->assertEquals($expected, zdate('20160129015320')); - + $this->assertEquals($expected, zdate('2016-01-29 01:53:20')); + $this->assertEquals($expected, zdate('2016-01-29 05:53:20+13:00')); + $this->assertEquals($expected, zdate('2016-01-28T20:53:20+04:00')); + // Restore the internal timezone. Rhymix\Framework\Config::set('locale.internal_timezone', 10800); } - + + public function testZdateDateOnly() + { + // Korea Standard Time was UTC+08:30 until 1961 + $expected = '1960-04-19 08:30:00'; + + // Test zdate() when the internal time zone is different from the default time zone. + Rhymix\Framework\Config::set('locale.internal_timezone', 10800); + $this->assertEquals($expected, zdate('19600419', 'Y-m-d H:i:s')); + + // Test zdate() when the internal time zone is the same as the default time zone. + Rhymix\Framework\Config::set('locale.internal_timezone', 32400); + $this->assertEquals($expected, zdate('19600419', 'Y-m-d H:i:s')); + + // Test special dates. + Rhymix\Framework\Config::set('locale.internal_timezone', 32400); + $expected = '1970-01-01 09:00:00'; + $this->assertEquals($expected, zdate('19700101', 'Y-m-d H:i:s')); + $expected = '1969-12-31 15:00:00'; + $this->assertEquals($expected, zdate('1969-12-31 15:00:00', 'Y-m-d H:i:s')); + Rhymix\Framework\Config::set('locale.internal_timezone', 10800); + $expected = '1970-01-01 06:00:00'; + $this->assertEquals($expected, zdate('19700101000000', 'Y-m-d H:i:s')); + $expected = '1970-01-01 00:00:00'; + $this->assertEquals($expected, zdate('19691231180000', 'Y-m-d H:i:s')); + + // Restore the internal timezone. + Rhymix\Framework\Config::set('locale.internal_timezone', 10800); + } + public function testGetInternalDateTime() { $timestamp = 1454000000; - + // Test when the internal time zone is different from the default time zone. Rhymix\Framework\Config::set('locale.internal_timezone', 10800); $this->assertEquals('20160128195320', getInternalDateTime($timestamp)); - + // Test when the internal time zone is the same as the default time zone. Rhymix\Framework\Config::set('locale.internal_timezone', 32400); $this->assertEquals('20160129015320', getInternalDateTime($timestamp)); } - + public function testGetDisplayDateTime() { $timestamp = 1454000000; - + // Test when the display time zone is different from the internal time zone. $_SESSION['RHYMIX']['timezone'] = 'America/Los_Angeles'; $this->assertEquals('20160128085320', getDisplayDateTime($timestamp)); - + // Test when the display time zone is the same as the internal time zone. $_SESSION['RHYMIX']['timezone'] = 'Etc/GMT-3'; $this->assertEquals('20160128195320', getDisplayDateTime($timestamp)); } - + public function testGetTimeGap() { $GLOBALS['lang'] = Rhymix\Framework\Lang::getInstance('en'); $GLOBALS['lang']->loadPlugin('common'); - + // Test getTimeGap() when the internal time zone is different from the default time zone. Rhymix\Framework\Config::set('locale.internal_timezone', 10800); $this->assertEquals('1 minute ago', getTimeGap(getInternalDateTime(RX_TIME - 30))); $this->assertEquals('30 minutes ago', getTimeGap(getInternalDateTime(RX_TIME - 1800))); $this->assertEquals('2 hours ago', getTimeGap(getInternalDateTime(RX_TIME - 8000))); - + // Test getTimeGap() when the internal time zone is the same as the default time zone. Rhymix\Framework\Config::set('locale.internal_timezone', 32400); $this->assertEquals('30 minutes ago', getTimeGap(getInternalDateTime(RX_TIME - 1800))); $this->assertEquals('2 hours ago', getTimeGap(getInternalDateTime(RX_TIME - 8000))); $this->assertEquals(getInternalDateTime(RX_TIME - 240000, 'Y.m.d'), getTimeGap(getInternalDateTime(RX_TIME - 240000))); } - + public function testGetTimezoneForCurrentUser() { // Test when the current user's time zone is different from the system default. $_SESSION['RHYMIX']['timezone'] = 'Pacific/Auckland'; $this->assertEquals('Pacific/Auckland', Rhymix\Framework\DateTime::getTimezoneForCurrentUser()); - + // Test when the current user's time zone is the same as the system default. unset($_SESSION['RHYMIX']['timezone']); $this->assertEquals('Asia/Seoul', Rhymix\Framework\DateTime::getTimezoneForCurrentUser()); } - + public function testFormatTimestampForCurrentUser() { $timestamp_winter = 1454000000; $timestamp_summer = $timestamp_winter - (86400 * 184); - + // Test when the current user's time zone is in the Northern hemisphere with DST. $_SESSION['RHYMIX']['timezone'] = 'America/Chicago'; $this->assertEquals('20160128 105320', Rhymix\Framework\DateTime::formatTimestampForCurrentUser('Ymd His', $timestamp_winter)); $this->assertEquals('20150728 115320', Rhymix\Framework\DateTime::formatTimestampForCurrentUser('Ymd His', $timestamp_summer)); $this->assertEquals('20150728 115320', getDisplayDateTime($timestamp_summer, 'Ymd His')); - + // Test when the current user's time zone is in the Southern hemisphere with DST. $_SESSION['RHYMIX']['timezone'] = 'Pacific/Auckland'; $this->assertEquals('20160129 055320', Rhymix\Framework\DateTime::formatTimestampForCurrentUser('Ymd His', $timestamp_winter)); $this->assertEquals('20150729 045320', Rhymix\Framework\DateTime::formatTimestampForCurrentUser('Ymd His', $timestamp_summer)); $this->assertEquals('20150729 045320', getDisplayDateTime($timestamp_summer, 'Ymd His')); - + // Test when the current user's time zone is the same as the system default without DST. unset($_SESSION['RHYMIX']['timezone']); $this->assertEquals('20160129 015320', Rhymix\Framework\DateTime::formatTimestampForCurrentUser('Ymd His', $timestamp_winter)); $this->assertEquals('20150729 015320', Rhymix\Framework\DateTime::formatTimestampForCurrentUser('Ymd His', $timestamp_summer)); $this->assertEquals('20150729 015320', getDisplayDateTime($timestamp_summer, 'Ymd His')); } - + public function testGetTimezoneList() { $tzlist = Rhymix\Framework\DateTime::getTimezoneList(); $this->assertTrue(array_key_exists('Etc/UTC', $tzlist)); $this->assertEquals('Asia/Seoul (+09:00)', $tzlist['Asia/Seoul']); } - + public function testGetTimezoneOffset() { $this->assertEquals(32400, Rhymix\Framework\DateTime::getTimezoneOffset('Asia/Seoul')); @@ -156,7 +223,7 @@ class DateTimeTest extends \Codeception\TestCase\Test $this->assertEquals(-18000, Rhymix\Framework\DateTime::getTimezoneOffset('America/New_York', strtotime('2016-01-01'))); $this->assertEquals(-14400, Rhymix\Framework\DateTime::getTimezoneOffset('America/New_York', strtotime('2015-07-01'))); } - + public function testGetTimezoneOffsetFromInternal() { $this->assertEquals(21600, Rhymix\Framework\DateTime::getTimezoneOffsetFromInternal('Asia/Seoul')); @@ -165,7 +232,7 @@ class DateTimeTest extends \Codeception\TestCase\Test $this->assertEquals(-28800, Rhymix\Framework\DateTime::getTimezoneOffsetFromInternal('America/New_York', strtotime('2016-01-01'))); $this->assertEquals(-25200, Rhymix\Framework\DateTime::getTimezoneOffsetFromInternal('America/New_York', strtotime('2015-07-01'))); } - + public function testGetTimezoneOffsetByLegacyFormat() { $this->assertEquals(32400, Rhymix\Framework\DateTime::getTimezoneOffsetByLegacyFormat('+0900')); @@ -173,7 +240,7 @@ class DateTimeTest extends \Codeception\TestCase\Test $this->assertEquals(19800, Rhymix\Framework\DateTime::getTimezoneOffsetByLegacyFormat('+0530')); $this->assertEquals(-38700, Rhymix\Framework\DateTime::getTimezoneOffsetByLegacyFormat('-1045')); } - + public function testGetTimezoneNameByOffset() { $this->assertEquals('Etc/GMT-9', Rhymix\Framework\DateTime::getTimezoneNameByOffset(32400)); @@ -182,22 +249,22 @@ class DateTimeTest extends \Codeception\TestCase\Test $this->assertEquals('Asia/Kolkata', Rhymix\Framework\DateTime::getTimezoneNameByOffset(19800)); $this->assertEquals('Australia/Eucla', Rhymix\Framework\DateTime::getTimezoneNameByOffset(31500)); } - + public function testGetRelativeTimestamp() { $GLOBALS['lang'] = Rhymix\Framework\Lang::getInstance('ko'); $GLOBALS['lang']->loadPlugin('common'); - + $this->assertEquals('방금', Rhymix\Framework\DateTime::getRelativeTimestamp(RX_TIME)); $this->assertEquals('20초 전', Rhymix\Framework\DateTime::getRelativeTimestamp(RX_TIME - 20)); $this->assertEquals('1분 전', Rhymix\Framework\DateTime::getRelativeTimestamp(RX_TIME - 60)); $this->assertEquals('30분 전', Rhymix\Framework\DateTime::getRelativeTimestamp(RX_TIME - 1800)); $this->assertEquals('10일 전', Rhymix\Framework\DateTime::getRelativeTimestamp(RX_TIME - 86400 * 10)); $this->assertEquals('6개월 전', Rhymix\Framework\DateTime::getRelativeTimestamp(RX_TIME - 86400 * 190)); - + $GLOBALS['lang'] = Rhymix\Framework\Lang::getInstance('en'); $GLOBALS['lang']->loadPlugin('common'); - + $this->assertEquals('just now', getInternalDateTime(RX_TIME + 3600, 'relative')); $this->assertEquals('5 days ago', getDisplayDateTime(RX_TIME - 86400 * 5.4, 'relative')); $this->assertEquals('3 months ago', zdate(date('YmdHis', RX_TIME - 86400 * 100), 'relative')); diff --git a/tests/unit/framework/DebugTest.php b/tests/unit/framework/DebugTest.php index 5fee9f741..8a211a321 100644 --- a/tests/unit/framework/DebugTest.php +++ b/tests/unit/framework/DebugTest.php @@ -1,15 +1,16 @@ error_log = ini_get('error_log'); ini_set('error_log', '/dev/null'); } - + public function _after() { if ($this->error_log) @@ -31,22 +32,22 @@ class DebugTest extends \Codeception\TestCase\Test Rhymix\Framework\Debug::clearEntries(); $this->assertEquals(0, count(Rhymix\Framework\Debug::getEntries())); } - + public function testDebugError() { $file = __FILE__; $line = __LINE__ + 1; - Rhymix\Framework\Debug::addError(~0, 'Rhymix', $file, $line, null); + Rhymix\Framework\Debug::addError(~0, 'Rhymix', $file, $line); $errors = Rhymix\Framework\Debug::getErrors(); $this->assertGreaterThanOrEqual(1, count($errors)); $error = array_pop($errors); - $this->assertContains('Rhymix', $error->message); + $this->assertStringContainsString('Rhymix', $error->message); $this->assertEquals($file, $error->file); $this->assertEquals($line, $error->line); Rhymix\Framework\Debug::clearErrors(); $this->assertEquals(0, count(Rhymix\Framework\Debug::getErrors())); } - + public function testDebugQuery() { Rhymix\Framework\Debug::addQuery(array( @@ -71,13 +72,13 @@ class DebugTest extends \Codeception\TestCase\Test Rhymix\Framework\Debug::clearQueries(); $this->assertEquals(0, count(Rhymix\Framework\Debug::getQueries())); } - + public function testDebugTranslateFilename() { $original_filename = __FILE__; $trans_filename = substr($original_filename, strlen(\RX_BASEDIR)); $this->assertEquals($trans_filename, Rhymix\Framework\Debug::translateFilename($original_filename)); - + $original_filename = __FILE__; $alias_filename = $original_filename . '.foobar'; $trans_filename = substr($alias_filename, strlen(\RX_BASEDIR)); diff --git a/tests/unit/framework/FormatterTest.php b/tests/unit/framework/FormatterTest.php index 7688cd7df..ed1cb6da6 100644 --- a/tests/unit/framework/FormatterTest.php +++ b/tests/unit/framework/FormatterTest.php @@ -1,75 +1,80 @@ assertEquals($html1, Rhymix\Framework\Formatter::text2html($text)); $this->assertEquals($html2, Rhymix\Framework\Formatter::text2html($text, Rhymix\Framework\Formatter::TEXT_NEWLINE_AS_P)); $this->assertEquals($html3, Rhymix\Framework\Formatter::text2html($text, Rhymix\Framework\Formatter::TEXT_DOUBLE_NEWLINE_AS_P)); } - + public function testHTML2Text() { $html = file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/html2text.source.html'); $text = file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/html2text.target.txt'); - + $this->assertEquals($text, Rhymix\Framework\Formatter::html2text($html)); } - + public function testMarkdown2HTML() { $markdown = file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/markdown2html.source.md'); - $html1 = file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/markdown2html.target1.html'); - $html2 = file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/markdown2html.target2.html'); - $html3 = file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/markdown2html.target3.html'); - $html4 = file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/markdown2html.target4.html'); - - $this->assertEquals($html1, Rhymix\Framework\Formatter::markdown2html($markdown)); - $this->assertEquals($html2, Rhymix\Framework\Formatter::markdown2html($markdown, Rhymix\Framework\Formatter::MD_NEWLINE_AS_BR)); - $this->assertEquals($html3, Rhymix\Framework\Formatter::markdown2html($markdown, Rhymix\Framework\Formatter::MD_NEWLINE_AS_BR | Rhymix\Framework\Formatter::MD_ENABLE_EXTRA)); - $this->assertEquals($html4, Rhymix\Framework\Formatter::markdown2html($markdown, Rhymix\Framework\Formatter::MD_ENABLE_EXTRA)); + $html1 = $this->_removeSpace(file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/markdown2html.target1.html')); + $html2 = $this->_removeSpace(file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/markdown2html.target2.html')); + $html3 = $this->_removeSpace(file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/markdown2html.target3.html')); + $html4 = $this->_removeSpace(file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/markdown2html.target4.html')); + + $this->assertEquals($html1, $this->_removeSpace(Rhymix\Framework\Formatter::markdown2html($markdown))); + $this->assertEquals($html2, $this->_removeSpace(Rhymix\Framework\Formatter::markdown2html($markdown, Rhymix\Framework\Formatter::MD_NEWLINE_AS_BR))); + $this->assertEquals($html3, $this->_removeSpace(Rhymix\Framework\Formatter::markdown2html($markdown, Rhymix\Framework\Formatter::MD_NEWLINE_AS_BR | Rhymix\Framework\Formatter::MD_ENABLE_EXTRA))); + $this->assertEquals($html4, $this->_removeSpace(Rhymix\Framework\Formatter::markdown2html($markdown, Rhymix\Framework\Formatter::MD_ENABLE_EXTRA))); } - + public function testMarkdownExtra() { $markdown = file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/markdownextra.source.md'); - $html1 = file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/markdownextra.target1.html'); - $html2 = file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/markdownextra.target2.html'); - - $this->assertEquals($html1, Rhymix\Framework\Formatter::markdown2html($markdown)); - $this->assertEquals($html2, Rhymix\Framework\Formatter::markdown2html($markdown, Rhymix\Framework\Formatter::MD_ENABLE_EXTRA)); + $html1 = $this->_removeSpace(file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/markdownextra.target1.html')); + $html2 = $this->_removeSpace(file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/markdownextra.target2.html')); + + $this->assertEquals($html1, $this->_removeSpace(Rhymix\Framework\Formatter::markdown2html($markdown))); + $this->assertEquals($html2, $this->_removeSpace(Rhymix\Framework\Formatter::markdown2html($markdown, Rhymix\Framework\Formatter::MD_ENABLE_EXTRA))); } - + public function testHTML2Markdown() { $html = file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/html2markdown.source.html'); $markdown = file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/html2markdown.target.md'); - + $this->assertEquals($markdown, Rhymix\Framework\Formatter::html2markdown($html)); } - + public function testBBCode() { $bbcode = file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/bbcode.source.txt'); $html = file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/bbcode.target.html'); - + $this->assertEquals($html, Rhymix\Framework\Formatter::bbcode($bbcode)); } - + public function testApplySmartQuotes() { $before = file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/smartypants.source.html'); $after = file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/smartypants.target.html'); - + $this->assertEquals($after, Rhymix\Framework\Formatter::applySmartQuotes($before)); } - + public function testCompileLESS() { $sources = array( @@ -80,21 +85,21 @@ class FormatterTest extends \Codeception\TestCase\Test 'foo' => '#123456', 'bar' => '320px', ); - + $real_target1 = file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/less.target1.css'); $real_target2 = file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/less.target2.css'); $test_target1 = \RX_BASEDIR . 'tests/_output/less.target1.css'; $test_target2 = \RX_BASEDIR . 'tests/_output/less.target2.css'; - + $this->assertTrue(Rhymix\Framework\Formatter::compileLESS($sources, $test_target1, $variables)); $this->assertEquals($real_target1, file_get_contents($test_target1)); $this->assertTrue(Rhymix\Framework\Formatter::compileLESS($sources, $test_target2, $variables, true)); $this->assertEquals($real_target2, file_get_contents($test_target2)); - + unlink($test_target1); unlink($test_target2); } - + public function testCompileSCSS() { $sources = array( @@ -105,45 +110,45 @@ class FormatterTest extends \Codeception\TestCase\Test 'foo' => '#123456', 'bar' => '320px', ); - + $real_target1 = file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/scss.target1.css'); $real_target2 = file_get_contents(\RX_BASEDIR . 'tests/_data/formatter/scss.target2.css'); $test_target1 = \RX_BASEDIR . 'tests/_output/scss.target1.css'; $test_target2 = \RX_BASEDIR . 'tests/_output/scss.target2.css'; - + $this->assertTrue(Rhymix\Framework\Formatter::compileSCSS($sources, $test_target1, $variables)); $this->assertEquals($real_target1, file_get_contents($test_target1)); $this->assertTrue(Rhymix\Framework\Formatter::compileSCSS($sources, $test_target2, $variables, true)); $this->assertEquals($real_target2, file_get_contents($test_target2)); - + unlink($test_target1); unlink($test_target2); } - + public function testMinifyCSS() { $source = \RX_BASEDIR . 'tests/_data/formatter/minify.source.css'; $real_target = \RX_BASEDIR . 'tests/_data/formatter/minify.target.css'; $test_target = \RX_BASEDIR . 'tests/_output/minify.target.css'; - + $this->assertTrue(Rhymix\Framework\Formatter::minifyCSS($source, $test_target)); $this->assertEquals(file_get_contents($real_target), file_get_contents($test_target)); - + unlink($test_target); } - + public function testMinifyJS() { $source = \RX_BASEDIR . 'tests/_data/formatter/minify.source.js'; $real_target = \RX_BASEDIR . 'tests/_data/formatter/minify.target.js'; $test_target = \RX_BASEDIR . 'tests/_output/minify.target.js'; - + $this->assertTrue(Rhymix\Framework\Formatter::minifyJS($source, $test_target)); $this->assertEquals(file_get_contents($real_target), file_get_contents($test_target)); - + unlink($test_target); } - + public function testConcatCSS() { $source1 = \RX_BASEDIR . 'tests/_data/formatter/concat.source1.css'; @@ -151,14 +156,33 @@ class FormatterTest extends \Codeception\TestCase\Test $real_target1 = \RX_BASEDIR . 'tests/_data/formatter/concat.target1.css'; $real_target2 = \RX_BASEDIR . 'tests/_data/formatter/concat.target2.css'; $test_target = \RX_BASEDIR . 'tests/_output/concat.target.css'; - + $test_without_media_query = Rhymix\Framework\Formatter::concatCSS(array($source1, $source2), $test_target); $this->assertEquals(trim(file_get_contents($real_target1)), trim($test_without_media_query)); - + $test_with_media_query = Rhymix\Framework\Formatter::concatCSS(array(array($source1, 'screen and (max-width: 640px)'), $source2), $test_target); $this->assertEquals(trim(file_get_contents($real_target2)), trim($test_with_media_query)); } - + + public function testConcatMinifiedCSS() + { + $source1 = \RX_BASEDIR . 'tests/_data/formatter/concat.source1.css'; + $source2 = \RX_BASEDIR . 'tests/_data/formatter/concat.source2.css'; + $final_target = \RX_BASEDIR . 'tests/_data/formatter/concat.target3.css'; + $test_target1 = \RX_BASEDIR . 'tests/_output/concat+minify.target1.css'; + $test_target2 = \RX_BASEDIR . 'tests/_output/concat+minify.target2.css'; + $test_target3 = \RX_BASEDIR . 'tests/_output/concat+minify.target3.css'; + + $this->assertTrue(Rhymix\Framework\Formatter::minifyCSS($source1, $test_target1)); + $this->assertTrue(Rhymix\Framework\Formatter::minifyCSS($source2, $test_target2)); + + $concat_result = Rhymix\Framework\Formatter::concatCSS(array($test_target1, $test_target2), $test_target3); + $this->assertEquals(trim(file_get_contents($final_target)), trim($concat_result)); + + unlink($test_target1); + unlink($test_target2); + } + public function testConcatJS() { $source1 = \RX_BASEDIR . 'tests/_data/formatter/concat.source1.js'; @@ -166,40 +190,16 @@ class FormatterTest extends \Codeception\TestCase\Test $real_target1 = \RX_BASEDIR . 'tests/_data/formatter/concat.target1.js'; $real_target2 = \RX_BASEDIR . 'tests/_data/formatter/concat.target2.js'; $test_target = \RX_BASEDIR . 'tests/_output/concat.target.js'; - + $test_without_targetie = Rhymix\Framework\Formatter::concatJS(array($source1, $source2), $test_target); $this->assertEquals(trim(file_get_contents($real_target1)), trim($test_without_targetie)); - + $test_with_targetie = Rhymix\Framework\Formatter::concatJS(array($source1, array($source2, '(gte IE 6) & (lte IE 8)')), $test_target); $this->assertEquals(trim(file_get_contents($real_target2)), trim($test_with_targetie)); } - - public function testConvertIECondition() + + protected function _removeSpace($str) { - $this->assertEquals('window.navigator.userAgent.match(/MSIE\s/)', Rhymix\Framework\Formatter::convertIECondition('IE')); - $this->assertEquals('!window.navigator.userAgent.match(/MSIE\s/)', Rhymix\Framework\Formatter::convertIECondition('!IE')); - $this->assertEquals('!window.navigator.userAgent.match(/MSIE\s/)', Rhymix\Framework\Formatter::convertIECondition('!(IE)')); - $this->assertEquals('true && false', Rhymix\Framework\Formatter::convertIECondition('true&false')); - $this->assertEquals('false', Rhymix\Framework\Formatter::convertIECondition('gobbledygook')); - - $source = 'gt IE 7'; - $target = '(/MSIE (\d+)/.exec(window.navigator.userAgent) && /MSIE (\d+)/.exec(window.navigator.userAgent)[1] > 7)'; - $this->assertEquals($target, Rhymix\Framework\Formatter::convertIECondition($source)); - - $source = 'lte IE 8'; - $target = '(/MSIE (\d+)/.exec(window.navigator.userAgent) && /MSIE (\d+)/.exec(window.navigator.userAgent)[1] <= 8)'; - $this->assertEquals($target, Rhymix\Framework\Formatter::convertIECondition($source)); - - $source = '(gte IE 6) & (lt IE 8)'; - $target = '(/MSIE (\d+)/.exec(window.navigator.userAgent) && /MSIE (\d+)/.exec(window.navigator.userAgent)[1] >= 6) && (/MSIE (\d+)/.exec(window.navigator.userAgent) && /MSIE (\d+)/.exec(window.navigator.userAgent)[1] < 8)'; - $this->assertEquals($target, Rhymix\Framework\Formatter::convertIECondition($source)); - - $source = '!(gt IE 9)'; - $target = '!(/MSIE (\d+)/.exec(window.navigator.userAgent) && /MSIE (\d+)/.exec(window.navigator.userAgent)[1] > 9)'; - $this->assertEquals($target, Rhymix\Framework\Formatter::convertIECondition($source)); - - $source = '!lt IE 8|lt IE 6'; - $target = '!(/MSIE (\d+)/.exec(window.navigator.userAgent) && /MSIE (\d+)/.exec(window.navigator.userAgent)[1] < 8) || (/MSIE (\d+)/.exec(window.navigator.userAgent) && /MSIE (\d+)/.exec(window.navigator.userAgent)[1] < 6)'; - $this->assertEquals($target, Rhymix\Framework\Formatter::convertIECondition($source)); + return preg_replace('/\s+/', '', $str); } } diff --git a/tests/unit/framework/KoreaTest.php b/tests/unit/framework/KoreaTest.php index 958af741b..e6df721fa 100644 --- a/tests/unit/framework/KoreaTest.php +++ b/tests/unit/framework/KoreaTest.php @@ -1,6 +1,6 @@ assertEquals('010-6666-7777', Rhymix\Framework\Korea::formatPhoneNumber('82+1066667777')); $this->assertEquals('0303-456-7890', Rhymix\Framework\Korea::formatPhoneNumber('03034567890')); $this->assertEquals('0505-987-6543', Rhymix\Framework\Korea::formatPhoneNumber('050-5987-6543')); + $this->assertEquals('0303-4567-8900', Rhymix\Framework\Korea::formatPhoneNumber('030345678900')); + $this->assertEquals('0505-9876-5432', Rhymix\Framework\Korea::formatPhoneNumber('050-5987-65432')); $this->assertEquals('070-7432-1000', Rhymix\Framework\Korea::formatPhoneNumber('0707-432-1000')); } - + public function testIsValidPhoneNumber() { $this->assertTrue(Rhymix\Framework\Korea::isValidPhoneNumber('1588-0000')); @@ -26,14 +28,20 @@ class KoreaTest extends \Codeception\TestCase\Test $this->assertTrue(Rhymix\Framework\Korea::isValidPhoneNumber('053-4444-5555')); $this->assertTrue(Rhymix\Framework\Korea::isValidPhoneNumber('011-444-5555')); $this->assertTrue(Rhymix\Framework\Korea::isValidPhoneNumber('010-4444-5555')); + $this->assertTrue(Rhymix\Framework\Korea::isValidPhoneNumber('0303-4444-5555')); + $this->assertTrue(Rhymix\Framework\Korea::isValidPhoneNumber('0505-4444-5555')); + $this->assertTrue(Rhymix\Framework\Korea::isValidPhoneNumber('0507-1234-5678')); + $this->assertTrue(Rhymix\Framework\Korea::isValidPhoneNumber('0506-123-4567')); $this->assertFalse(Rhymix\Framework\Korea::isValidPhoneNumber('010-4444-55555')); $this->assertFalse(Rhymix\Framework\Korea::isValidPhoneNumber('010-1234-5678')); $this->assertFalse(Rhymix\Framework\Korea::isValidPhoneNumber('02-123-4567')); $this->assertFalse(Rhymix\Framework\Korea::isValidPhoneNumber('02-123456')); $this->assertFalse(Rhymix\Framework\Korea::isValidPhoneNumber('03-456-7890')); $this->assertFalse(Rhymix\Framework\Korea::isValidPhoneNumber('090-9876-5432')); + $this->assertFalse(Rhymix\Framework\Korea::isValidPhoneNumber('0303-0000-5432')); + $this->assertFalse(Rhymix\Framework\Korea::isValidPhoneNumber('0505-9876-543210')); } - + public function testIsValidMobilePhoneNumber() { $this->assertTrue(Rhymix\Framework\Korea::isValidMobilePhoneNumber('011-345-6789')); @@ -45,28 +53,28 @@ class KoreaTest extends \Codeception\TestCase\Test $this->assertFalse(Rhymix\Framework\Korea::isValidMobilePhoneNumber('063-9876-5432')); $this->assertFalse(Rhymix\Framework\Korea::isValidMobilePhoneNumber('070-7654-3210')); } - + public function testIsValidJuminNumber() { // These numbers are fake. $this->assertTrue(Rhymix\Framework\Korea::isValidJuminNumber('123456-3456787')); $this->assertFalse(Rhymix\Framework\Korea::isValidJuminNumber('123456-3456788')); } - + public function testIsValidCorporationNumber() { // These numbers are fake. $this->assertTrue(Rhymix\Framework\Korea::isValidCorporationNumber('123456-0123453')); $this->assertFalse(Rhymix\Framework\Korea::isValidCorporationNumber('123456-0123454')); } - + public function testIsValidBusinessNumber() { // These numbers are fake. $this->assertTrue(Rhymix\Framework\Korea::isValidBusinessNumber('123-45-67891')); $this->assertFalse(Rhymix\Framework\Korea::isValidBusinessNumber('123-45-67892')); } - + public function testIsKoreanIP() { // Private IP ranges. @@ -74,32 +82,32 @@ class KoreaTest extends \Codeception\TestCase\Test $this->assertTrue(Rhymix\Framework\Korea::isKoreanIP('127.0.123.45')); $this->assertTrue(Rhymix\Framework\Korea::isKoreanIP('192.168.10.1')); $this->assertTrue(Rhymix\Framework\Korea::isKoreanIP('::1')); - + // Korean IP ranges. $this->assertTrue(Rhymix\Framework\Korea::isKoreanIP('115.71.233.0')); $this->assertTrue(Rhymix\Framework\Korea::isKoreanIP('114.207.12.3')); $this->assertTrue(Rhymix\Framework\Korea::isKoreanIP('2001:0320::1')); $this->assertTrue(Rhymix\Framework\Korea::isKoreanIP('2407:B800::F')); - + // Foreign IP ranges. $this->assertFalse(Rhymix\Framework\Korea::isKoreanIP('216.58.197.0')); $this->assertFalse(Rhymix\Framework\Korea::isKoreanIP('170.14.168.0')); $this->assertFalse(Rhymix\Framework\Korea::isKoreanIP('2001:41d0:8:e8ad::1')); $this->assertFalse(Rhymix\Framework\Korea::isKoreanIP('2404:6800:4005:802::200e')); } - + public function testIsKoreanEmailAddress() { // Test Korean portals. $this->assertTrue(Rhymix\Framework\Korea::isKoreanEmailAddress('test@naver.com')); $this->assertTrue(Rhymix\Framework\Korea::isKoreanEmailAddress('test@hanmail.net')); $this->assertTrue(Rhymix\Framework\Korea::isKoreanEmailAddress('test@worksmobile.com')); - + // Test foreign portals. $this->assertFalse(Rhymix\Framework\Korea::isKoreanEmailAddress('test@gmail.com')); $this->assertFalse(Rhymix\Framework\Korea::isKoreanEmailAddress('test@hotmail.com')); $this->assertFalse(Rhymix\Framework\Korea::isKoreanEmailAddress('test@yahoo.com')); - + // Test third-party MX services. $this->assertTrue(Rhymix\Framework\Korea::isKoreanEmailAddress('test@woorimail.com')); $this->assertFalse(Rhymix\Framework\Korea::isKoreanEmailAddress('test@rhymix.org')); diff --git a/tests/unit/framework/LangTest.php b/tests/unit/framework/LangTest.php index 0fc64b12f..4dfb40dcc 100644 --- a/tests/unit/framework/LangTest.php +++ b/tests/unit/framework/LangTest.php @@ -1,6 +1,6 @@ assertTrue($ko instanceof Rhymix\Framework\Lang); $this->assertTrue($en instanceof Rhymix\Framework\Lang); $this->assertFalse($ko === $en); - + // Test backward compatible language code for Japanese. $ja = Rhymix\Framework\Lang::getInstance('ja'); $jp = Rhymix\Framework\Lang::getInstance('jp'); $this->assertTrue($ja === $jp); - + // Test loading new plugins. $this->assertNotEquals('ヘルプ', $ja->help); $ja->loadPlugin('common'); $this->assertEquals('ヘルプ', $ja->help); - + // Test simple translations with namespacing. $this->assertEquals('도움말', $ko->get('common.help')); $this->assertEquals('Help', $en->get('common.help')); - + // Test simple translations without namespacing. $this->assertEquals('도움말', $ko->help); $this->assertEquals('Help', $en->help); - + // Test complex translations with multidimensional arrays. $this->assertEquals('%d분 전', $ko->get('common.time_gap.min')); $this->assertEquals('10분 전', $ko->get('common.time_gap.min', 10)); $this->assertTrue($ko->get('common.time_gap') instanceof \ArrayObject); $this->assertEquals('%d분 전', $ko->get('common.time_gap')->min); - + // Test nonexistent keys. $this->assertEquals('common.nonexistent', $ko->get('common.nonexistent')); $this->assertEquals('common.nonexistent', $ko->get('common.nonexistent', 'foo', 'bar')); $this->assertEquals('admin.help', $ko->get('admin.help')); $this->assertEquals('admin.help', $en->get('admin.help')); - + // Test setting new keys with and without namespacing. $ko->set('foo', 'FOO!'); $this->assertEquals('FOO!', $ko->get('foo')); $ko->set('common.foobar', 'FOOBAR!'); $this->assertEquals('FOOBAR!', $ko->get('common.foobar')); $this->assertEquals('FOOBAR!', $ko->get('foobar')); - + // Test setting new keys with multidimensional arrays. $ko->set('common.time_gap.foobar', 'FOOBAR!'); $this->assertEquals('FOOBAR!', $ko->get('common.time_gap.foobar')); $ko->set('common.foobar.baz', 'BAZ!'); $this->assertNotEquals('BAZ!', $ko->get('common.foobar.baz')); - + // Test fallback to English. $en->only_in_english = 'Hello world'; $this->assertEquals('Hello world', $ko->only_in_english); $this->assertEquals('Hello world', $en->only_in_english); $this->assertEquals('Hello world', $ja->only_in_english); - + // Test string interpolation. $ko->foobartestlang = '%s님 안녕하세요?'; $this->assertEquals('Travis님 안녕하세요?', $ko->foobartestlang('Travis')); $en->foobartestlang = 'Hello, %s!'; $this->assertEquals('Hello, Travis!', $en->get('foobartestlang', 'Travis')); } - + public function testLangCompat() { Context::loadLang('./modules/member/lang'); diff --git a/tests/unit/framework/MIMETest.php b/tests/unit/framework/MIMETest.php index f62324ce7..da8eb374b 100644 --- a/tests/unit/framework/MIMETest.php +++ b/tests/unit/framework/MIMETest.php @@ -1,22 +1,26 @@ assertEquals('audio/ogg', Rhymix\Framework\MIME::getTypeByExtension('ogg')); $this->assertEquals('image/gif', Rhymix\Framework\MIME::getTypeByExtension('gif')); $this->assertEquals('text/html', Rhymix\Framework\MIME::getTypeByExtension('htm')); - + $this->assertEquals('application/msword', Rhymix\Framework\MIME::getTypeByFilename('attachment.doc')); $this->assertEquals('application/pdf', Rhymix\Framework\MIME::getTypeByFilename('라이믹스.pdf')); $this->assertEquals('application/postscript', Rhymix\Framework\MIME::getTypeByFilename('MyGraphics.v2.eps')); $this->assertEquals('application/vnd.ms-excel', Mail::returnMIMEType('MySpreadsheet.xls')); $this->assertEquals('application/octet-stream', Mail::returnMIMEType('Untitled File')); - + $this->assertEquals('odt', Rhymix\Framework\MIME::getExtensionByType('application/vnd.oasis.opendocument.text')); $this->assertEquals('jpg', Rhymix\Framework\MIME::getExtensionByType('image/jpeg')); - $this->assertEquals('mpeg', Rhymix\Framework\MIME::getExtensionByType('video/mpeg')); - $this->assertFalse(Rhymix\Framework\MIME::getExtensionByType('application/octet-stream')); + $this->assertEquals('mpg', Rhymix\Framework\MIME::getExtensionByType('video/mpeg')); + $this->assertEquals('ogg', Rhymix\Framework\MIME::getExtensionByType('audio/ogg')); + $this->assertEquals('ogv', Rhymix\Framework\MIME::getExtensionByType('video/ogg')); + $this->assertEquals('mp4', Rhymix\Framework\MIME::getExtensionByType('audio/mp4')); + $this->assertEquals('mp4', Rhymix\Framework\MIME::getExtensionByType('video/mp4')); + $this->assertNull(Rhymix\Framework\MIME::getExtensionByType('application/octet-stream')); } } diff --git a/tests/unit/framework/MailTest.php b/tests/unit/framework/MailTest.php index 801b384af..4764620fb 100644 --- a/tests/unit/framework/MailTest.php +++ b/tests/unit/framework/MailTest.php @@ -1,17 +1,30 @@ _prev_queue_config = config('queue'); + config('queue.enabled', false); + } + + public function _after() + { + config('queue', $this->_prev_queue_config); + } + public function testGetSetDefaultDriver() { $driver = Rhymix\Framework\Mail::getDefaultDriver(); $this->assertInstanceOf('\\Rhymix\\Framework\\Drivers\\MailInterface', $driver); - + $driver = Rhymix\Framework\Drivers\Mail\Dummy::getInstance(array()); Rhymix\Framework\Mail::setDefaultDriver($driver); $this->assertEquals($driver, Rhymix\Framework\Mail::getDefaultDriver()); } - + public function testGetSupportedDrivers() { $drivers = Rhymix\Framework\Mail::getSupportedDrivers(); @@ -22,98 +35,108 @@ class MailTest extends \Codeception\TestCase\Test $this->assertEquals(array('api_token'), $drivers['sparkpost']['required']); $this->assertNotEmpty($drivers['woorimail']['spf_hint']); } - + public function testSenderAndRecipients() { $mail = new Rhymix\Framework\Mail; - + $this->assertNull($mail->getFrom()); $mail->setFrom('devops@rhymix.org', 'Rhymix Developers'); $this->assertEquals('Rhymix Developers ', $mail->getFrom()); - + $this->assertEquals(null, $mail->message->getTo()); $mail->addTo('whoever@rhymix.org', 'Name'); $this->assertEquals(array('whoever@rhymix.org' => 'Name'), $mail->message->getTo()); - + $this->assertEquals(null, $mail->message->getCc()); $mail->addCc('whatever@rhymix.org', 'Nick'); $this->assertEquals(array('whatever@rhymix.org' => 'Nick'), $mail->message->getCc()); - + $this->assertEquals(null, $mail->message->getBcc()); $mail->addBcc('wherever@rhymix.org', 'User'); $this->assertEquals(array('wherever@rhymix.org' => 'User'), $mail->message->getBcc()); - + $this->assertEquals(null, $mail->message->getReplyTo()); $mail->setReplyTo('replyto@rhymix.org'); $this->assertEquals(array('replyto@rhymix.org' => ''), $mail->message->getReplyTo()); - + $recipients = $mail->getRecipients(); $this->assertEquals(3, count($recipients)); $this->assertContains('Name ', $recipients); $this->assertContains('Nick ', $recipients); $this->assertContains('User ', $recipients); } - + public function testMiscHeaders() { $mail = new Rhymix\Framework\Mail; - + $mail->setReturnPath('envelope@rhymix.org'); $this->assertEquals('envelope@rhymix.org', $mail->message->getReturnPath()); - + $mail->setMessageID('some.random.string@rhymix.org'); $this->assertEquals('some.random.string@rhymix.org', $mail->message->getId()); - + $mail->setInReplyTo(''); $this->assertEquals('', $mail->message->getHeaders()->get('In-Reply-To')->getValue()); - + $mail->setReferences(', , '); $this->assertEquals(', , ', $mail->message->getHeaders()->get('References')->getValue()); } - + public function testMailSubject() { $mail = new Rhymix\Framework\Mail; - + $mail->setSubject('Foobar!'); $this->assertEquals('Foobar!', $mail->getSubject()); $mail->setTitle('Foobarbazz?'); $this->assertEquals('Foobarbazz?', $mail->getTitle()); } - + public function testMailBody() { + $baseurl = '/' . basename(dirname(dirname(dirname(__DIR__)))) . '/'; $mail = new Rhymix\Framework\Mail; - + $mail->setBody('

                Hello world!

                ', 'text/html'); $this->assertEquals('

                Hello world!

                ', $mail->getBody()); $this->assertEquals('text/html', $mail->getContentType()); - + $mail->setContent('

                Hello world! Foobar?

                ', 'text/plain'); $this->assertEquals('

                Hello world! Foobar?

                ', $mail->getBody()); $this->assertEquals('text/plain', $mail->getContentType()); - + $mail->setBody('

                Hello foobar...

                ', 'invalid value'); $this->assertEquals('

                Hello foobar...

                ', $mail->getContent()); $this->assertEquals('text/plain', $mail->getContentType()); - + + $mail->setBody('

                TEST

                ', 'text/html'); + $this->assertEquals('

                TEST

                ', $mail->getBody()); + $mail->setBody('

                TEST

                ', 'text/html'); + $this->assertEquals('

                TEST

                ', $mail->getBody()); + $mail->setBody('

                TEST

                ', 'text/html'); + $this->assertEquals('

                TEST

                ', $mail->getBody()); + $mail->setBody('

                TEST

                ', 'text/plain'); + $this->assertEquals('

                TEST

                ', $mail->getBody()); + $mail->setContentType('html'); $this->assertEquals('text/html', $mail->getContentType()); $mail->setContentType('invalid'); $this->assertEquals('text/plain', $mail->getContentType()); } - + public function testMailAttach() { $mail = new Rhymix\Framework\Mail; - + $success = $mail->attach(\RX_BASEDIR . 'tests/_data/formatter/minify.source.css'); $this->assertTrue($success); $success = $mail->attach(\RX_BASEDIR . 'tests/_data/formatter/minify.target.css', 'target.css'); $this->assertTrue($success); $success = $mail->attach(\RX_BASEDIR . 'tests/_data/nonexistent.file.jpg'); $this->assertFalse($success); - + $attachments = $mail->getAttachments(); $this->assertEquals(2, count($attachments)); $this->assertEquals('attach', $attachments[0]->type); @@ -121,51 +144,51 @@ class MailTest extends \Codeception\TestCase\Test $this->assertEquals('minify.source.css', $attachments[0]->display_filename); $this->assertEquals('target.css', $attachments[1]->display_filename); } - + public function testMailEmbed() { $mail = new Rhymix\Framework\Mail; - + $cid = $mail->embed(\RX_BASEDIR . 'tests/_data/formatter/minify.source.css', 'thisismyrandomcid@rhymix.org'); $this->assertEquals('cid:thisismyrandomcid@rhymix.org', $cid); - + $cid = $mail->embed(\RX_BASEDIR . 'tests/_data/formatter/minify.target.css'); $this->assertRegexp('/^cid:[0-9a-z]+@[^@]+$/i', $cid); } - + public function testMailClassCompatibility() { \Mail::useGmailAccount('devops@rhymix.org', 'password'); $this->assertInstanceOf('\\Rhymix\\Framework\\Drivers\\Mail\\SMTP', \Mail::getDefaultDriver()); - + \Mail::useSMTP(null, 'rhymix.org', 'devops@rhymix.org', 'password', 'tls', 587); $this->assertInstanceOf('\\Rhymix\\Framework\\Drivers\\Mail\\SMTP', \Mail::getDefaultDriver()); - + $mail = new \Mail; - + $mail->setSender('Rhymix', 'devops@rhymix.org'); $this->assertEquals('Rhymix ', $mail->getSender()); - + $mail->setReceiptor('Recipient', 'whoever@rhymix.org'); $this->assertEquals('Recipient ', $mail->getReceiptor()); $mail->setReceiptor('Another Recipient', 'whatever@rhymix.org'); $this->assertEquals('Another Recipient ', $mail->getReceiptor()); $this->assertEquals(1, count($mail->message->getTo())); $this->assertEquals(null, $mail->message->getCc()); - + $mail->setBcc('bcc-1@rhymix.org'); $mail->setBcc('bcc-2@rhymix.org'); $this->assertEquals(array('bcc-2@rhymix.org' => ''), $mail->message->getBcc()); - + $content = '

                Hello world!

                This is a long message to test chunk splitting.

                This feature is only available using the legacy Mail class.

                '; $mail->setBody($content); $this->assertEquals(chunk_split(base64_encode($content)), $mail->getHTMLContent()); $this->assertEquals(chunk_split(base64_encode(htmlspecialchars($content))), $mail->getPlainContent()); - + $mail->addAttachment(\RX_BASEDIR . 'tests/_data/formatter/minify.target.css', 'target.css'); $cid = $mail->addCidAttachment(\RX_BASEDIR . 'tests/_data/formatter/minify.target.css', 'thisismyrandomcid@rhymix.org'); $this->assertEquals('cid:thisismyrandomcid@rhymix.org', $cid); - + $attachments = $mail->getAttachments(); $this->assertEquals(2, count($attachments)); $this->assertEquals('attach', $attachments[0]->type); @@ -173,12 +196,14 @@ class MailTest extends \Codeception\TestCase\Test $this->assertEquals('embed', $attachments[1]->type); $this->assertEquals('cid:thisismyrandomcid@rhymix.org', $attachments[1]->cid); } - + public function testEmailAddressValidator() { $this->assertEquals('devops@rhymix.org', Mail::isVaildMailAddress('devops@rhymix.org')); $this->assertEquals('some+thing@gmail.com', Mail::isVaildMailAddress('some+thing@gmail.com')); - $this->assertEquals('weird@localhost', Mail::isVaildMailAddress('weird@localhost')); + $this->assertEquals('', Mail::isVaildMailAddress('weird@localhost')); + $this->assertEquals('weird@localhost', Mail::isVaildMailAddress('weird@localhost', false)); $this->assertEquals('', Mail::isVaildMailAddress('invalid@')); + $this->assertEquals('', Mail::isVaildMailAddress('invalid@', false)); } } diff --git a/tests/unit/framework/PaginationTest.php b/tests/unit/framework/PaginationTest.php index 057c0b791..c4d518d59 100644 --- a/tests/unit/framework/PaginationTest.php +++ b/tests/unit/framework/PaginationTest.php @@ -1,6 +1,6 @@ assertEquals(1, Rhymix\Framework\Pagination::countPages(20, 20)); $this->assertEquals(2, Rhymix\Framework\Pagination::countPages(21, 20)); } - + public function testCreateLinks() { $links = Rhymix\Framework\Pagination::createLinks('index.php?page=', 27, 3); - $this->assertContains('