diff --git a/app/assets/javascripts/discourse/controllers/create_account_controller.js b/app/assets/javascripts/discourse/controllers/create_account_controller.js index d409757d621..6babd49d80a 100644 --- a/app/assets/javascripts/discourse/controllers/create_account_controller.js +++ b/app/assets/javascripts/discourse/controllers/create_account_controller.js @@ -31,7 +31,7 @@ Discourse.CreateAccountController = Discourse.Controller.extend(Discourse.ModalF }.property('authOptions.auth_provider'), passwordInstructions: function() { - return I18n.t('user.password.instructions', {count: 6}); // TODO: soon to be configurable + return I18n.t('user.password.instructions', {count: Discourse.SiteSettings.min_password_length}); }.property(), // Validate the name @@ -273,7 +273,7 @@ Discourse.CreateAccountController = Discourse.Controller.extend(Discourse.ModalF } // If too short - if (password.length < 6) { + if (password.length < Discourse.SiteSettings.min_password_length) { return Discourse.InputValidation.create({ failed: true, reason: I18n.t('user.password.too_short') diff --git a/app/assets/javascripts/discourse/dialects/dialect.js b/app/assets/javascripts/discourse/dialects/dialect.js index ebf09e3d3fb..f7c53f1a66a 100644 --- a/app/assets/javascripts/discourse/dialects/dialect.js +++ b/app/assets/javascripts/discourse/dialects/dialect.js @@ -17,7 +17,6 @@ var parser = window.BetterMarkdown, @method initializeDialects **/ function initializeDialects() { - Discourse.Dialect.trigger('register', {dialect: dialect, MD: MD}); MD.buildBlockOrder(dialect.block); MD.buildInlinePatterns(dialect.inline); initialized = true; diff --git a/app/assets/javascripts/discourse/routes/topic_route.js b/app/assets/javascripts/discourse/routes/topic_route.js index 5e922405387..8c5b68e0ce5 100644 --- a/app/assets/javascripts/discourse/routes/topic_route.js +++ b/app/assets/javascripts/discourse/routes/topic_route.js @@ -128,10 +128,13 @@ Discourse.TopicRoute = Discourse.Route.extend({ editingTopic: false }); + Discourse.TopicRoute.trigger('setupTopicController', this); + this.controllerFor('header').setProperties({ topic: model, showExtraInfo: false }); + this.controllerFor('composer').set('topic', model); Discourse.TopicTrackingState.current().trackIncoming('all'); controller.subscribe(); @@ -142,4 +145,4 @@ Discourse.TopicRoute = Discourse.Route.extend({ }); - +RSVP.EventTarget.mixin(Discourse.TopicRoute); diff --git a/app/assets/javascripts/discourse/templates/user/user.js.handlebars b/app/assets/javascripts/discourse/templates/user/user.js.handlebars index 2181c36a811..7cc6818c731 100644 --- a/app/assets/javascripts/discourse/templates/user/user.js.handlebars +++ b/app/assets/javascripts/discourse/templates/user/user.js.handlebars @@ -41,7 +41,7 @@
<%= t('author_wrote', author: link_to(topic.user.name, userpage_url(topic.user))).html_safe %>
<%= topic.posts.first.cooked.html_safe %>diff --git a/app/views/topics/show.rss.erb b/app/views/topics/show.rss.erb index 85d1e2e5750..61be3b86ada 100644 --- a/app/views/topics/show.rss.erb +++ b/app/views/topics/show.rss.erb @@ -3,23 +3,22 @@
<%= t('author_wrote', author: link_to(post.user.name, post.user)).html_safe %>
+<%= t('author_wrote', author: link_to(post.user.name, userpage_url(post.user))).html_safe %>
<%= post.cooked.html_safe %>diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml index ac2cd0f6664..0eedf418233 100644 --- a/config/locales/client.ru.yml +++ b/config/locales/client.ru.yml @@ -197,7 +197,7 @@ ru: you_replied_to_topic: 'Вы ответили в теме' user_mentioned_user: '{{user}} упомянул {{another_user}}' user_mentioned_you: '{{user}} упомянулВас' - you_mentioned_user: 'Вы упомянули{{another_user}}' + you_mentioned_user: 'Вы упомянули {{another_user}}' posted_by_user: 'Размещено пользователем {{user}}' posted_by_you: 'Размещено Вами' sent_by_user: 'Отправлено пользователем {{user}}' @@ -225,6 +225,8 @@ ru: latest_by: 'последние по' toggle_ordering: 'изменить сортировку' subcategories: 'Подкатегории:' + total_topics: 'Всего тем: %{count}' + total_posts: 'Всего сообщений: %{count}' user: said: '{{username}} писал(а):' profile: Профайл @@ -393,12 +395,14 @@ ru: month_desc: 'создано тем за последние 30 дней' week: неделя week_desc: 'создано тем за последние 7 дней' + day: день first_post: 'Первое сообщение' mute: Отключить unmute: Включить summary: enabled_description: 'Вы просматриваете только популярные сообщения в данной теме. Для просмотра всех сообщений нажмите кнопку ниже.' description: 'В теме {{count}} сообщений. Хотите посмотреть только сообщения релевантные теме?' + description_time: 'В теме {{count}} сообщений со средним временем чтения {{readingTime}} минут. Сократить время чтения, отобразив только важные сообщения?' enable: 'Сводка по теме' disable: 'Показать все сообщения' private_message_info: @@ -520,8 +524,10 @@ ru: help: 'Справка по Markdown' toggler: 'скрыть / показать панель редактирования' admin_options_title: 'Дополнительные настройки темы' - auto_close_label: 'Автоматически закрыть тему после:' - auto_close_units: дней + auto_close_label: 'Автоматически закрыть тему:' + auto_close_units: '(# часов, время, или штамп времени)' + auto_close_examples: 'Например: 24, 17:00, 2013-11-22 14:00' + auto_close_error: 'Пожалуйста, введите правильное значение' notifications: title: 'уведомления об упоминании @name в сообщениях, ответах на ваши сообщения и темы, личные сообщения и т.д.' none: 'На данный момент уведомлений нет.' @@ -534,8 +540,8 @@ ru: liked: ' {{username}} {{link}}' private_message: ' {{username}} {{link}}' invited_to_private_message: ' {{username}} {{link}}' - invitee_accepted: ' {{username}} принял ваше приглашение' - moved_post: ' {{username}} переместил сообщение в {{link}}' + invitee_accepted: ' {{username}} принял ваше приглашение' + moved_post: ' {{username}} перемещено {{link}}' total_flagged: 'всего сообщений с жалобами' upload_selector: title: 'Add an image' @@ -653,8 +659,10 @@ ru: title: 'текущее местоположение в теме' jump_top: 'перейти к первому сообщению' jump_bottom: 'перейти к последнему сообщению' + jump_bottom_with_number: 'перейти к сообщению %{post_number}' total: 'всего сообщений' current: 'текущее сообщение' + position: '%{current} сообщение из %{total}' notifications: title: ' ' reasons: @@ -778,6 +786,11 @@ ru: many: '(сообщение отозвано автором и будет автоматически удалено через %{count} часов при отсутствии жалоб)' deleted_by: 'Удалено' expand_collapse: развернуть/свернуть + gap: + one: '1 сообщение пропущено' + other: '{{count}} сообщений пропущено' + few: '{{count}} сообщения пропущено' + many: '{{count}} сообщений пропущено' has_replies: one: ответ other: ответов @@ -949,6 +962,25 @@ ru: other: 'Вы уверены, что хотите удалить эти сообщения?' few: 'Вы уверены, что хотите удалить сообщения?' many: 'Вы уверены, что хотите удалить сообщения?' + revisions: + controls: + first: 'Начальная версия' + previous: 'Предыдущая версия' + next: 'Следующая версия' + last: 'Последняя версия' + comparing_previous_to_current_out_of_total: '#{{previous}} vs. #{{current}} (из {{total}})' + displays: + inline: + title: 'Отобразить сообщение с включенными добавлениями и удалениями.' + button: ' HTML' + side_by_side: + title: 'Отобразить сообщение с построчными изменениями' + button: ' HTML' + side_by_side_markdown: + title: 'Отобразить вывод с построчными изменениями и разметкой' + button: ' Markdown' + details: + edited_by: 'Изменено' category: can: 'может… ' none: '(без категории)' @@ -981,6 +1013,7 @@ ru: already_used: 'Цвет уже используется другой категорией' security: Безопасность auto_close_label: 'Закрыть тему через:' + auto_close_units: часов edit_permissions: 'Изменить права доступа' add_permission: 'Добавить права' this_year: 'в год' @@ -1135,6 +1168,7 @@ ru: disagree_title: 'Удалить все жалобы с данного сообщения' delete_spammer_title: 'Удалить пользователя и все его сообщения.' flagged_by: 'Отмечено' + system: Системные error: 'что-то пошло не так' view_message: Ответить no_results: 'Жалоб нет.' @@ -1430,3 +1464,5 @@ ru: rate_limits: 'Ограничения' developer: Разработчик uncategorized: Без категории + lightbox: + download: загрузить diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml index d5e753b19bd..710df0a442d 100644 --- a/config/locales/client.zh_CN.yml +++ b/config/locales/client.zh_CN.yml @@ -185,17 +185,26 @@ zh_CN: categories: all: "所有分类" - only_category: "只看{{categoryName}}" + all_subcategories: "所有子分类" + no_subcategory: "无子分类" category: "分类" - posts: "帖子" - topics: "主题" + posts: "新帖子" + topics: "新主题" latest: "最新" latest_by: "最新发表:" toggle_ordering: "排序控制" subcategories: "子分类:" + topic_stats: "新主题的数量。" + topic_stat_sentence: + one: "过去的%{unit}中有%{count}个新主题。" + other: "过去的%{unit}中有%{count}个新主题。" + post_stats: "新帖子的数量。" + post_stat_sentence: + one: "过去的%{unit}中有%{count}个新帖子。" + other: "过去的%{unit}中有%{count}个新帖子。" user: - said: "{{username}} 说:" + said: "{{username}}说:" profile: "个人简介" show_profile: "访问个人简介" mute: "防打扰" @@ -348,6 +357,7 @@ zh_CN: title: "密码" too_short: "你设置的密码太短了。" ok: "你设置的密码符合要求。" + instructions: "至少需要%{count}个字符。" ip_address: title: "最后使用的IP地址" @@ -375,6 +385,7 @@ zh_CN: month_desc: '30天以前发表的主题' week: '周' week_desc: '7天以前发表的主题' + day: '天' first_post: 第一帖 mute: 防打扰 @@ -1003,6 +1014,7 @@ zh_CN: add_permission: "添加权限" this_year: "今年" position: "位置" + default_position: "默认位置" parent: "上级分类" flagging: diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index de9d54678a0..97350bcf521 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -27,6 +27,8 @@ en: site_under_maintenance: 'Site is currently undergoing maintenance.' operation_already_running: "An %{operation} is currently running. Can't start a new %{operation} job right now." + too_many_replies: "Sorry you can't reply any more times in that topic." + too_many_mentions: zero: "Sorry, you can't mention other users." one: "Sorry, you can only mention one other user in a post." @@ -132,6 +134,13 @@ en: Are you sure you're providing adequate time for other people to share their points of view, too? + too_many_replies: | + ### You have reached the reply limit + + We're sorry, but new users are temporarily limited to %{newuser_max_replies_per_topic} replies in a single topic. + + Instead of adding another reply, please consider editing your previous replies. + activerecord: attributes: category: @@ -594,6 +603,7 @@ en: login_required: "Require authentication to read posts" + min_password_length: "Minimum password length." enable_local_logins: "Enable traditional, local username and password authentication" enable_local_account_create: "Enable creating new local accounts" enable_google_logins: "Enable Google authentication" @@ -667,6 +677,7 @@ en: newuser_max_images: "How many images a new user can add to a post" newuser_max_attachments: "How many attachments a new user can add to a post" newuser_max_mentions_per_post: "Maximum number of @name notifications a new user can use in a post" + newuser_max_replies_per_topic: "Maximum number of replies a new user can make in a single topic" max_mentions_per_post: "Maximum number of @name notifications you can use in a post" create_thumbnails: "Create thumbnails for lightboxed images" diff --git a/config/locales/server.ru.yml b/config/locales/server.ru.yml index bb4c3c03f15..8e6787cc2d4 100644 --- a/config/locales/server.ru.yml +++ b/config/locales/server.ru.yml @@ -67,6 +67,9 @@ ru: rss_posts_in_topic: 'RSS лента темы ''%{topic}' rss_topics_in_category: 'RSS лента тем в категории ''%{category}''' author_wrote: '%{author} написал:' + num_posts: 'Сообщений:' + num_participants: 'Участников:' + read_full_topic: 'Читать всю тему' private_message_abbrev: ЛС rss_description: latest: 'Последние темы' @@ -85,7 +88,7 @@ ru: trust_level_5: trust_level_5 education: until_posts: - one: сообщение + one: '1 сообщение' other: '%{count} сообщений' few: '%{count} сообщения' many: '%{count} сообщений' @@ -510,7 +513,7 @@ ru: delete_removed_posts_after: 'Количество часов, после которого сообщение, удаленное пользователем, удаляется.' max_image_width: 'Максимальная ширина изображений, добавляемых в сообщение' max_image_height: 'Максимальная высота изображения в сообщении' - category_featured_topics: 'Количество отображаемых тем в категориях на странице /categories' + category_featured_topics: 'Количество тем, отображаемых в одной категории или на странице категорий. После изменения значения требуется около 15 минут для обновления списков.' add_rel_nofollow_to_user_content: 'Добавить "rel nofollow" для всех ссылок за исключением внутренних (включая родительский домен). Изменение данной настройки потребует обновления всех сообщений (
rake posts:rebake
)'
exclude_rel_nofollow_domains: 'Разделенный запятыми список доменов, в которых nofollow не добавлено (tld.com автоматически позволит также и sub.tld.com)'
post_excerpt_maxlength: 'Максимальное количество символов выдержки из сообщения'
@@ -602,6 +605,7 @@ ru:
suggested_topics: 'Количество рекомендованных тем, отображаемых внизу текущей темы'
clean_up_uploads: 'Удалить неиспользуемые загрузки для предотвращения хранения нелегального контента. ВНИМАНИЕ: рекомендуется сделать резервную копию директории /uploads перед включением данной настройки.'
clean_orphan_uploads_grace_period_hours: 'Период (в часах) после которого неопубликованные вложения удаляются.'
+ purge_deleted_uploads_grace_period_days: 'Период (в днях) после которого удаленные вложения очищаются.'
enable_s3_uploads: 'Размещать загруженные файлы на Amazon S3'
s3_upload_bucket: 'Наименование Amazon S3 bucket в который будут загружаться файлы. ВНИМАНИЕ: имя должно быть в нижнем регистре (см. http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html)'
s3_access_key_id: 'Amazon S3 access key для загрузки и хранения изображений'
@@ -637,7 +641,7 @@ ru:
min_title_similar_length: 'Минимальная длина названия темы, при которой тема будет проверена на наличие похожих'
min_body_similar_length: 'Минимальная длина тела сообщения, при которой оно будет проверено на наличие похожих тем'
category_colors: 'Разделенный чертой (|) список дозволенных hexadecimal цветов для категорий'
- enable_wide_category_list: 'Включить традиционный полноразмерный список категорий.'
+ enable_wide_category_list: 'Использовать обычный полноразмерный список категорий. ВНИМАНИЕ: на текущий момент данная настройка ничего не делает и в скором времени будет удалена. Полноразмерный список является единственно возможным.'
max_image_size_kb: 'Максимальный размер изображений для загрузки пользователем в КБ – убедитесь, что вы так же настроили лимит в nginx (client_max_body_size) / apache или прокси.'
max_attachment_size_kb: 'Максимальный размер файлов для загрузки пользователем в кб – убедитесь, что вы настроили лимит также в nginx (client_max_body_size) / apache или proxy.'
authorized_extensions: 'Список расширений файлов, разрешенных к загрузке, разделенный вертикальной чертой (|)'
@@ -675,6 +679,7 @@ ru:
enable_names: 'Разрешить пользователям отображать полные имена'
display_name_on_posts: 'Отображать полные имена пользователей в сообщениях'
invites_shown: 'Максимальное количество приглашений, отображаемых на странице пользователя'
+ short_progress_text_threshold: 'После достижения указанного числа сообщений в теме, бар будет отображать только текущий номер сообщения. Если вы измените ширину бара, вы можете изменить это значение.'
notification_types:
mentioned: '%{display_username} упомянул вас в %{link}'
liked: '%{display_username} понравилось ваше сообщение в теме %{link}'
@@ -711,12 +716,24 @@ ru:
archived_disabled: 'Эта тема разархивирована. Она более не заморожена, и может быть изменена.'
closed_enabled: 'Эта тема закрыта. В ней больше нельзя отвечать.'
closed_disabled: 'Эта тема открыта. В ней можно отвечать.'
- autoclosed_enabled:
+ autoclosed_enabled_days:
zero: 'Эта тема была автоматически закрыта через 1 день. В ней больше нельзя отвечать.'
one: 'Эта тема была автоматически закрыта через 1 день. В ней больше нельзя отвечать.'
- other: 'Эта тема была автоматически закрыта через %{count} дней. В ней больше нельзя отвечать.'
+ other: 'Эта тема была автоматически закрыта спустя %{count} дней. В ней больше нельзя отвечать.'
few: 'Эта тема была автоматически закрыта спустя %{count} дня. В ней больше нельзя отвечать.'
many: 'Эта тема была автоматически закрыта спустя %{count} дней. В ней больше нельзя отвечать.'
+ autoclosed_enabled_hours:
+ zero: 'Эта тема была автоматически закрыта через 1 час. В ней больше нельзя отвечать.'
+ one: 'Эта тема была автоматически закрыта через 1 час. В ней больше нельзя отвечать.'
+ other: 'Эта тема была автоматически закрыта спустя %{count} часов. В ней больше нельзя отвечать.'
+ few: 'Эта тема была автоматически закрыта спустя %{count} часа. В ней больше нельзя отвечать.'
+ many: 'Эта тема была автоматически закрыта спустя %{count} часов. В ней больше нельзя отвечать.'
+ autoclosed_enabled_minutes:
+ zero: 'Эта тема была автоматически закрыта через 1 минуту. В ней больше нельзя отвечать.'
+ one: 'Эта тема была автоматически закрыта через 1 минуту. В ней больше нельзя отвечать.'
+ other: 'Эта тема была автоматически закрыта спустя %{count} минут. В ней больше нельзя отвечать.'
+ few: 'Эта тема была автоматически закрыта спустя %{count} минуты. В ней больше нельзя отвечать.'
+ many: 'Эта тема была автоматически закрыта спустя %{count} минут. В ней больше нельзя отвечать.'
autoclosed_disabled: 'Эта тема открыта. В ней можно отвечать.'
pinned_enabled: 'Эта тема прилеплена. Она будет всегда отображаться первой в списке тем своей категории, пока не будет отлеплена модератором, или сброшена вниз, когда каждый пользователь нажмет кнопку «Отлепить»'
pinned_disabled: 'Эта тема отлеплена. Она больше не будет отображаться наверху списка тем категории.'
@@ -801,7 +818,7 @@ ru:
other: '%{count} пользователей ожидают подтверждения'
few: '%{count} пользователя ожидают подтверждения'
many: '%{count} пользователей ожидают подтверждения'
- text_body_template: "Новые пользователи ожидают подтверждения (или отказа).\n\n[Проверьте список в секции администрирования](/admin/users/list/pending).\n"
+ text_body_template: "Новые пользователи ожидают подтверждения (или отказа) перед тем, как они получат доступ до форума.\n\n[Проверьте список в секции администрирования](%{base_url}/admin/users/list/pending).\n"
unsubscribe_link: 'Для того, чтобы отписаться от подобных сообщений, перейдите в [настройки профиля](%{user_preferences_url}).'
user_notifications:
previous_discussion: 'Предыдущие ответы'
@@ -836,6 +853,11 @@ ru:
click_here: 'нажмите здесь'
from: 'Cводка новостей сайта %{site_name}'
read_more: 'Читать еще'
+ posts:
+ one: '1 сообщение'
+ other: '%{count} сообщений'
+ few: '%{count} сообщения'
+ many: '%{count} сообщений'
forgot_password:
subject_template: '[%{site_name}] Сброс пароля'
text_body_template: "Кто-то запросил смену вашего пароля на сайте [%{site_name}](%{base_url}).\n\nЕсли это были не вы, спокойно проигнорируйте это письмо.\n\nПройдите по следующей ссылке, чтобы задать новый пароль:\n%{base_url}/users/password-reset/%{email_token}\n"
diff --git a/config/routes.rb b/config/routes.rb
index d91aa179362..36fb3dbcf24 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -140,7 +140,7 @@ Discourse::Application.routes.draw do
get 'user_preferences' => 'users#user_preferences_redirect'
get 'users/:username/private-messages' => 'user_actions#private_messages', constraints: {username: USERNAME_ROUTE_FORMAT}
get 'users/:username/private-messages/:filter' => 'user_actions#private_messages', constraints: {username: USERNAME_ROUTE_FORMAT}
- get 'users/:username' => 'users#show', constraints: {username: USERNAME_ROUTE_FORMAT}
+ get 'users/:username' => 'users#show', as: 'userpage', constraints: {username: USERNAME_ROUTE_FORMAT}
put 'users/:username' => 'users#update', constraints: {username: USERNAME_ROUTE_FORMAT}
get 'users/:username/preferences' => 'users#preferences', constraints: {username: USERNAME_ROUTE_FORMAT}, as: :email_preferences
get 'users/:username/preferences/email' => 'users#preferences', constraints: {username: USERNAME_ROUTE_FORMAT}
diff --git a/config/site_settings.yml b/config/site_settings.yml
index 4b1bbe40cb4..280879454b3 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -76,6 +76,9 @@ users:
must_approve_users:
client: true
default: false
+ min_password_length:
+ client: true
+ default: 8
enable_google_logins:
client: true
default: true
@@ -177,6 +180,7 @@ posting:
default: true
post_undo_action_window_mins: 10
max_mentions_per_post: 10
+ newuser_max_replies_per_topic: 3
newuser_max_mentions_per_post: 2
onebox_max_chars: 5000
title_min_entropy: 10
diff --git a/lib/composer_messages_finder.rb b/lib/composer_messages_finder.rb
index c39731d820e..c7f27bcba05 100644
--- a/lib/composer_messages_finder.rb
+++ b/lib/composer_messages_finder.rb
@@ -7,6 +7,7 @@ class ComposerMessagesFinder
def find
check_education_message ||
+ check_new_user_many_replies ||
check_avatar_notification ||
check_sequential_replies ||
check_dominating_topic
@@ -32,6 +33,12 @@ class ComposerMessagesFinder
nil
end
+ # New users have a limited number of replies in a topic
+ def check_new_user_many_replies
+ return unless replying? && @user.posted_too_much_in_topic?(@details[:topic_id])
+ {templateName: 'composer/education', body: PrettyText.cook(I18n.t('education.too_many_replies', newuser_max_replies_per_topic: SiteSetting.newuser_max_replies_per_topic)) }
+ end
+
# Should a user be contacted to update their avatar?
def check_avatar_notification
diff --git a/lib/validators/password_validator.rb b/lib/validators/password_validator.rb
new file mode 100644
index 00000000000..b396cdf3f77
--- /dev/null
+++ b/lib/validators/password_validator.rb
@@ -0,0 +1,12 @@
+class PasswordValidator < ActiveModel::EachValidator
+
+ def validate_each(record, attribute, value)
+ return unless record.password_required?
+ if value.nil?
+ record.errors.add(attribute, :blank)
+ elsif value.length < SiteSetting.min_password_length
+ record.errors.add(attribute, :too_short, count: SiteSetting.min_password_length)
+ end
+ end
+
+end
diff --git a/lib/validators/post_validator.rb b/lib/validators/post_validator.rb
index 124d55058ec..5c5dab1efb0 100644
--- a/lib/validators/post_validator.rb
+++ b/lib/validators/post_validator.rb
@@ -5,6 +5,7 @@ class Validators::PostValidator < ActiveModel::Validator
presence(record)
stripped_length(record)
raw_quality(record)
+ max_posts_validator(record)
max_mention_validator(record)
max_images_validator(record)
max_attachments_validator(record)
@@ -40,6 +41,12 @@ class Validators::PostValidator < ActiveModel::Validator
end
end
+ def max_posts_validator(post)
+ if post.acting_user.present? && post.acting_user.posted_too_much_in_topic?(post.topic_id)
+ post.errors.add(:base, I18n.t(:too_many_replies))
+ end
+ end
+
# Ensure new users can not put too many images in a post
def max_images_validator(post)
add_error_if_count_exceeded(post, :too_many_images, post.image_count, SiteSetting.newuser_max_images) unless acting_user_is_trusted?(post)
diff --git a/spec/components/composer_messages_finder_spec.rb b/spec/components/composer_messages_finder_spec.rb
index df26e9be682..f211157a1fb 100644
--- a/spec/components/composer_messages_finder_spec.rb
+++ b/spec/components/composer_messages_finder_spec.rb
@@ -10,6 +10,7 @@ describe ComposerMessagesFinder do
it "calls all the message finders" do
finder.expects(:check_education_message).once
+ finder.expects(:check_new_user_many_replies).once
finder.expects(:check_avatar_notification).once
finder.expects(:check_sequential_replies).once
finder.expects(:check_dominating_topic).once
@@ -56,6 +57,24 @@ describe ComposerMessagesFinder do
finder.check_education_message.should be_blank
end
end
+ end
+
+ context '.check_new_user_many_replies' do
+ let(:user) { Fabricate.build(:user) }
+
+ context 'replying' do
+ let(:finder) { ComposerMessagesFinder.new(user, composerAction: 'reply') }
+
+ it "has no message when `posted_too_much_in_topic?` is false" do
+ user.expects(:posted_too_much_in_topic?).returns(false)
+ finder.check_new_user_many_replies.should be_blank
+ end
+
+ it "has a message when a user has posted too much" do
+ user.expects(:posted_too_much_in_topic?).returns(true)
+ finder.check_new_user_many_replies.should be_present
+ end
+ end
end
diff --git a/spec/components/validators/password_validator_spec.rb b/spec/components/validators/password_validator_spec.rb
new file mode 100644
index 00000000000..6d23ce1be9a
--- /dev/null
+++ b/spec/components/validators/password_validator_spec.rb
@@ -0,0 +1,60 @@
+require 'spec_helper'
+
+describe PasswordValidator do
+
+ let(:validator) { described_class.new({attributes: :password}) }
+ subject(:validate) { validator.validate_each(record,:password,@password) }
+
+ context "password required" do
+ let(:record) { u = Fabricate.build(:user, password: @password); u.password_required!; u }
+
+ context "min password length is 8" do
+ before { SiteSetting.stubs(:min_password_length).returns(8) }
+
+ it "doesn't add an error when password is good" do
+ @password = "weron235alsfn234"
+ validate
+ record.errors[:password].should_not be_present
+ end
+
+ it "adds an error when password is too short" do
+ @password = "p"
+ validate
+ record.errors[:password].should be_present
+ end
+
+ it "adds an error when password is blank" do
+ @password = ''
+ validate
+ record.errors[:password].should be_present
+ end
+
+ it "adds an error when password is nil" do
+ @password = nil
+ validate
+ record.errors[:password].should be_present
+ end
+ end
+
+ context "min password length is 12" do
+ before { SiteSetting.stubs(:min_password_length).returns(12) }
+
+ it "adds an error when password length is 11" do
+ @password = "gt38sdt92bv"
+ validate
+ record.errors[:password].should be_present
+ end
+ end
+ end
+
+ context "password not required" do
+ let(:record) { Fabricate.build(:user, password: @password) }
+
+ it "doesn't add an error if password is not required" do
+ @password = nil
+ validate
+ record.errors[:password].should_not be_present
+ end
+ end
+
+end
diff --git a/spec/components/validators/post_validator_spec.rb b/spec/components/validators/post_validator_spec.rb
index 76865503d9e..e1a5ed725ea 100644
--- a/spec/components/validators/post_validator_spec.rb
+++ b/spec/components/validators/post_validator_spec.rb
@@ -24,6 +24,20 @@ describe Validators::PostValidator do
end
end
+ context "too_many_posts" do
+ it "should be invalid when the user has posted too much" do
+ post.user.expects(:posted_too_much_in_topic?).returns(true)
+ validator.max_posts_validator(post)
+ expect(post.errors.count).to be > 0
+ end
+
+ it "should be valid when the user hasn't posted too much" do
+ post.user.expects(:posted_too_much_in_topic?).returns(false)
+ validator.max_posts_validator(post)
+ expect(post.errors.count).to be(0)
+ end
+ end
+
context "invalid post" do
it "should be invalid" do
validator.validate(post)
diff --git a/spec/integration/topic_auto_close_spec.rb b/spec/integration/topic_auto_close_spec.rb
index 9743363924a..7f6c90aaf28 100644
--- a/spec/integration/topic_auto_close_spec.rb
+++ b/spec/integration/topic_auto_close_spec.rb
@@ -87,6 +87,14 @@ describe Topic do
Then { scheduled_jobs_for(:close_topic).should have(2).jobs }
end
end
+
+ context 'a topic that has been auto-closed' do
+ Given(:admin) { Fabricate(:admin) }
+ Given!(:auto_closed_topic) { Fabricate(:topic, user: admin, closed: true, auto_close_at: 1.day.ago, auto_close_user_id: admin.id, auto_close_started_at: 6.days.ago) }
+ When { auto_closed_topic.update_status('closed', false, admin) }
+ Then { auto_closed_topic.reload.auto_close_at.should be_nil }
+ And { auto_closed_topic.auto_close_started_at.should be_nil }
+ end
end
end
end
diff --git a/test/javascripts/fixtures/site_settings_fixtures.js b/test/javascripts/fixtures/site_settings_fixtures.js
index 718c65dafb5..d32a26ba707 100644
--- a/test/javascripts/fixtures/site_settings_fixtures.js
+++ b/test/javascripts/fixtures/site_settings_fixtures.js
@@ -1,3 +1,3 @@
/*jshint maxlen:10000000 */
-Discourse.SiteSettingsOriginal = {"title":"Discourse Meta","logo_url":"/assets/logo.png","logo_small_url":"/assets/logo-single.png","traditional_markdown_linebreaks":false,"top_menu":"latest|new|unread|read|favorited|categories","post_menu":"like|edit|flag|delete|share|bookmark|reply","share_links":"twitter|facebook|google+|email","track_external_right_clicks":false,"must_approve_users":false,"ga_tracking_code":"UA-33736483-2","ga_domain_name":"","enable_long_polling":true,"polling_interval":3000,"anon_polling_interval":30000,"min_post_length":20,"max_post_length":16000,"min_topic_title_length":15,"max_topic_title_length":255,"min_private_message_title_length":2,"allow_uncategorized_topics":true,"min_search_term_length":3,"flush_timings_secs":5,"suppress_reply_directly_below":true,"email_domains_blacklist":"mailinator.com","email_domains_whitelist":null,"version_checks":true,"min_title_similar_length":10,"min_body_similar_length":15,"category_colors":"BF1E2E|F1592A|F7941D|9EB83B|3AB54A|12A89D|25AAE2|0E76BD|652D90|92278F|ED207B|8C6238|231F20|808281|B3B5B4|283890","max_upload_size_kb":1024,"category_featured_topics":6,"favicon_url":"/assets/favicon.ico","dynamic_favicon":false,"uncategorized_name":"uncategorized","uncategorized_color":"AB9364","uncategorized_text_color":"FFFFFF","invite_only":false,"login_required":false,"enable_local_logins":true,"enable_local_account_create":true,"enable_google_logins":true,"enable_yahoo_logins":true,"enable_twitter_logins":true,"enable_facebook_logins":true,"enable_cas_logins":false,"enable_github_logins":true,"enable_persona_logins":true,"educate_until_posts":2,"topic_views_heat_low":1000,"topic_views_heat_medium":2000,"topic_views_heat_high":5000,"min_private_message_post_length":5,"faq_url":"","tos_url":"","privacy_policy_url":"","authorized_extensions":".jpg|.jpeg|.png|.gif|.txt","relative_date_duration":14,"delete_removed_posts_after":24,"delete_user_max_age":7, "default_code_lang": "lang-auto"};
+Discourse.SiteSettingsOriginal = {"title":"Discourse Meta","logo_url":"/assets/logo.png","logo_small_url":"/assets/logo-single.png","traditional_markdown_linebreaks":false,"top_menu":"latest|new|unread|read|favorited|categories","post_menu":"like|edit|flag|delete|share|bookmark|reply","share_links":"twitter|facebook|google+|email","track_external_right_clicks":false,"must_approve_users":false,"ga_tracking_code":"UA-33736483-2","ga_domain_name":"","enable_long_polling":true,"polling_interval":3000,"anon_polling_interval":30000,"min_post_length":20,"max_post_length":16000,"min_topic_title_length":15,"max_topic_title_length":255,"min_private_message_title_length":2,"allow_uncategorized_topics":true,"min_search_term_length":3,"flush_timings_secs":5,"suppress_reply_directly_below":true,"email_domains_blacklist":"mailinator.com","email_domains_whitelist":null,"version_checks":true,"min_title_similar_length":10,"min_body_similar_length":15,"category_colors":"BF1E2E|F1592A|F7941D|9EB83B|3AB54A|12A89D|25AAE2|0E76BD|652D90|92278F|ED207B|8C6238|231F20|808281|B3B5B4|283890","max_upload_size_kb":1024,"category_featured_topics":6,"favicon_url":"/assets/favicon.ico","dynamic_favicon":false,"uncategorized_name":"uncategorized","uncategorized_color":"AB9364","uncategorized_text_color":"FFFFFF","invite_only":false,"login_required":false,"min_password_length":8,"enable_local_logins":true,"enable_local_account_create":true,"enable_google_logins":true,"enable_yahoo_logins":true,"enable_twitter_logins":true,"enable_facebook_logins":true,"enable_cas_logins":false,"enable_github_logins":true,"enable_persona_logins":true,"educate_until_posts":2,"topic_views_heat_low":1000,"topic_views_heat_medium":2000,"topic_views_heat_high":5000,"min_private_message_post_length":5,"faq_url":"","tos_url":"","privacy_policy_url":"","authorized_extensions":".jpg|.jpeg|.png|.gif|.txt","relative_date_duration":14,"delete_removed_posts_after":24,"delete_user_max_age":7, "default_code_lang": "lang-auto"};
Discourse.SiteSettings = jQuery.extend(true, {}, Discourse.SiteSettingsOriginal);