diff --git a/.tx/config b/.tx/config
index eb73e33399..3df5e9b1b8 100644
--- a/.tx/config
+++ b/.tx/config
@@ -19,6 +19,7 @@ trans.he = public/language/he/category.json
trans.hu = public/language/hu/category.json
trans.it = public/language/it/category.json
trans.ja = public/language/ja/category.json
+trans.ko = public/language/ko/category.json
trans.lt = public/language/lt/category.json
trans.ms = public/language/ms/category.json
trans.nb = public/language/nb/category.json
@@ -54,6 +55,7 @@ trans.he = public/language/he/login.json
trans.hu = public/language/hu/login.json
trans.it = public/language/it/login.json
trans.ja = public/language/ja/login.json
+trans.ko = public/language/ko/login.json
trans.lt = public/language/lt/login.json
trans.ms = public/language/ms/login.json
trans.nb = public/language/nb/login.json
@@ -88,6 +90,7 @@ trans.he = public/language/he/recent.json
trans.hu = public/language/hu/recent.json
trans.it = public/language/it/recent.json
trans.ja = public/language/ja/recent.json
+trans.ko = public/language/ko/recent.json
trans.lt = public/language/lt/recent.json
trans.ms = public/language/ms/recent.json
trans.nb = public/language/nb/recent.json
@@ -122,6 +125,7 @@ trans.he = public/language/he/unread.json
trans.hu = public/language/hu/unread.json
trans.it = public/language/it/unread.json
trans.ja = public/language/ja/unread.json
+trans.ko = public/language/ko/unread.json
trans.lt = public/language/lt/unread.json
trans.ms = public/language/ms/unread.json
trans.nb = public/language/nb/unread.json
@@ -156,6 +160,7 @@ trans.he = public/language/he/modules.json
trans.hu = public/language/hu/modules.json
trans.it = public/language/it/modules.json
trans.ja = public/language/ja/modules.json
+trans.ko = public/language/ko/modules.json
trans.lt = public/language/lt/modules.json
trans.ms = public/language/ms/modules.json
trans.nb = public/language/nb/modules.json
@@ -190,6 +195,7 @@ trans.he = public/language/he/register.json
trans.hu = public/language/hu/register.json
trans.it = public/language/it/register.json
trans.ja = public/language/ja/register.json
+trans.ko = public/language/ko/register.json
trans.lt = public/language/lt/register.json
trans.ms = public/language/ms/register.json
trans.nb = public/language/nb/register.json
@@ -224,6 +230,7 @@ trans.he = public/language/he/user.json
trans.hu = public/language/hu/user.json
trans.it = public/language/it/user.json
trans.ja = public/language/ja/user.json
+trans.ko = public/language/ko/user.json
trans.lt = public/language/lt/user.json
trans.ms = public/language/ms/user.json
trans.nb = public/language/nb/user.json
@@ -258,6 +265,7 @@ trans.he = public/language/he/global.json
trans.hu = public/language/hu/global.json
trans.it = public/language/it/global.json
trans.ja = public/language/ja/global.json
+trans.ko = public/language/ko/global.json
trans.lt = public/language/lt/global.json
trans.ms = public/language/ms/global.json
trans.nb = public/language/nb/global.json
@@ -292,6 +300,7 @@ trans.he = public/language/he/notifications.json
trans.hu = public/language/hu/notifications.json
trans.it = public/language/it/notifications.json
trans.ja = public/language/ja/notifications.json
+trans.ko = public/language/ko/notifications.json
trans.lt = public/language/lt/notifications.json
trans.ms = public/language/ms/notifications.json
trans.nb = public/language/nb/notifications.json
@@ -326,6 +335,7 @@ trans.he = public/language/he/reset_password.json
trans.hu = public/language/hu/reset_password.json
trans.it = public/language/it/reset_password.json
trans.ja = public/language/ja/reset_password.json
+trans.ko = public/language/ko/reset_password.json
trans.lt = public/language/lt/reset_password.json
trans.ms = public/language/ms/reset_password.json
trans.nb = public/language/nb/reset_password.json
@@ -360,6 +370,7 @@ trans.he = public/language/he/users.json
trans.hu = public/language/hu/users.json
trans.it = public/language/it/users.json
trans.ja = public/language/ja/users.json
+trans.ko = public/language/ko/users.json
trans.lt = public/language/lt/users.json
trans.ms = public/language/ms/users.json
trans.nb = public/language/nb/users.json
@@ -394,6 +405,7 @@ trans.he = public/language/he/language.json
trans.hu = public/language/hu/language.json
trans.it = public/language/it/language.json
trans.ja = public/language/ja/language.json
+trans.ko = public/language/ko/language.json
trans.lt = public/language/lt/language.json
trans.ms = public/language/ms/language.json
trans.nb = public/language/nb/language.json
@@ -428,6 +440,7 @@ trans.he = public/language/he/pages.json
trans.hu = public/language/hu/pages.json
trans.it = public/language/it/pages.json
trans.ja = public/language/ja/pages.json
+trans.ko = public/language/ko/pages.json
trans.lt = public/language/lt/pages.json
trans.ms = public/language/ms/pages.json
trans.nb = public/language/nb/pages.json
@@ -462,6 +475,7 @@ trans.he = public/language/he/topic.json
trans.hu = public/language/hu/topic.json
trans.it = public/language/it/topic.json
trans.ja = public/language/ja/topic.json
+trans.ko = public/language/ko/topic.json
trans.lt = public/language/lt/topic.json
trans.ms = public/language/ms/topic.json
trans.nb = public/language/nb/topic.json
@@ -496,6 +510,7 @@ trans.he = public/language/he/success.json
trans.hu = public/language/hu/success.json
trans.it = public/language/it/success.json
trans.ja = public/language/ja/success.json
+trans.ko = public/language/ko/success.json
trans.lt = public/language/lt/success.json
trans.ms = public/language/ms/success.json
trans.nb = public/language/nb/success.json
@@ -530,6 +545,7 @@ trans.he = public/language/he/error.json
trans.hu = public/language/hu/error.json
trans.it = public/language/it/error.json
trans.ja = public/language/ja/error.json
+trans.ko = public/language/ko/error.json
trans.lt = public/language/lt/error.json
trans.ms = public/language/ms/error.json
trans.nb = public/language/nb/error.json
diff --git a/docs/upgrading/index.rst b/docs/upgrading/index.rst
index 00b5d36d50..be5ce29edf 100644
--- a/docs/upgrading/index.rst
+++ b/docs/upgrading/index.rst
@@ -108,16 +108,15 @@ Navigate to your NodeBB: ``$ cd /path/to/nodebb``.
If you are upgrading from a lower branch to a higher branch, switch branches as necessary. ***Make sure you are completely up-to-date on your current branch!***.
-For example, if upgrading from ``v0.1.4`` to ``v0.2.0``:
+For example, if upgrading from ``v0.3.2`` to ``v0.4.3``:
.. code:: bash
- $ git fetch # Grab the latest code from your current branch
- $ git checkout v0.2.x
+ $ git fetch # Grab the latest code from the NodeBB Repository
+ $ git checkout v0.4.x # Type this as-is! Not v0.4.2 or v0.4.3, but "v0.4.x"!
+ $ git merge origin/v0.4.x
-If not upgrading between branches, skip the commands above.
-
-Then, grab the latest code:
+If not upgrading between branches, just run the following command:
.. code:: bash
@@ -145,4 +144,4 @@ This script will install any missing dependencies, upgrade any plugins or themes
6. Start up NodeBB & Test!
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-You should now be running the latest version of NodeBB.
\ No newline at end of file
+You should now be running the latest version of NodeBB.
diff --git a/package.json b/package.json
index cd1fa88b4c..8226b165c2 100644
--- a/package.json
+++ b/package.json
@@ -47,8 +47,8 @@
"nodebb-plugin-mentions": "~0.4.0",
"nodebb-plugin-markdown": "~0.4.1",
"nodebb-widget-essentials": "~0.0.21",
- "nodebb-theme-vanilla": "<0.0.21",
- "nodebb-theme-lavender": "<0.0.27",
+ "nodebb-theme-vanilla": "~0.0.21",
+ "nodebb-theme-lavender": "~0.0.26",
"nodebb-plugin-soundpack-default": "~0.1.1",
"nodebb-plugin-dbsearch": "0.0.9"
},
diff --git a/public/language/ko/category.json b/public/language/ko/category.json
new file mode 100644
index 0000000000..bbd85848a8
--- /dev/null
+++ b/public/language/ko/category.json
@@ -0,0 +1,7 @@
+{
+ "new_topic_button": "새 주제 생성",
+ "no_topics": "이 카테고리에는 생성된 주제가 없습니다.
먼저 주제를 생성해 보세요.",
+ "browsing": "이 주제를 읽고 있는 사용자",
+ "no_replies": "답글이 없습니다.",
+ "share_this_category": "이 카테고리를 공유"
+}
diff --git a/public/language/ko/error.json b/public/language/ko/error.json
new file mode 100644
index 0000000000..12a15dc868
--- /dev/null
+++ b/public/language/ko/error.json
@@ -0,0 +1,50 @@
+{
+ "invalid-data": "올바르지 않은 정보입니다.",
+ "not-logged-in": "로그인하지 않았습니다.",
+ "account-locked": "임시로 잠긴 계정입니다.",
+ "invalid-cid": "올바르지 않은 카테고리 ID입니다.",
+ "invalid-tid": "올바르지 않은 주제 ID입니다.",
+ "invalid-pid": "올바르지 않은 게시물 ID입니다.",
+ "invalid-uid": "올바르지 않은 사용자 ID입니다.",
+ "invalid-username": "올바르지 않은 사용자 이름입니다.",
+ "invalid-email": "올바르지 않은 이메일입니다.",
+ "invalid-title": "올바르지 않은 제목입니다.",
+ "invalid-user-data": "올바르지 않은 사용자 정보입니다.",
+ "invalid-password": "올바르지 않은 비밀번호입니다.",
+ "invalid-pagination-value": "올바르지 않은 페이지입니다.",
+ "username-taken": "이미 사용 중인 사용자 이름입니다.",
+ "email-taken": "이미 사용 중인 이메일입니다.",
+ "user-banned": "차단된 사용자입니다.",
+ "no-category": "존재하지 않는 카테고리입니다.",
+ "no-topic": "존재하지 않는 주제입니다.",
+ "no-post": "존재하지 않는 게시물입니다.",
+ "no-group": "존재하지 않는 그룹입니다.",
+ "no-user": "존재하지 않는 사용자입니다.",
+ "no-teaser": "존재하지 않는 미리보기입니다.",
+ "no-privileges": "이 작업을 할 수 있는 권한이 없습니다.",
+ "category-disabled": "비활성화된 카테고리입니다.",
+ "topic-locked": "잠긴 주제입니다.",
+ "still-uploading": "업로드가 끝날 때까지 기다려 주세요.",
+ "content-too-short": "게시물의 내용은 최소 %1자로 제한됩니다.",
+ "title-too-short": "제목은 최소 %1자로 제한됩니다.",
+ "title-too-long": "제목은 최대 %1자로 제한됩니다.",
+ "too-many-posts": "새 게시물 작성은 %1초 간격으로 제한됩니다",
+ "file-too-big": "파일의 크기는 최대 %1KB로 제한됩니다.",
+ "cant-vote-self-post": "자신의 게시물은 추천할 수 없습니다.",
+ "already-favourited": "이미 이 게시물을 좋아하는 중입니다.",
+ "already-unfavourited": "이미 이 게시물을 좋아하고 있지 않습니다.",
+ "cant-ban-other-admins": "다른 관리자를 차단할 수 없습니다.",
+ "invalid-image-type": "올바르지 않은 이미지입니다.",
+ "group-name-too-short": "그룹 이름이 너무 짧습니다.",
+ "group-already-exists": "이미 존재하는 그룹입니다.",
+ "group-name-change-not-allowed": "금지된 그룹 이름입니다.",
+ "post-already-deleted": "이미 삭제된 게시물입니다.",
+ "post-already-restored": "이미 복원된 게시물입니다.",
+ "topic-already-deleted": "이미 삭제된 주제입니다.",
+ "topic-already-restored": "이미 복원된 주제입니다.",
+ "topic-thumbnails-are-disabled": "주제 섬네일이 이미 해제되었습니다.",
+ "invalid-file": "올바르지 않은 파일입니다.",
+ "uploads-are-disabled": "업로드는 비활성화되어 있습니다.",
+ "signature-too-long": "서명은 최대 %1자로 제한됩니다.",
+ "cant-chat-with-yourself": "자신과는 채팅할 수 없습니다."
+}
\ No newline at end of file
diff --git a/public/language/ko/footer.json b/public/language/ko/footer.json
new file mode 100644
index 0000000000..ef75c4211d
--- /dev/null
+++ b/public/language/ko/footer.json
@@ -0,0 +1,7 @@
+{
+ "stats.online": "온라인",
+ "stats.users": "사용자",
+ "stats.topics": "주제",
+ "stats.posts": "게시물",
+ "success": "성공"
+}
diff --git a/public/language/ko/global.json b/public/language/ko/global.json
new file mode 100644
index 0000000000..6b5bb9a2b2
--- /dev/null
+++ b/public/language/ko/global.json
@@ -0,0 +1,71 @@
+{
+ "home": "홈",
+ "search": "검색",
+ "buttons.close": "닫기",
+ "403.title": "접근이 거부되었습니다.",
+ "403.message": "접근 권한이 없는 페이지에 접근하였습니다. 로그인되어 있는지 확인해 주세요.",
+ "404.title": "페이지를 찾을 수 없습니다.",
+ "404.message": "존재하지 않는 페이지에 접근하였습니다. 홈으로 돌아갈 수 있습니다.",
+ "500.title": "내부 오류가 발생했습니다.",
+ "500.message": "알 수 없는 오류가 발생했습니다.",
+ "register": "회원가입",
+ "login": "로그인",
+ "please_log_in": "로그인해 주세요.",
+ "logout": "로그아웃",
+ "posting_restriction_info": "게시물 작성은 현재 회원에게만 제한되고 있습니다. 여기를 누르면 로그인 페이지로 이동합니다.",
+ "welcome_back": "환영합니다 : ",
+ "you_have_successfully_logged_in": "성공적으로 로그인했습니다.",
+ "save_changes": "저장",
+ "close": "닫기",
+ "pagination": "페이지",
+ "pagination.out_of": "%1 out of %2",
+ "header.admin": "관리자",
+ "header.recent": "최근 주제",
+ "header.unread": "읽지 않은 주제",
+ "header.popular": "인기 주제",
+ "header.users": "사용자",
+ "header.chats": "채팅",
+ "header.notifications": "알림",
+ "header.search": "검색",
+ "header.profile": "프로필",
+ "notifications.loading": "알림을 로딩중입니다.",
+ "chats.loading": "채팅을 로딩중입니다.",
+ "motd.welcome": "NodeBB에 오신 것을 환경합니다.",
+ "previouspage": "이전 페이지",
+ "nextpage": "다음 페이지",
+ "alert.success": "성공",
+ "alert.error": "오류",
+ "alert.banned": "차단됨",
+ "alert.banned.message": "이 계정은 차단되었습니다. 지금 로그아웃됩니다.",
+ "alert.unfollow": "더 이상 %1님을 팔로우하지 않습니다.",
+ "alert.follow": "%1님을 팔로우합니다.",
+ "online": "온라인",
+ "users": "사용자",
+ "topics": "주제",
+ "posts": "게시물",
+ "views": "조회 수",
+ "reputation": "인기도",
+ "read_more": "전체 보기",
+ "posted_ago_by_guest": "익명 사용자가 %1에 작성했습니다.",
+ "posted_ago_by": "%2님이 %1에 작성했습니다.",
+ "posted_ago": "%1에 작성되었습니다.",
+ "posted_in_ago_by_guest": "%2, 익명 사용자가 %1에 작성했습니다.",
+ "posted_in_ago_by": "%3님이 %2 %1에 작성했습니다.",
+ "posted_in_ago": "%2 %1에 작성되었습니다. ",
+ "replied_ago": "%1에 답글이 작성되었습니다.",
+ "user_posted_ago": "%1님이 %2에 작성했습니다.",
+ "guest_posted_ago": "익명 사용자가 %1에 작성했습니다.",
+ "last_edited_by_ago": "%1님이 %2에 마지막으로 수정했습니다.",
+ "norecentposts": "최근 작성된 게시물이 없습니다.",
+ "norecenttopics": "최근 생성된 생성된 주제가 없습니다.",
+ "recentposts": "최근 게시물",
+ "recentips": "최근 로그인 IP",
+ "away": "자리 비움",
+ "dnd": "다른 용무 중",
+ "invisible": "오프라인으로 표시",
+ "offline": "오프라인",
+ "email": "이메일",
+ "language": "언어",
+ "guest": "익명 사용자",
+ "guests": "익명 사용자"
+}
\ No newline at end of file
diff --git a/public/language/ko/language.json b/public/language/ko/language.json
new file mode 100644
index 0000000000..9a19e01124
--- /dev/null
+++ b/public/language/ko/language.json
@@ -0,0 +1,5 @@
+{
+ "name": "한국어",
+ "code": "ko",
+ "dir": "ltr"
+}
\ No newline at end of file
diff --git a/public/language/ko/login.json b/public/language/ko/login.json
new file mode 100644
index 0000000000..c967c46230
--- /dev/null
+++ b/public/language/ko/login.json
@@ -0,0 +1,8 @@
+{
+ "username": "사용자 이름 / 이메일",
+ "remember_me": "로그인 유지",
+ "forgot_password": "비밀번호 초기화",
+ "alternative_logins": "다른 방법으로 로그인",
+ "failed_login_attempt": "로그인에 실패했습니다.",
+ "login_successful": "성공적으로 로그인했습니다."
+}
diff --git a/public/language/ko/modules.json b/public/language/ko/modules.json
new file mode 100644
index 0000000000..395536cb8a
--- /dev/null
+++ b/public/language/ko/modules.json
@@ -0,0 +1,8 @@
+{
+ "chat.chatting_with": "님과의 채팅",
+ "chat.placeholder": "여기에 메시지를 입력하세요.",
+ "chat.send": "전송",
+ "chat.no_active": "활성화된 채팅이 없습니다.",
+ "chat.user_typing": "%1님이 입력 중입니다.",
+ "chat.user_has_messaged_you": "%1님이 메시지를 보냈습니다."
+}
\ No newline at end of file
diff --git a/public/language/ko/notifications.json b/public/language/ko/notifications.json
new file mode 100644
index 0000000000..b2e9d7e4ce
--- /dev/null
+++ b/public/language/ko/notifications.json
@@ -0,0 +1,18 @@
+{
+ "title": "알림",
+ "no_notifs": "새 알림이 없습니다.",
+ "see_all": "모든 알림 보기",
+ "back_to_home": "홈으로 돌아가기",
+ "outgoing_link": "외부 링크",
+ "outgoing_link_message": "다른 사이트로 이동합니다.",
+ "continue_to": "계속",
+ "return_to": "돌아가기 : ",
+ "new_notification": "새 알림",
+ "you_have_unread_notifications": "읽지 않은 알림이 있습니다.",
+ "user_made_post": "%1님이 새 게시물을 작성했습니다.",
+ "new_message_from": "%1님이 메시지를 보냈습니다.",
+ "upvoted_your_post": "%1님이 내 게시물을 추천했습니다.",
+ "favourited_your_post": "%1님이 내 게시물을 관심글로 등록했습니다.",
+ "user_flagged_post": "%1님이 게시물을 신고했습니다.",
+ "user_posted_to": "%1님이 %2님의 게시물에 답글을 작성했습니다."
+}
\ No newline at end of file
diff --git a/public/language/ko/pages.json b/public/language/ko/pages.json
new file mode 100644
index 0000000000..230a012757
--- /dev/null
+++ b/public/language/ko/pages.json
@@ -0,0 +1,15 @@
+{
+ "home": "홈",
+ "unread": "읽지 않은 주제",
+ "popular": "인기 주제",
+ "recent": "최근 주제",
+ "users": "사용자",
+ "notifications": "알림",
+ "user.edit": "%1님의 프로필 수정",
+ "user.following": "%1님이 팔로우하는 사용자",
+ "user.followers": "%1님을 팔로우하는 사용자",
+ "user.posts": "%1님이 작성한 게시물",
+ "user.topics": "%1님이 생성한 주제",
+ "user.favourites": "%1님이 좋아하는 게시물",
+ "user.settings": "설정"
+}
\ No newline at end of file
diff --git a/public/language/ko/recent.json b/public/language/ko/recent.json
new file mode 100644
index 0000000000..0280acfaab
--- /dev/null
+++ b/public/language/ko/recent.json
@@ -0,0 +1,7 @@
+{
+ "title": "최근 주제",
+ "day": "일간",
+ "week": "주간",
+ "month": "월간",
+ "no_recent_topics": "최근 생성된 주제가 없습니다."
+}
\ No newline at end of file
diff --git a/public/language/ko/register.json b/public/language/ko/register.json
new file mode 100644
index 0000000000..4d531f7cb4
--- /dev/null
+++ b/public/language/ko/register.json
@@ -0,0 +1,18 @@
+{
+ "register": "회원가입",
+ "help.email": "입력하신 이메일 주소는 공개되지 않으며, 설정을 통해 공개하실 수 있습니다.",
+ "help.username_restrictions": "%1자 이상 %2자 이하의 고유한 이름을 입력하세요. @username 같은 방식으로 다른 사람들을 언급할 수 있습니다.",
+ "help.minimum_password_length": "비밀번호는 최소 %1자로 제한됩니다.",
+ "email_address": "이메일",
+ "email_address_placeholder": "여기에 이메일 주소를 입력하세요.",
+ "username": "사용자 이름",
+ "username_placeholder": "여기에 사용자 이름을 입력하세요.",
+ "password": "비밀번호",
+ "password_placeholder": "여기에 비밀번호를 입력하세요.",
+ "confirm_password": "비밀번호 재입력",
+ "confirm_password_placeholder": "여기에 비밀번호를 재입력하세요.",
+ "register_now_button": "회원가입",
+ "alternative_registration": "다른 방법으로 회원가입",
+ "terms_of_use": "이용약관",
+ "agree_to_terms_of_use": "이용약관에 동의합니다."
+}
\ No newline at end of file
diff --git a/public/language/ko/reset_password.json b/public/language/ko/reset_password.json
new file mode 100644
index 0000000000..d229a5a931
--- /dev/null
+++ b/public/language/ko/reset_password.json
@@ -0,0 +1,14 @@
+{
+ "reset_password": "비밀번호 초기화",
+ "update_password": "비밀번호 변경",
+ "password_changed.title": "비밀번호가 변경되었습니다.",
+ "password_changed.message": "
성공적으로 비밀번호가 초기화되었습니다. 다시 로그인해 주세요.",
+ "wrong_reset_code.title": "올바르지 않은 초기화 코드입니다.",
+ "wrong_reset_code.message": "올바르지 않은 초기화 코드입니다. 다시 시도하거나 새로운 초기화 코드를 요청하세요.",
+ "new_password": "새 비밀번호",
+ "repeat_password": "비밀번호 재입력",
+ "enter_email": "이메일 주소를 입력하면 비밀번호를 초기화하는 방법을 메일로 알려드립니다.",
+ "enter_email_address": "여기에 이메일 주소를 입력하세요.",
+ "password_reset_sent": "이메일이 발송되었습니다.",
+ "invalid_email": "올바르지 않거나 가입되지 않은 이메일입니다."
+}
\ No newline at end of file
diff --git a/public/language/ko/success.json b/public/language/ko/success.json
new file mode 100644
index 0000000000..e7b911bb2d
--- /dev/null
+++ b/public/language/ko/success.json
@@ -0,0 +1,6 @@
+{
+ "success": "성공",
+ "topic-post": "성공적으로 게시물을 작성했습니다.",
+ "authentication-successful": "인증에 성공했습니다.",
+ "settings-saved": "성공적으로 설정을 저장했습니다."
+}
\ No newline at end of file
diff --git a/public/language/ko/topic.json b/public/language/ko/topic.json
new file mode 100644
index 0000000000..3432b0ae87
--- /dev/null
+++ b/public/language/ko/topic.json
@@ -0,0 +1,95 @@
+{
+ "topic": "주제",
+ "topic_id": "주제 ID",
+ "topic_id_placeholder": "여기에 주제 ID를 입력하세요.",
+ "no_topics_found": "주제를 찾을 수 없습니다.",
+ "no_posts_found": "게시물을 찾을 수 없습니다.",
+ "post_is_deleted": "이 게시물은 삭제되었습니다.",
+ "profile": "프로필",
+ "posted_by": "%1님이 작성했습니다.",
+ "posted_by_guest": "익명 사용자가 작성했습니다.",
+ "chat": "채팅",
+ "notify_me": "이 주제의 새 답글 알리기",
+ "quote": "인용",
+ "reply": "답글",
+ "edit": "수정",
+ "delete": "삭제",
+ "restore": "복원",
+ "move": "이동",
+ "fork": "복제",
+ "banned": "차단됨",
+ "link": "링크",
+ "share": "공유",
+ "tools": "도구",
+ "flag": "신고",
+ "bookmark_instructions": "여기를 누르면 마지막에 읽던 위치로 이동합니다.",
+ "flag_title": "이 게시물을 신고",
+ "flag_confirm": "정말로 신고하시겠습니까?",
+ "flag_success": "이 게시물은 신고되었습니다.",
+ "deleted_message": "이 주제는 삭제되었습니다. 게시물 관리 권한이 있는 사용자만 접근할 수 있습니다.",
+ "following_topic.message": "이제 이 주제에 새 답글이 달리면 알림을 받습니다.",
+ "not_following_topic.message": "더 이상 이 주제의 새 답글을 알리지 않습니다.",
+ "login_to_subscribe": "이 주제의 알림을 받기 위해서는 로그인해야 합니다.",
+ "markAsUnreadForAll.success": "모든 사용자에 대해 읽지 않음으로 표시했습니다.",
+ "watch": "관심 주제",
+ "watch.title": "이 주제의 새 답글 알리기",
+ "share_this_post": "이 게시물 공유",
+ "thread_tools.title": "주제 도구",
+ "thread_tools.markAsUnreadForAll": "모두에게 읽지 않음으로 표시",
+ "thread_tools.pin": "상단 고정",
+ "thread_tools.unpin": "상단 고정 해제",
+ "thread_tools.lock": "잠금",
+ "thread_tools.unlock": "잠금 해제",
+ "thread_tools.move": "이동",
+ "thread_tools.move_all": "모두 이동",
+ "thread_tools.fork": "복제",
+ "thread_tools.delete": "삭제",
+ "thread_tools.delete_confirm": "이 주제를 삭제하시겠습니까?",
+ "thread_tools.restore": "복원",
+ "thread_tools.restore_confirm": "이 주제를 복원하시겠습니까?",
+ "topic_lock_success": "성공적으로 주제를 잠갔습니다.",
+ "topic_unlock_success": "성공적으로 주제가 잠금 해제되었습니다.",
+ "topic_pin_success": "성공적으로 주제가 상단에 고정되었습니다.",
+ "topic_unpin_success": "성공적으로 주제가 상단 고정 해제되었습니다.",
+ "topic_move_success": "성공적으로 이 주제를 %1로 이동했습니다.",
+ "post_delete_confirm": "이 게시물을 삭제하시겠습니까?",
+ "post_restore_confirm": "이 게시물을 복원하시겠습니까?",
+ "post_delete_error": "이 게시물을 삭제하는 데 실패했습니다.",
+ "post_restore_error": "이 게시물을 복원하는 데 실패했습니다.",
+ "load_categories": "카테고리 로딩 중",
+ "disabled_categories_note": "비활성화된 카테고리는 회색으로 표시됩니다.",
+ "confirm_move": "이동",
+ "confirm_fork": "복제",
+ "favourite": "좋아요",
+ "favourites": "좋아요",
+ "favourites.has_no_favourites": "좋아하는 게시물이 없습니다.",
+ "loading_more_posts": "게시물을 로딩 중",
+ "move_topic": "주제 이동",
+ "move_topics": "주제 이동",
+ "move_post": "게시물 이동",
+ "post_moved": "게시물이 이동되었습니다.",
+ "fork_topic": "주제 복제",
+ "topic_will_be_moved_to": "이 주제를 지정한 카테고리로 이동합니다.",
+ "fork_topic_instruction": "복제할 게시물을 선택하세요.",
+ "fork_no_pids": "게시물이 선택되지 않았습니다.",
+ "fork_success": "성공적으로 주제를 복제했습니다.",
+ "composer.title_placeholder": "여기에 제목을 입력하세요.",
+ "composer.write": "작성",
+ "composer.preview": "미리보기",
+ "composer.help": "도움말",
+ "composer.discard": "취소",
+ "composer.submit": "등록",
+ "composer.replying_to": "%1님께 답글>",
+ "composer.new_topic": "새 주제 생성",
+ "composer.uploading": "업로드 중",
+ "composer.thumb_url_label": "섬네일 URL",
+ "composer.thumb_title": "이 주제에 섬네일 추가",
+ "composer.thumb_url_placeholder": "http://example.com/thumb.png",
+ "composer.thumb_file_label": "혹은 파일 업로드",
+ "composer.thumb_remove": "섬네일 삭제",
+ "composer.drag_and_drop_images": "이미지를 여기에 드래그&드롭하세요.",
+ "composer.upload_instructions": "이미지를 드래그&드롭해서 업로드",
+ "more_users_and_guests": "%1명 이상의 회원과 %2명의 익명 사용자",
+ "more_users": "%1명 이상의 회원",
+ "more_guests": "%1명 이상의 익명 사용자"
+}
\ No newline at end of file
diff --git a/public/language/ko/unread.json b/public/language/ko/unread.json
new file mode 100644
index 0000000000..b7ffd0ad4e
--- /dev/null
+++ b/public/language/ko/unread.json
@@ -0,0 +1,9 @@
+{
+ "title": "읽지 않은 주제",
+ "no_unread_topics": "읽지 않은 주제가 없습니다.",
+ "load_more": "더 보기",
+ "mark_as_read": "읽음으로 표시",
+ "selected": "선택된 주제",
+ "all": "전체",
+ "topics_marked_as_read.success": "성공적으로 읽음으로 표시했습니다."
+}
\ No newline at end of file
diff --git a/public/language/ko/user.json b/public/language/ko/user.json
new file mode 100644
index 0000000000..d341173148
--- /dev/null
+++ b/public/language/ko/user.json
@@ -0,0 +1,64 @@
+{
+ "banned": "차단됨",
+ "offline": "오프라인",
+ "username": "사용자 이름",
+ "email": "이메일",
+ "fullname": "이름",
+ "website": "웹 사이트",
+ "location": "거주지",
+ "age": "나이",
+ "joined": "가입일",
+ "lastonline": "최근 로그인",
+ "profile": "프로필",
+ "profile_views": "프로필 조회 수",
+ "reputation": "인기도",
+ "favourites": "좋아요",
+ "followers": "이 사용자를 팔로우",
+ "following": "이 사용자가 팔로우",
+ "signature": "서명",
+ "gravatar": "Gravatar",
+ "birthday": "생일",
+ "chat": "채팅",
+ "follow": "팔로우",
+ "unfollow": "팔로우 취소",
+ "profile_update_success": "성공적으로 프로필을 저장했습니다.",
+ "change_picture": "사진 변경",
+ "edit": "프로필 수정",
+ "uploaded_picture": "사진 업로드",
+ "upload_new_picture": "새 사진 업로드",
+ "current_password": "현재 비밀번호",
+ "change_password": "비밀번호 변경",
+ "change_password_error": "유효하지 않은 비밀번호",
+ "change_password_error_wrong_current": "현재 비밀번호가 올바르지 않습니다.",
+ "change_password_error_length": "새 비밀번호가 너무 짧습니다.",
+ "change_password_error_match": "비밀번호 재입력이 새 비밀번호와 일치하지 않습니다.",
+ "change_password_error_privileges": "비밀번호를 바꿀 권한이 없습니다.",
+ "change_password_success": "성공적으로 비밀번호를 변경했습니다.",
+ "confirm_password": "비밀번호 재입력",
+ "password": "새 비밀번호",
+ "username_taken_workaround": "새 사용자 이름이 이미 존재하여 %1로 저장되었습니다.",
+ "upload_picture": "사진 업로드",
+ "upload_a_picture": "사진 업로드",
+ "image_spec": "PNG, JPG, GIF 파일만을 업로드할 수 있습니다.",
+ "max": "최대",
+ "settings": "설정",
+ "show_email": "이메일 공개",
+ "digest_label": "포럼 이메일 수신",
+ "digest_description": "지정한 주기로 이 포럼의 새 알림과 주제를 이메일로 수신합니다.",
+ "digest_off": "해제",
+ "digest_daily": "매일",
+ "digest_weekly": "매주",
+ "digest_monthly": "매월",
+ "has_no_follower": "아무도 이 사용자를 팔로우하지 않습니다.",
+ "follows_no_one": "이 사용자는 아무도 팔로우하지 않습니다.",
+ "has_no_posts": "이 사용자는 게시물을 작성하지 않았습니다.",
+ "has_no_topics": "이 사용자는 주제를 생성하지 않았습니다.",
+ "email_hidden": "이메일 비공개",
+ "hidden": "비공개",
+ "paginate_description": "주제와 게시물을 여러 페이지로 나누어 표시",
+ "topics_per_page": "페이지 당 주제 수",
+ "posts_per_page": "페이지 당 게시물 수",
+ "notification_sounds": "알림을 수신하면 알림음을 재생",
+ "browsing": "페이지 열기",
+ "open_links_in_new_tab": "외부 링크를 새 탭에서 열기"
+}
\ No newline at end of file
diff --git a/public/language/ko/users.json b/public/language/ko/users.json
new file mode 100644
index 0000000000..63040e0782
--- /dev/null
+++ b/public/language/ko/users.json
@@ -0,0 +1,8 @@
+{
+ "latest_users": "가입일",
+ "top_posters": "게시물 수",
+ "most_reputation": "인기도",
+ "search": "검색",
+ "enter_username": "검색할 사용자 이름을 입력하세요.",
+ "load_more": "더 보기"
+}
\ No newline at end of file
diff --git a/public/src/admin.js b/public/src/admin.js
index 65a7504208..d504750add 100644
--- a/public/src/admin.js
+++ b/public/src/admin.js
@@ -1,4 +1,5 @@
"use strict";
+/*global app, socket*/
var admin = {};
@@ -17,7 +18,47 @@ var admin = {};
}
}
});
- });
+ });
};
+ $(function() {
+ var menuEl = $('.sidebar-nav'),
+ liEls = menuEl.find('li'),
+ parentEl,
+ activate = function(li) {
+ liEls.removeClass('active');
+ li.addClass('active');
+ };
+
+ // also on ready, check the pathname, maybe it was a page refresh and no item was clicked
+ liEls.each(function(i, li){
+ li = $(li);
+ if ((li.find('a').attr('href') || '').indexOf(location.pathname) >= 0) {
+ activate(li);
+ }
+ });
+
+ // On menu click, change "active" state
+ menuEl.on('click', function(e) {
+ parentEl = $(e.target).parent();
+ if (parentEl.is('li')) {
+ activate(parentEl);
+ }
+ });
+ });
+
+ socket.emit('admin.config.get', function(err, config) {
+ if(err) {
+ return app.alert({
+ alert_id: 'config_status',
+ timeout: 2500,
+ title: 'Error',
+ message: 'NodeBB encountered a problem getting config',
+ type: 'danger'
+ });
+ }
+
+ // move this to admin.config
+ app.config = config;
+ });
}());
\ No newline at end of file
diff --git a/public/src/forum/admin/categories.js b/public/src/forum/admin/categories.js
index 78e173997a..6a6f2f9373 100644
--- a/public/src/forum/admin/categories.js
+++ b/public/src/forum/admin/categories.js
@@ -308,9 +308,11 @@ define(['uploader'], function(uploader) {
uid: uid,
privilege: privilege,
set: !anchorEl.hasClass('active')
- }, function(err, privileges) {
- anchorEl.toggleClass('active', privileges[privilege]);
-
+ }, function(err) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+ anchorEl.toggleClass('active', !anchorEl.hasClass('active'));
Categories.refreshPrivilegeList(cid);
});
});
diff --git a/public/src/forum/admin/footer.js b/public/src/forum/admin/footer.js
deleted file mode 100644
index a7a040a0f4..0000000000
--- a/public/src/forum/admin/footer.js
+++ /dev/null
@@ -1,41 +0,0 @@
-"use strict";
-/*global app, socket*/
-
-$(function() {
- var menuEl = $('.sidebar-nav'),
- liEls = menuEl.find('li'),
- parentEl,
- activate = function(li) {
- liEls.removeClass('active');
- li.addClass('active');
- };
-
- // also on ready, check the pathname, maybe it was a page refresh and no item was clicked
- liEls.each(function(i, li){
- li = $(li);
- if ((li.find('a').attr('href') || '').indexOf(location.pathname) >= 0) {
- activate(li);
- }
- });
-
- // On menu click, change "active" state
- menuEl.on('click', function(e) {
- parentEl = $(e.target).parent();
- if (parentEl.is('li')) {
- activate(parentEl);
- }
- });
-});
-
-socket.emit('admin.config.get', function(err, config) {
- if(err) {
- return app.alert({
- alert_id: 'config_status',
- timeout: 2500,
- title: 'Error',
- message: 'NodeBB encountered a problem getting config',
- type: 'danger'
- });
- }
- app.config = config;
-});
diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js
index 460bfa43b6..3d4775b774 100644
--- a/public/src/forum/topic.js
+++ b/public/src/forum/topic.js
@@ -278,7 +278,7 @@ define(['forum/pagination', 'forum/topic/threadTools', 'forum/topic/postTools',
pagination.recreatePaginationLinks(newPageCount);
- if(pagination.currentPage === pagination.pageCount) {
+ if (pagination.currentPage === pagination.pageCount) {
createNewPosts(data);
} else if(data.posts && data.posts.length && parseInt(data.posts[0].uid, 10) === parseInt(app.uid, 10)) {
pagination.loadPage(pagination.pageCount);
@@ -390,8 +390,11 @@ define(['forum/pagination', 'forum/topic/threadTools', 'forum/topic/postTools',
}
function toggleModTools(postHtml, privileges) {
- postHtml.find('.edit, .delete').toggleClass('none', !privileges.editable);
- postHtml.find('.move').toggleClass('none', !privileges.move);
+ postHtml.find('.edit, .delete').toggleClass('none', !privileges.meta.editable);
+ postHtml.find('.move').toggleClass('none', !privileges.meta.move);
+ postHtml.find('.reply, .quote').toggleClass('none', !$('.post_reply').length)
+ var isSelfPost = parseInt(postHtml.attr('data-uid'), 10) === parseInt(app.uid, 10);
+ postHtml.find('.chat, .flag').toggleClass('none', isSelfPost);
}
function loadMorePosts(tid, after, callback) {
diff --git a/public/src/forum/topic/threadTools.js b/public/src/forum/topic/threadTools.js
index 35dbf38a42..7f6fe6a4b7 100644
--- a/public/src/forum/topic/threadTools.js
+++ b/public/src/forum/topic/threadTools.js
@@ -21,53 +21,48 @@ define(['forum/topic/fork', 'forum/topic/move'], function(fork, move) {
ThreadTools.setPinnedState({tid: tid, isPinned: true});
}
- if (ajaxify.variables.get('expose_tools') === '1') {
+ $('.delete_thread').on('click', function(e) {
+ var command = threadState.deleted !== '1' ? 'delete' : 'restore';
- $('.thread-tools').removeClass('hide');
-
- $('.delete_thread').on('click', function(e) {
- var command = threadState.deleted !== '1' ? 'delete' : 'restore';
-
- translator.translate('[[topic:thread_tools.' + command + '_confirm]]', function(msg) {
- bootbox.confirm(msg, function(confirm) {
- if (confirm) {
- socket.emit('topics.' + command, [tid]);
- }
- });
+ translator.translate('[[topic:thread_tools.' + command + '_confirm]]', function(msg) {
+ bootbox.confirm(msg, function(confirm) {
+ if (confirm) {
+ socket.emit('topics.' + command, [tid]);
+ }
});
-
- return false;
});
- $('.lock_thread').on('click', function(e) {
- socket.emit(threadState.locked !== '1' ? 'topics.lock' : 'topics.unlock', [tid]);
- return false;
- });
+ return false;
+ });
- $('.pin_thread').on('click', function(e) {
- socket.emit(threadState.pinned !== '1' ? 'topics.pin' : 'topics.unpin', [tid]);
- return false;
- });
+ $('.lock_thread').on('click', function(e) {
+ socket.emit(threadState.locked !== '1' ? 'topics.lock' : 'topics.unlock', [tid]);
+ return false;
+ });
- $('.markAsUnreadForAll').on('click', function() {
- var btn = $(this);
- socket.emit('topics.markAsUnreadForAll', [tid], function(err) {
- if(err) {
- return app.alertError(err.message);
- }
- app.alertSuccess('[[topic:markAsUnreadForAll.success]]');
- btn.parents('.thread-tools.open').find('.dropdown-toggle').trigger('click');
- });
- return false;
- });
+ $('.pin_thread').on('click', function(e) {
+ socket.emit(threadState.pinned !== '1' ? 'topics.pin' : 'topics.unpin', [tid]);
+ return false;
+ });
- $('.move_thread').on('click', function(e) {
- move.init([tid], ajaxify.variables.get('category_id'));
- return false;
+ $('.markAsUnreadForAll').on('click', function() {
+ var btn = $(this);
+ socket.emit('topics.markAsUnreadForAll', [tid], function(err) {
+ if(err) {
+ return app.alertError(err.message);
+ }
+ app.alertSuccess('[[topic:markAsUnreadForAll.success]]');
+ btn.parents('.thread-tools.open').find('.dropdown-toggle').trigger('click');
});
+ return false;
+ });
- fork.init();
- }
+ $('.move_thread').on('click', function(e) {
+ move.init([tid], ajaxify.variables.get('category_id'));
+ return false;
+ });
+
+ fork.init();
socket.emit('topics.followCheck', tid, function(err, state) {
setFollowState(state);
diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js
index e68130519f..4d99a202f3 100644
--- a/public/src/modules/chat.js
+++ b/public/src/modules/chat.js
@@ -323,10 +323,11 @@ define(['taskbar', 'string', 'sounds'], function(taskbar, S, sounds) {
message.append(userPicture)
.append(userName)
- .append('
');
+ .append('
')
+ .prepend(time);
}
- message.append(S(data.content + time).stripTags('p').s);
+ message.append(S(data.content).stripTags('p').s);
message.toggleClass('chat-message-them', !isYou);
message.find('img:not(".chat-user-image")').addClass('img-responsive');
diff --git a/src/categories.js b/src/categories.js
index 0b0e2dbe76..06b96c5c84 100644
--- a/src/categories.js
+++ b/src/categories.js
@@ -8,7 +8,6 @@ var db = require('./database'),
Groups = require('./groups'),
topics = require('./topics'),
plugins = require('./plugins'),
- CategoryTools = require('./categoryTools'),
meta = require('./meta'),
emitter = require('./emitter'),
validator = require('validator'),
@@ -60,6 +59,10 @@ var db = require('./database'),
});
};
+ Categories.exists = function(cid, callback) {
+ db.isSortedSetMember('categories:cid', cid, callback);
+ };
+
Categories.getCategoryById = function(cid, start, end, uid, callback) {
Categories.getCategoryData(cid, function(err, category) {
if(err || !category) {
diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js
index 925e83e42e..8142a06c24 100644
--- a/src/categories/recentreplies.js
+++ b/src/categories/recentreplies.js
@@ -6,8 +6,7 @@ var async = require('async'),
db = require('./../database'),
posts = require('./../posts'),
- topics = require('./../topics'),
- CategoryTools = require('./../categoryTools');
+ topics = require('./../topics');
module.exports = function(Categories) {
Categories.getRecentReplies = function(cid, uid, count, callback) {
diff --git a/src/categoryTools.js b/src/categoryTools.js
deleted file mode 100644
index c965b9b17c..0000000000
--- a/src/categoryTools.js
+++ /dev/null
@@ -1,135 +0,0 @@
-"use strict";
-
-var Groups = require('./groups'),
- User = require('./user'),
- categories = require('./categories'),
-
- async = require('async'),
- db = require('./database');
-
-var internals = {
- isMember: function(key, candidate, next){
- Groups.exists(key, function(err, exists) {
- if (exists) {
- Groups.isMember(candidate, key, next);
- } else {
- next(null, null);
- }
- });
- },
-
- isMemberOfGroupList: function(key, candidate, next){
- Groups.exists(key, function(err, exists) {
- if (exists) {
- Groups.isMemberOfGroupList(candidate, key, next);
- } else {
- next(null, null);
- }
- });
- }
-};
-
-var CategoryTools = {};
-
-CategoryTools.exists = function(cid, callback) {
- db.isSortedSetMember('categories:cid', cid, callback);
-};
-
-CategoryTools.privileges = function(cid, uid, callback) {
- async.parallel({
- "disabled": function(next) {
- categories.getCategoryField(cid, 'disabled', next);
- },
- read: function(next) {
- internals.isMember('cid:' + cid + ':privileges:read', uid, next);
- },
- "topics:create": function(next) {
- internals.isMember('cid:' + cid + ':privileges:topics:create', uid, next);
- },
- "topics:reply": function(next) {
- internals.isMember('cid:' + cid + ':privileges:topics:reply', uid, next);
- },
- "groups:read": function(next) {
- internals.isMemberOfGroupList('cid:' + cid + ':privileges:groups:read', uid, next);
- },
- "groups:topics:create": function(next) {
- internals.isMemberOfGroupList('cid:' + cid + ':privileges:groups:topics:create', uid, next);
- },
- "groups:topics:reply": function(next) {
- internals.isMemberOfGroupList('cid:' + cid + ':privileges:groups:topics:reply', uid, next);
- },
- mods: function(next) {
- User.isModerator(uid, cid, next);
- },
- admin: function(next) {
- User.isAdministrator(uid, next);
- }
- }, function(err, privileges) {
- if (privileges) {
- privileges.meta = {
- read: (
- (
- parseInt(privileges.disabled, 10) !== 1 &&
- (
- (privileges['read'] === null && privileges['groups:read'] === null) ||
- privileges['read'] || privileges['groups:read']
- )
- ) ||
- privileges.mods ||
- privileges.admin
- ),
- "topics:create": (
- (
- parseInt(privileges.disabled, 10) !== 1 &&
- (
- (privileges['topics:create'] === null && privileges['groups:topics:create'] === null) ||
- privileges['topics:create'] || privileges['groups:topics:create']
- )
- ) ||
- privileges.mods ||
- privileges.admin
- ),
- "topics:reply": (
- (
- parseInt(privileges.disabled, 10) !== 1 &&
- (
- (privileges['topics:reply'] === null && privileges['groups:topics:reply'] === null) ||
- privileges['topics:reply'] || privileges['groups:topics:reply']
- )
- ) ||
- privileges.mods ||
- privileges.admin
- ),
- editable: privileges.mods || privileges.admin,
- view_deleted: privileges.mods || privileges.admin
- };
- }
-
- // console.log(privileges, cid, uid);
- callback(err, privileges || null);
- });
-};
-
-CategoryTools.groupPrivileges = function(cid, groupName, callback) {
- async.parallel({
- "groups:read":function(next) {
- internals.isMember('cid:' + cid + ':privileges:groups:read', groupName, function(err, isMember){
- next(err, !!isMember);
- });
- },
- "groups:topics:create":function(next) {
- internals.isMember('cid:' + cid + ':privileges:groups:topics:create', groupName, function(err, isMember){
- next(err, !!isMember);
- });
- },
- "groups:topics:reply":function(next) {
- internals.isMember('cid:' + cid + ':privileges:groups:topics:reply', groupName, function(err, isMember){
- next(err, !!isMember);
- });
- }
- }, function(err, privileges) {
- callback(err, privileges || null);
- });
-};
-
-module.exports = CategoryTools;
diff --git a/src/controllers/categories.js b/src/controllers/categories.js
index 58fd5e80d5..12d5f8346f 100644
--- a/src/controllers/categories.js
+++ b/src/controllers/categories.js
@@ -4,7 +4,7 @@ var categoriesController = {},
async = require('async'),
qs = require('querystring'),
nconf = require('nconf'),
- categoryTools = require('./../categoryTools'),
+ privileges = require('../privileges'),
user = require('./../user'),
categories = require('./../categories'),
topics = require('./../topics');
@@ -72,16 +72,16 @@ categoriesController.get = function(req, res, next) {
});
},
function(next) {
- categoryTools.privileges(cid, uid, function(err, categoryPrivileges) {
+ privileges.categories.get(cid, uid, function(err, categoryPrivileges) {
if (err) {
return next(err);
}
if (!categoryPrivileges.meta.read) {
- next(new Error('[[error:no-privileges]]'));
- } else {
- next(null, categoryPrivileges);
+ return next(new Error('[[error:no-privileges]]'));
}
+
+ next(null, categoryPrivileges);
});
},
function (privileges, next) {
@@ -152,14 +152,14 @@ categoriesController.get = function(req, res, next) {
}
], function (err, data) {
if (err) {
- if (err.message === 'not-enough-privileges') {
- return res.redirect('403');
+ if (err.message === '[[error:no-privileges]]') {
+ return res.locals.isAPI ? res.json(403, err.message) : res.redirect('403');
} else {
- return res.redirect('404');
+ return res.locals.isAPI ? res.json(404, 'not-found') : res.redirect('404');
}
}
- if(data.link) {
+ if (data.link) {
return res.redirect(data.link);
}
diff --git a/src/controllers/index.js b/src/controllers/index.js
index 4bb46ed943..9d3254fca1 100644
--- a/src/controllers/index.js
+++ b/src/controllers/index.js
@@ -17,7 +17,7 @@ var topicsController = require('./topics'),
topics = require('./../topics'),
plugins = require('./../plugins'),
categories = require('./../categories'),
- categoryTools = require('./../categoryTools');
+ privileges = require('../privileges');
@@ -69,12 +69,6 @@ Controllers.home = function(req, res, next) {
return !category.disabled;
});
- function canSee(category, next) {
- categoryTools.privileges(category.cid, ((req.user) ? req.user.uid || 0 : 0), function(err, privileges) {
- next(!err && privileges.meta.read);
- });
- }
-
function getRecentReplies(category, callback) {
categories.getRecentReplies(category.cid, uid, parseInt(category.numRecentReplies, 10), function (err, posts) {
category.posts = posts;
@@ -83,7 +77,11 @@ Controllers.home = function(req, res, next) {
});
}
- async.filter(data.categories, canSee, function(visibleCategories) {
+ async.filter(data.categories, function (category, next) {
+ privileges.categories.canRead(category.cid, uid, function(err, canRead) {
+ next(!err && canRead);
+ });
+ }, function(visibleCategories) {
data.categories = visibleCategories;
async.each(data.categories, getRecentReplies, function (err) {
diff --git a/src/controllers/topics.js b/src/controllers/topics.js
index b16541afc7..d407c07168 100644
--- a/src/controllers/topics.js
+++ b/src/controllers/topics.js
@@ -10,27 +10,27 @@ var topicsController = {},
meta = require('./../meta'),
topics = require('./../topics'),
posts = require('../posts'),
- threadTools = require('./../threadTools'),
+ privileges = require('../privileges'),
utils = require('./../../public/src/utils');
topicsController.get = function(req, res, next) {
var tid = req.params.topic_id,
page = req.query.page || 1,
uid = req.user ? req.user.uid : 0,
- privileges;
+ userPrivileges;
async.waterfall([
function(next) {
- threadTools.privileges(tid, uid, function(err, userPrivileges) {
+ privileges.topics.get(tid, uid, function(err, privileges) {
if (err) {
return next(err);
}
- if (!userPrivileges.meta.read) {
+ if (!privileges.meta.read) {
return next(new Error('[[error:no-privileges]]'));
}
- privileges = userPrivileges;
+ userPrivileges = privileges;
next();
});
},
@@ -45,7 +45,7 @@ topicsController.get = function(req, res, next) {
topics.getTopicWithPosts(tid, uid, start, end, function (err, topicData) {
if (topicData) {
- if (parseInt(topicData.deleted, 10) === 1 && parseInt(topicData.expose_tools, 10) === 0) {
+ if (parseInt(topicData.deleted, 10) === 1 && !userPrivileges.view_deleted) {
return next(new Error('[[error:no-topic]]'));
}
topicData.currentPage = page;
@@ -147,14 +147,14 @@ topicsController.get = function(req, res, next) {
}
], function (err, data) {
if (err) {
- if (err.message === 'not-enough-privileges') {
+ if (err.message === '[[error:no-privileges]]') {
return res.locals.isAPI ? res.json(403, err.message) : res.redirect('403');
} else {
return res.locals.isAPI ? res.json(404, 'not-found') : res.redirect('404');
}
}
- data.privileges = privileges;
+ data.privileges = userPrivileges;
var topic_url = tid + (req.params.slug ? '/' + req.params.slug : '');
var queryString = qs.stringify(req.query);
diff --git a/src/privileges.js b/src/privileges.js
index 275db8b724..e93584b727 100644
--- a/src/privileges.js
+++ b/src/privileges.js
@@ -2,7 +2,8 @@
var privileges = {};
+require('./privileges/categories')(privileges);
+require('./privileges/topics')(privileges);
require('./privileges/posts')(privileges);
-
module.exports = privileges;
\ No newline at end of file
diff --git a/src/privileges/categories.js b/src/privileges/categories.js
new file mode 100644
index 0000000000..af2f50cdaf
--- /dev/null
+++ b/src/privileges/categories.js
@@ -0,0 +1,118 @@
+
+'use strict';
+
+var async = require('async'),
+
+ user = require('../user'),
+ groups = require('../groups'),
+ helpers = require('./helpers');
+
+
+module.exports = function(privileges) {
+
+ privileges.categories = {};
+
+ privileges.categories.get = function(cid, uid, callback) {
+ async.parallel({
+ 'topics:create': function(next) {
+ helpers.allowedTo('topics:create', uid, cid, next);
+ },
+ read: function(next) {
+ helpers.allowedTo('read', uid, cid, next);
+ },
+ isAdministrator: function(next) {
+ user.isAdministrator(uid, next);
+ },
+ isModerator: function(next) {
+ user.isModerator(uid, cid, next);
+ }
+ }, function(err, results) {
+ if(err) {
+ return callback(err);
+ }
+
+ var editable = results.isAdministrator || results.isModerator;
+
+ callback(null, {
+ meta: {
+ 'topics:create': results['topics:create'],
+ editable: editable,
+ view_deleted: editable,
+ read: results.read
+ }
+ });
+ });
+ };
+
+ privileges.categories.canRead = function(cid, uid, callback) {
+ helpers.some([
+ function(next) {
+ helpers.allowedTo('read', uid, cid, next);
+ },
+ function(next) {
+ user.isModerator(uid, cid, next);
+ },
+ function(next) {
+ user.isAdministrator(uid, next);
+ }
+ ], callback);
+ };
+
+ privileges.categories.canMoveAllTopics = function(currentCid, targetCid, uid, callback) {
+ async.parallel({
+ isAdministrator: function(next) {
+ user.isAdministrator(uid, next);
+ },
+ moderatorOfCurrent: function(next) {
+ user.isModerator(uid, currentCid, next);
+ },
+ moderatorOfTarget: function(next) {
+ user.isModerator(uid, targetCid, next);
+ }
+ }, function(err, results) {
+ if (err) {
+ return callback(err);
+ }
+
+ callback(null, results.isAdministrator || (results.moderatorOfCurrent && results.moderatorOfTarget));
+ });
+ };
+
+ privileges.categories.userPrivileges = function(cid, uid, callback) {
+ async.parallel({
+ read: function(next) {
+ helpers.isMember(groups.isMember, 'cid:' + cid + ':privileges:read', uid, next);
+ },
+ 'topics:create': function(next) {
+ helpers.isMember(groups.isMember, 'cid:' + cid + ':privileges:topics:create', uid, next);
+ },
+ 'topics:reply': function(next) {
+ helpers.isMember(groups.isMember, 'cid:' + cid + ':privileges:topics:reply', uid, next);
+ },
+ mods: function(next) {
+ user.isModerator(uid, cid, next);
+ }
+ }, callback);
+ };
+
+ privileges.categories.groupPrivileges = function(cid, groupName, callback) {
+ async.parallel({
+ 'groups:read': function(next) {
+ helpers.isMember(groups.isMember, 'cid:' + cid + ':privileges:groups:read', groupName, function(err, isMember){
+ next(err, !!isMember);
+ });
+ },
+ 'groups:topics:create': function(next) {
+ helpers.isMember(groups.isMember, 'cid:' + cid + ':privileges:groups:topics:create', groupName, function(err, isMember){
+ next(err, !!isMember);
+ });
+ },
+ 'groups:topics:reply': function(next) {
+ helpers.isMember(groups.isMember, 'cid:' + cid + ':privileges:groups:topics:reply', groupName, function(err, isMember){
+ next(err, !!isMember);
+ });
+ }
+ }, callback);
+ };
+
+};
diff --git a/src/privileges/helpers.js b/src/privileges/helpers.js
index f57fcc73fd..2bf2f49226 100644
--- a/src/privileges/helpers.js
+++ b/src/privileges/helpers.js
@@ -10,6 +10,16 @@ var async = require('async'),
var helpers = {};
+helpers.some = function(tasks, callback) {
+ async.some(tasks, function(task, next) {
+ task(function(err, result) {
+ next(!err && result);
+ });
+ }, function(result) {
+ callback(null, result);
+ });
+};
+
helpers.allowedTo = function(privilege, uid, cid, callback) {
categories.getCategoryField(cid, 'disabled', function(err, disabled) {
if (err) {
@@ -22,31 +32,32 @@ helpers.allowedTo = function(privilege, uid, cid, callback) {
async.parallel({
hasUserPrivilege: function(next) {
- hasPrivilege(groups.isMember, 'cid:' + cid + ':privileges:' + privilege, uid, next);
+ helpers.isMember(groups.isMember, 'cid:' + cid + ':privileges:' + privilege, uid, next);
},
hasGroupPrivilege: function(next) {
- hasPrivilege(groups.isMemberOfGroupList, 'cid:' + cid + ':privileges:groups:' + privilege, uid, next);
+ helpers.isMember(groups.isMemberOfGroupList, 'cid:' + cid + ':privileges:groups:' + privilege, uid, next);
},
}, function(err, results) {
if (err) {
return callback(err);
}
- callback(null, results.hasUserPrivilege && results.hasGroupPrivilege);
+
+ callback(null, (results.hasUserPrivilege === null && results.hasGroupPrivilege === null) || results.hasUserPrivilege || results.hasGroupPrivilege);
});
});
};
-function hasPrivilege(method, group, uid, callback) {
+helpers.isMember = function(method, group, uid, callback) {
groups.exists(group, function(err, exists) {
if (err) {
return callback(err);
}
if (!exists) {
- return callback(null, true);
+ return callback(null, null);
}
- method(group, uid, callback);
+ method(uid, group, callback);
});
}
diff --git a/src/privileges/posts.js b/src/privileges/posts.js
index 8894dd1509..d3501f218c 100644
--- a/src/privileges/posts.js
+++ b/src/privileges/posts.js
@@ -59,28 +59,12 @@ module.exports = function(privileges) {
return callback(err);
}
- async.some([
- function(next) {
- helpers.allowedTo('read', uid, cid, next);
- },
- function(next) {
- user.isModerator(uid, cid, next);
- },
- function(next) {
- user.isAdministrator(uid, next);
- }
- ], function(task, next) {
- task(function(err, result) {
- next(!err && result);
- });
- }, function(result) {
- callback(null, result);
- });
+ privileges.categories.canRead(cid, uid, callback);
});
};
privileges.posts.canEdit = function(pid, uid, callback) {
- async.some([
+ helpers.some([
function(next) {
posts.isOwner(pid, uid, next);
},
@@ -101,12 +85,22 @@ module.exports = function(privileges) {
function(next) {
user.isAdministrator(uid, next);
}
- ], function(task, next) {
- task(function(err, result) {
- next(!err && result);
- });
- }, function(result) {
- callback(null, result);
- });
+ ], callback);
+ };
+
+ privileges.posts.canMove = function(pid, uid, callback) {
+ helpers.some([
+ function(next) {
+ posts.getCidByPid(pid, function(err, cid) {
+ if (err) {
+ return next(err);
+ }
+ user.isModerator(uid, cid, next);
+ });
+ },
+ function(next) {
+ user.isAdministrator(uid, next);
+ }
+ ], callback);
};
};
diff --git a/src/privileges/topics.js b/src/privileges/topics.js
new file mode 100644
index 0000000000..7c82452f93
--- /dev/null
+++ b/src/privileges/topics.js
@@ -0,0 +1,136 @@
+
+'use strict';
+
+var async = require('async'),
+
+ topics = require('../topics'),
+ user = require('../user'),
+ helpers = require('./helpers'),
+ groups = require('../groups'),
+ categories = require('../categories');
+
+module.exports = function(privileges) {
+
+ privileges.topics = {};
+
+ privileges.topics.get = function(tid, uid, callback) {
+
+ topics.getTopicField(tid, 'cid', function(err, cid) {
+ if (err) {
+ return callback(err);
+ }
+
+ async.parallel({
+ 'topics:reply': function(next) {
+ helpers.allowedTo('topics:reply', uid, cid, next);
+ },
+ read: function(next) {
+ helpers.allowedTo('read', uid, cid, next);
+ },
+ manage_topic: function(next) {
+ helpers.hasEnoughReputationFor('privileges:manage_topic', uid, next);
+ },
+ isAdministrator: function(next) {
+ user.isAdministrator(uid, next);
+ },
+ isModerator: function(next) {
+ user.isModerator(uid, cid, next);
+ }
+ }, function(err, results) {
+ if(err) {
+ return callback(err);
+ }
+
+ var editable = results.isAdministrator || results.isModerator || results.manage_topic;
+
+ callback(null, {
+ meta: {
+ 'topics:reply': results['topics:reply'],
+ editable: editable,
+ view_deleted: editable,
+ read: results.read
+ }
+ });
+ });
+ });
+ };
+
+ privileges.topics.canRead = function(tid, uid, callback) {
+ topics.getTopicField(tid, 'cid', function(err, cid) {
+ if (err) {
+ return callback(err);
+ }
+
+ privileges.categories.canRead(cid, uid, callback);
+ });
+ };
+
+ privileges.topics.canCreate = function(cid, uid, callback) {
+ helpers.some([
+ function(next) {
+ helpers.allowedTo('topics:create', uid, cid, next);
+ },
+ function(next) {
+ user.isModerator(uid, cid, next);
+ },
+ function(next) {
+ user.isAdministrator(uid, next);
+ }
+ ], callback);
+ };
+
+ privileges.topics.canReply = function(tid, uid, callback) {
+ topics.getTopicField(tid, 'cid', function(err, cid) {
+ if (err) {
+ return callback(err);
+ }
+
+ helpers.some([
+ function(next) {
+ helpers.allowedTo('topics:reply', uid, cid, next);
+ },
+ function(next) {
+ user.isModerator(uid, cid, next);
+ },
+ function(next) {
+ user.isAdministrator(uid, next);
+ }
+ ], callback);
+ });
+ };
+
+ privileges.topics.canEdit = function(tid, uid, callback) {
+ helpers.some([
+ function(next) {
+ helpers.hasEnoughReputationFor('privileges:manage_topic', uid, next);
+ },
+ function(next) {
+ topics.getTopicField(tid, 'cid', function(err, cid) {
+ if (err) {
+ return next(err);
+ }
+ user.isModerator(uid, cid, next);
+ });
+ },
+ function(next) {
+ user.isAdministrator(uid, next);
+ }
+ ], callback);
+ };
+
+ privileges.topics.canMove = function(tid, uid, callback) {
+ helpers.some([
+ function(next) {
+ topics.getTopicField(tid, 'cid', function(err, cid) {
+ if (err) {
+ return next(err);
+ }
+ user.isModerator(uid, cid, next);
+ });
+ },
+ function(next) {
+ user.isAdministrator(uid, next);
+ }
+ ], callback);
+ };
+};
diff --git a/src/routes/debug.js b/src/routes/debug.js
index 6d6641615f..2534576d20 100644
--- a/src/routes/debug.js
+++ b/src/routes/debug.js
@@ -54,11 +54,10 @@ module.exports = function(app, middleware, controllers) {
});
app.get('/test', function(req, res) {
- var tools = require('../categoryTools');
- tools.privileges(1, 2, function() {
- console.log(arguments);
- })
- res.send(200);
+ var privileges = require('../privileges');
+ privileges.topics.get(1299, 1, function(err, result) {
+ res.json(result);
+ });
});
});
};
diff --git a/src/routes/feeds.js b/src/routes/feeds.js
index b42db1b3b3..3b1fc298ef 100644
--- a/src/routes/feeds.js
+++ b/src/routes/feeds.js
@@ -7,30 +7,29 @@ var posts = require('./../posts'),
rss = require('rss'),
nconf = require('nconf'),
- ThreadTools = require('./../threadTools'),
- CategoryTools = require('./../categoryTools');
+ privileges = require('../privileges');
function hasTopicPrivileges(req, res, next) {
var tid = req.params.topic_id;
- hasPrivileges(ThreadTools, tid, req, res, next);
+ hasPrivileges(privileges.topics.canRead, tid, req, res, next);
}
function hasCategoryPrivileges(req, res, next) {
var cid = req.params.category_id;
- hasPrivileges(CategoryTools, cid, req, res, next);
+ hasPrivileges(privileges.categories.canRead, cid, req, res, next);
}
-function hasPrivileges(module, id, req, res, next) {
+function hasPrivileges(method, id, req, res, next) {
var uid = req.user ? req.user.uid || 0 : 0;
- module.privileges(id, uid, function(err, privileges) {
- if(err) {
+ method(id, uid, function(err, canRead) {
+ if (err) {
return next(err);
}
- if(!privileges.read) {
+ if (!canRead) {
return res.redirect('403');
}
@@ -66,7 +65,7 @@ function generateForTopic(req, res, next) {
}
topicData.posts.forEach(function(postData) {
- if (parseInt(postData.deleted, 10) === 0) {
+ if (!postData.deleted) {
dateStamp = new Date(parseInt(parseInt(postData.edited, 10) === 0 ? postData.timestamp : postData.edited, 10)).toUTCString();
feed.item({
diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js
index 9a03319baa..aa653d516f 100644
--- a/src/socket.io/admin.js
+++ b/src/socket.io/admin.js
@@ -7,7 +7,6 @@ var groups = require('../groups'),
user = require('../user'),
topics = require('../topics'),
categories = require('../categories'),
- CategoryTools = require('../categoryTools'),
logger = require('../logger'),
events = require('../events'),
db = require('../database'),
diff --git a/src/socket.io/admin/categories.js b/src/socket.io/admin/categories.js
index dba2efca97..4c70599e14 100644
--- a/src/socket.io/admin/categories.js
+++ b/src/socket.io/admin/categories.js
@@ -3,7 +3,7 @@
var groups = require('../../groups'),
user = require('../../user'),
categories = require('../../categories'),
- categoryTools = require('../../categoryTools'),
+ privileges = require('../../privileges'),
async = require('async'),
Categories = {};
@@ -32,8 +32,12 @@ Categories.search = function(socket, data, callback) {
cid = data.cid;
user.search(username, function(err, data) {
+ if (err) {
+ return callback(err);
+ }
+
async.map(data.users, function(userObj, next) {
- categoryTools.privileges(cid, userObj.uid, function(err, privileges) {
+ privileges.categories.userPrivileges(cid, userObj.uid, function(err, privileges) {
if(err) {
return next(err);
}
@@ -50,22 +54,7 @@ Categories.setPrivilege = function(socket, data, callback) {
return callback(new Error('[[error:invalid-data]]'));
}
- var cid = data.cid,
- uid = data.uid,
- privilege = data.privilege,
- set = data.set,
- cb = function(err) {
- if(err) {
- return callback(err);
- }
- categoryTools.privileges(cid, uid, callback);
- };
-
- if (set) {
- groups.join('cid:' + cid + ':privileges:' + privilege, uid, cb);
- } else {
- groups.leave('cid:' + cid + ':privileges:' + privilege, uid, cb);
- }
+ groups[data.set ? 'join' : 'leave']('cid:' + data.cid + ':privileges:' + data.privilege, data.uid, callback);
};
Categories.getPrivilegeSettings = function(socket, cid, callback) {
@@ -119,7 +108,7 @@ Categories.groupsList = function(socket, cid, callback) {
}
async.map(data, function(groupObj, next) {
- categoryTools.groupPrivileges(cid, groupObj.name, function(err, privileges) {
+ privileges.categories.groupPrivileges(cid, groupObj.name, function(err, privileges) {
if(err) {
return next(err);
}
diff --git a/src/socket.io/categories.js b/src/socket.io/categories.js
index a3ca8abb37..117640793d 100644
--- a/src/socket.io/categories.js
+++ b/src/socket.io/categories.js
@@ -2,19 +2,19 @@
var async = require('async'),
categories = require('../categories'),
- categoryTools = require('../categoryTools'),
+ privileges = require('../privileges'),
meta = require('./../meta'),
user = require('./../user'),
SocketCategories = {};
SocketCategories.getRecentReplies = function(socket, cid, callback) {
- categoryTools.privileges(cid, socket.uid, function(err, privileges) {
+ privileges.categories.canRead(cid, socket.uid, function(err, canRead) {
if (err) {
return callback(err);
}
- if (!privileges || !privileges.meta.read) {
+ if (!canRead) {
return callback(null, []);
}
@@ -33,7 +33,7 @@ SocketCategories.loadMore = function(socket, data, callback) {
async.parallel({
privileges: function(next) {
- categoryTools.privileges(data.cid, socket.uid, next);
+ privileges.categories.get(data.cid, socket.uid, next);
},
settings: function(next) {
user.getSettings(socket.uid, next);
diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js
index 278a1ab2d3..1d240140f6 100644
--- a/src/socket.io/posts.js
+++ b/src/socket.io/posts.js
@@ -37,8 +37,15 @@ SocketPosts.reply = function(socket, data, callback) {
}
if (postData) {
+ var privileges = {
+ meta : {
+ 'topics:reply': true
+ }
+ };
+
websockets.server.sockets.emit('event:new_post', {
- posts: [postData]
+ posts: [postData],
+ privileges: privileges
});
module.parent.exports.emitTopicPostStats();
diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js
index 7971635410..d0cf8e716e 100644
--- a/src/socket.io/topics.js
+++ b/src/socket.io/topics.js
@@ -3,8 +3,8 @@
var topics = require('../topics'),
categories = require('../categories'),
+ privileges = require('../privileges'),
threadTools = require('../threadTools'),
- categoryTools = require('../categoryTools'),
websockets = require('./index'),
user = require('../user'),
db = require('./../database'),
@@ -181,12 +181,12 @@ function doTopicAction(action, socket, tids, callback) {
}
async.each(tids, function(tid, next) {
- threadTools.privileges(tid, socket.uid, function(err, privileges) {
+ privileges.topics.canEdit(tid, socket.uid, function(err, canEdit) {
if(err) {
return next(err);
}
- if(!privileges || !privileges.meta.editable) {
+ if(!canEdit) {
return next(new Error('[[error:no-privileges]]'));
}
@@ -210,21 +210,17 @@ SocketTopics.createTopicFromPosts = function(socket, data, callback) {
};
SocketTopics.movePost = function(socket, data, callback) {
- if(!socket.uid) {
+ if (!socket.uid) {
return callback(new Error('[[error:not-logged-in]]'));
}
- if(!data || !data.pid || !data.tid) {
+ if (!data || !data.pid || !data.tid) {
return callback(new Error('[[error:invalid-data]]'));
}
- threadTools.privileges(data.tid, socket.uid, function(err, privileges) {
- if(err) {
- return callback(err);
- }
-
- if(!(privileges.admin || privileges.moderator)) {
- return callback(new Error('[[error:no-privileges]]'));
+ privileges.posts.canMove(data.tid, socket.uid, function(err, canMove) {
+ if (err || !canMove) {
+ return callback(err || new Error('[[error:no-privileges]]'));
}
topics.movePostToTopic(data.pid, data.tid, callback);
@@ -240,10 +236,10 @@ SocketTopics.move = function(socket, data, callback) {
var oldCid;
async.waterfall([
function(next) {
- threadTools.privileges(tid, socket.uid, next);
+ privileges.topics.canMove(tid, socket.uid, next);
},
- function(privileges, next) {
- if(!(privileges.admin || privileges.moderator)) {
+ function(canMove, next) {
+ if (!canMove) {
return next(new Error('[[error:no-privileges]]'));
}
next();
@@ -278,20 +274,9 @@ SocketTopics.moveAll = function(socket, data, callback) {
return callback(new Error('[[error:invalid-data]]'));
}
- async.parallel({
- from: function(next) {
- categoryTools.privileges(data.currentCid, socket.uid, next);
- },
- to: function(next) {
- categoryTools.privileges(data.cid, socket.uid, next);
- }
- }, function(err, results) {
- if (err) {
- return callback(err);
- }
-
- if (!results.from.admin && (!results.from.mods || !results.to.mods)) {
- return callback(new Error('[[error:no-privileges]]'));
+ privileges.categories.canMoveAllTopics(data.currentCid, data.cid, data.uid, function(err, canMove) {
+ if (err || canMove) {
+ return callback(err || new Error('[[error:no-privileges]]'));
}
categories.getTopicIds(data.currentCid, 0, -1, function(err, tids) {
@@ -336,7 +321,7 @@ SocketTopics.loadMore = function(socket, data, callback) {
topics.getTopicPosts(data.tid, start, end, socket.uid, false, next);
},
privileges: function(next) {
- threadTools.privileges(data.tid, socket.uid, next);
+ privileges.topics.get(data.tid, socket.uid, next);
}
}, callback);
});
diff --git a/src/threadTools.js b/src/threadTools.js
index 72d5a5d13e..c476e461f5 100644
--- a/src/threadTools.js
+++ b/src/threadTools.js
@@ -7,7 +7,6 @@ var winston = require('winston'),
db = require('./database'),
topics = require('./topics'),
categories = require('./categories'),
- CategoryTools = require('./categoryTools'),
user = require('./user'),
notifications = require('./notifications'),
posts = require('./posts'),
@@ -23,38 +22,6 @@ var winston = require('winston'),
db.isSortedSetMember('topics:tid', tid, callback);
};
- ThreadTools.privileges = function(tid, uid, callback) {
- async.parallel({
- categoryPrivs: function(next) {
- topics.getTopicField(tid, 'cid', function(err, cid) {
- CategoryTools.privileges(cid, uid, next);
- });
- },
- hasEnoughRep: function(next) {
- if (parseInt(meta.config['privileges:disabled'], 10)) {
- return next(null, false);
- } else {
- user.getUserField(uid, 'reputation', function(err, reputation) {
- if (err) {
- return next(null, false);
- }
- next(null, parseInt(reputation, 10) >= parseInt(meta.config['privileges:manage_topic'], 10));
- });
- }
- }
- }, function(err, results) {
- if (err) {
- return callback(err);
- }
-
- var privileges = results.categoryPrivs;
- privileges.meta.editable = privileges.meta.editable || results.hasEnoughRep;
- privileges.meta.view_deleted = privileges.meta.view_deleted || results.hasEnoughRep;
-
- callback(null, privileges);
- });
- };
-
ThreadTools.delete = function(tid, uid, callback) {
toggleDelete(tid, uid, true, callback);
};
diff --git a/src/topics.js b/src/topics.js
index 6f31c817ca..0763a13a0c 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -10,8 +10,7 @@ var async = require('async'),
plugins = require('./plugins'),
user = require('./user'),
categories = require('./categories'),
- categoryTools = require('./categoryTools'),
- threadTools = require('./threadTools');
+ privileges = require('./privileges');
(function(Topics) {
@@ -22,7 +21,6 @@ var async = require('async'),
require('./topics/posts')(Topics);
require('./topics/follow')(Topics);
-
Topics.getTopicData = function(tid, callback) {
Topics.getTopicsData([tid], function(err, topics) {
if (err) {
@@ -132,8 +130,8 @@ var async = require('async'),
}
async.filter(tids, function(tid, next) {
- threadTools.privileges(tid, uid, function(err, privileges) {
- next(!err && privileges.meta.read);
+ privileges.topics.canRead(tid, uid, function(err, canRead) {
+ next(!err && canRead);
});
}, function(tids) {
Topics.getTopicsByTids(tids, uid, function(err, topicData) {
@@ -199,7 +197,7 @@ var async = require('async'),
if (privilegeCache[topicData.cid]) {
return next(null, privilegeCache[topicData.cid]);
}
- categoryTools.privileges(topicData.cid, uid, next);
+ privileges.categories.get(topicData.cid, uid, next);
},
categoryData: function(next) {
if (categoryCache[topicData.cid]) {
@@ -269,9 +267,6 @@ var async = require('async'),
posts: function(next) {
Topics.getTopicPosts(tid, start, end, uid, false, next);
},
- privileges: function(next) {
- threadTools.privileges(tid, uid, next);
- },
category: function(next) {
Topics.getCategoryData(tid, next);
},
@@ -291,7 +286,6 @@ var async = require('async'),
topicData.thread_tools = results.threadTools;
topicData.pageCount = results.pageCount;
topicData.unreplied = parseInt(topicData.postcount, 10) === 1;
- topicData.expose_tools = results.privileges.meta.editable ? 1 : 0;
callback(null, topicData);
});
diff --git a/src/topics/create.js b/src/topics/create.js
index 454ea14d28..e47216de08 100644
--- a/src/topics/create.js
+++ b/src/topics/create.js
@@ -2,14 +2,15 @@
'use strict';
var async = require('async'),
- db = require('./../database'),
- utils = require('./../../public/src/utils'),
- plugins = require('./../plugins'),
- user = require('./../user'),
- meta = require('./../meta'),
- posts = require('./../posts'),
- threadTools = require('./../threadTools'),
- categoryTools = require('./../categoryTools');
+ db = require('../database'),
+ utils = require('../../public/src/utils'),
+ plugins = require('../plugins'),
+ user = require('../user'),
+ meta = require('../meta'),
+ posts = require('../posts'),
+ threadTools = require('../threadTools'),
+ privileges = require('../privileges'),
+ categories = require('../categories');
module.exports = function(Topics) {
@@ -92,16 +93,16 @@ module.exports = function(Topics) {
});
},
function(next) {
- categoryTools.exists(cid, next);
+ categories.exists(cid, next);
},
function(categoryExists, next) {
if (!categoryExists) {
return next(new Error('[[error:no-category]]'));
}
- categoryTools.privileges(cid, uid, next);
+ privileges.topics.canCreate(cid, uid, next);
},
- function(privileges, next) {
- if(!privileges.meta['topics:create']) {
+ function(canCreate, next) {
+ if(!canCreate) {
return next(new Error('[[error:no-privileges]]'));
}
next();
@@ -144,7 +145,6 @@ module.exports = function(Topics) {
uid = data.uid,
toPid = data.toPid,
content = data.content,
- privileges,
postData;
async.waterfall([
@@ -173,11 +173,10 @@ module.exports = function(Topics) {
return next(new Error('[[error:topic-locked]]'));
}
- threadTools.privileges(tid, uid, next);
+ privileges.topics.canReply(tid, uid, next);
},
- function(privilegesData, next) {
- privileges = privilegesData;
- if (!privileges.meta['topics:reply']) {
+ function(canReply, next) {
+ if (!canReply) {
return next(new Error('[[error:no-privileges]]'));
}
next();
@@ -232,7 +231,8 @@ module.exports = function(Topics) {
postData.favourited = false;
postData.votes = 0;
postData.display_moderator_tools = true;
- postData.display_move_tools = privileges.admin || privileges.moderator;
+ postData.display_move_tools = true;
+ postData.selfPost = false;
postData.relativeTime = utils.toISOString(postData.timestamp);
next(null, postData);
diff --git a/src/topics/unread.js b/src/topics/unread.js
index 375d921921..4f153cf227 100644
--- a/src/topics/unread.js
+++ b/src/topics/unread.js
@@ -8,7 +8,7 @@ var async = require('async'),
user = require('./../user'),
notifications = require('./../notifications'),
categories = require('./../categories'),
- threadTools = require('./../threadTools');
+ privileges = require('../privileges');
module.exports = function(Topics) {
@@ -49,8 +49,8 @@ module.exports = function(Topics) {
});
async.filter(newtids, function(tid, next) {
- threadTools.privileges(tid, uid, function(err, privileges) {
- next(!err && privileges.meta.read);
+ privileges.topics.canRead(tid, uid, function(err, canRead) {
+ next(!err && canRead);
});
}, function(newtids) {
unreadTids.push.apply(unreadTids, newtids);