2018-11-19 08:50:00 -05:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2014-02-04 21:53:09 -05:00
|
|
|
# name: poll
|
2015-04-23 13:33:29 -04:00
|
|
|
# about: Official poll plugin for Discourse
|
2018-11-19 08:50:00 -05:00
|
|
|
# version: 1.0
|
2015-04-23 13:33:29 -04:00
|
|
|
# authors: Vikhyat Korrapati (vikhyat), Régis Hanol (zogstrip)
|
2021-07-19 11:35:47 -04:00
|
|
|
# url: https://github.com/discourse/discourse/tree/main/plugins/poll
|
2014-02-04 21:53:09 -05:00
|
|
|
|
2015-05-07 13:49:06 -04:00
|
|
|
register_asset "stylesheets/common/poll.scss"
|
2021-10-05 04:38:49 -04:00
|
|
|
register_asset "stylesheets/common/poll-ui-builder.scss"
|
|
|
|
register_asset "stylesheets/desktop/poll-ui-builder.scss", :desktop
|
|
|
|
register_asset "stylesheets/common/poll-breakdown.scss"
|
2015-05-07 13:49:06 -04:00
|
|
|
|
2024-09-13 11:50:52 -04:00
|
|
|
register_svg_icon "far fa-square-check"
|
2018-11-26 16:49:57 -05:00
|
|
|
|
2018-05-07 22:30:33 -04:00
|
|
|
enabled_site_setting :poll_enabled
|
2021-10-05 04:38:49 -04:00
|
|
|
hide_plugin
|
2015-09-14 13:27:54 -04:00
|
|
|
|
2014-02-04 21:53:09 -05:00
|
|
|
after_initialize do
|
2015-04-23 13:33:29 -04:00
|
|
|
module ::DiscoursePoll
|
2024-10-15 22:09:07 -04:00
|
|
|
PLUGIN_NAME = "poll"
|
|
|
|
DATA_PREFIX = "data-poll-"
|
|
|
|
HAS_POLLS = "has_polls"
|
|
|
|
DEFAULT_POLL_NAME = "poll"
|
2016-06-06 11:04:56 -04:00
|
|
|
|
2014-02-04 21:53:09 -05:00
|
|
|
class Engine < ::Rails::Engine
|
2015-04-23 13:33:29 -04:00
|
|
|
engine_name PLUGIN_NAME
|
|
|
|
isolate_namespace DiscoursePoll
|
2014-02-04 21:53:09 -05:00
|
|
|
end
|
2015-04-23 13:33:29 -04:00
|
|
|
|
2021-10-05 04:38:49 -04:00
|
|
|
class Error < StandardError
|
|
|
|
end
|
2015-04-29 17:34:40 -04:00
|
|
|
end
|
2014-04-12 13:23:21 -04:00
|
|
|
|
2024-02-01 11:28:10 -05:00
|
|
|
require_relative "app/controllers/polls_controller"
|
|
|
|
require_relative "app/models/poll_option"
|
|
|
|
require_relative "app/models/poll_vote"
|
|
|
|
require_relative "app/models/poll"
|
|
|
|
require_relative "app/serializers/poll_option_serializer"
|
|
|
|
require_relative "app/serializers/poll_serializer"
|
|
|
|
require_relative "jobs/regular/close_poll"
|
|
|
|
require_relative "lib/poll"
|
2024-07-17 05:49:14 -04:00
|
|
|
require_relative "lib/ranked_choice"
|
2024-02-01 11:28:10 -05:00
|
|
|
require_relative "lib/polls_updater"
|
|
|
|
require_relative "lib/polls_validator"
|
|
|
|
require_relative "lib/post_validator"
|
|
|
|
require_relative "lib/post_extension"
|
|
|
|
require_relative "lib/user_extension"
|
2014-02-04 21:53:09 -05:00
|
|
|
|
2015-04-23 13:33:29 -04:00
|
|
|
DiscoursePoll::Engine.routes.draw do
|
|
|
|
put "/vote" => "polls#vote"
|
2021-10-05 04:38:49 -04:00
|
|
|
delete "/vote" => "polls#remove_vote"
|
2015-04-23 13:33:29 -04:00
|
|
|
put "/toggle_status" => "polls#toggle_status"
|
2016-06-09 09:33:17 -04:00
|
|
|
get "/voters" => "polls#voters"
|
2019-11-25 12:51:01 -05:00
|
|
|
get "/grouped_poll_results" => "polls#grouped_poll_results"
|
2014-02-04 21:53:09 -05:00
|
|
|
end
|
|
|
|
|
2015-04-23 13:33:29 -04:00
|
|
|
Discourse::Application.routes.append { mount ::DiscoursePoll::Engine, at: "/polls" }
|
2014-02-04 21:53:09 -05:00
|
|
|
|
2021-10-05 04:38:49 -04:00
|
|
|
allow_new_queued_post_payload_attribute("is_poll")
|
|
|
|
register_post_custom_field_type(DiscoursePoll::HAS_POLLS, :boolean)
|
|
|
|
topic_view_post_custom_fields_allowlister { [DiscoursePoll::HAS_POLLS] }
|
|
|
|
|
2020-05-08 02:58:39 -04:00
|
|
|
reloadable_patch do
|
2024-02-01 11:28:10 -05:00
|
|
|
Post.prepend(DiscoursePoll::PostExtension)
|
|
|
|
User.prepend(DiscoursePoll::UserExtension)
|
2018-11-23 05:45:53 -05:00
|
|
|
end
|
|
|
|
|
2017-01-26 00:29:43 -05:00
|
|
|
validate(:post, :validate_polls) do |force = nil|
|
|
|
|
return unless self.raw_changed? || force
|
2014-02-04 21:53:09 -05:00
|
|
|
|
2016-06-06 11:04:56 -04:00
|
|
|
validator = DiscoursePoll::PollsValidator.new(self)
|
|
|
|
return unless (polls = validator.validate_polls)
|
2024-04-12 01:16:10 -04:00
|
|
|
return if polls.blank? && self.id.blank?
|
2014-02-04 21:53:09 -05:00
|
|
|
|
2018-11-19 08:50:00 -05:00
|
|
|
if polls.present?
|
2017-12-05 12:07:21 -05:00
|
|
|
validator = DiscoursePoll::PostValidator.new(self)
|
|
|
|
return unless validator.validate_post
|
|
|
|
end
|
|
|
|
|
2015-04-25 18:12:19 -04:00
|
|
|
# are we updating a post?
|
|
|
|
if self.id.present?
|
2024-04-12 01:16:10 -04:00
|
|
|
return if polls.blank? && ::Poll.where(post: self).empty?
|
|
|
|
|
2018-11-19 08:50:00 -05:00
|
|
|
DiscoursePoll::PollsUpdater.update(self, polls)
|
2015-04-23 13:33:29 -04:00
|
|
|
else
|
2018-11-19 08:50:00 -05:00
|
|
|
self.extracted_polls = polls
|
2015-04-23 13:33:29 -04:00
|
|
|
end
|
2015-04-25 18:12:19 -04:00
|
|
|
|
|
|
|
true
|
2015-04-23 13:33:29 -04:00
|
|
|
end
|
|
|
|
|
2017-01-26 00:29:43 -05:00
|
|
|
NewPostManager.add_handler(1) do |manager|
|
|
|
|
post = Post.new(raw: manager.args[:raw])
|
|
|
|
|
|
|
|
if !DiscoursePoll::PollsValidator.new(post).validate_polls
|
|
|
|
result = NewPostResult.new(:poll, false)
|
|
|
|
|
|
|
|
post.errors.full_messages.each { |message| result.add_error(message) }
|
|
|
|
|
|
|
|
result
|
|
|
|
else
|
|
|
|
manager.args["is_poll"] = true
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
on(:approved_post) do |queued_post, created_post|
|
|
|
|
created_post.validate_polls(true) if queued_post.payload["is_poll"]
|
|
|
|
end
|
|
|
|
|
2016-04-13 13:24:39 -04:00
|
|
|
on(:reduce_cooked) do |fragment, post|
|
|
|
|
if post.nil? || post.trashed?
|
|
|
|
fragment.css(".poll, [data-poll-name]").each(&:remove)
|
|
|
|
else
|
2018-05-02 20:12:19 -04:00
|
|
|
post_url = post.full_url
|
2016-04-13 13:24:39 -04:00
|
|
|
fragment
|
|
|
|
.css(".poll, [data-poll-name]")
|
|
|
|
.each do |poll|
|
|
|
|
poll.replace "<p><a href='#{post_url}'>#{I18n.t("poll.email.link_to_poll")}</a></p>"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-05-29 11:05:52 -04:00
|
|
|
on(:reduce_excerpt) do |doc, options|
|
|
|
|
post = options[:post]
|
2019-05-29 18:02:33 -04:00
|
|
|
|
2019-05-29 18:05:53 -04:00
|
|
|
replacement =
|
2023-01-06 15:42:16 -05:00
|
|
|
(
|
2019-05-29 18:05:53 -04:00
|
|
|
if post&.url.present?
|
2022-08-09 06:28:29 -04:00
|
|
|
"<a href='#{UrlHelper.normalized_encode(post.url)}'>#{I18n.t("poll.poll")}</a>"
|
2023-01-06 15:42:16 -05:00
|
|
|
else
|
2019-05-29 18:05:53 -04:00
|
|
|
I18n.t("poll.poll")
|
2023-01-06 15:42:16 -05:00
|
|
|
end
|
|
|
|
)
|
2019-05-29 18:02:33 -04:00
|
|
|
|
2019-05-29 11:05:52 -04:00
|
|
|
doc.css("div.poll").each { |poll| poll.replace(replacement) }
|
|
|
|
end
|
|
|
|
|
2020-03-20 14:29:00 -04:00
|
|
|
on(:post_created) do |post, _opts, user|
|
|
|
|
guardian = Guardian.new(user)
|
2018-05-02 20:12:19 -04:00
|
|
|
DiscoursePoll::Poll.schedule_jobs(post)
|
2018-11-19 08:50:00 -05:00
|
|
|
|
2021-11-22 06:31:53 -05:00
|
|
|
next if post.is_first_post?
|
|
|
|
next if post.custom_fields[DiscoursePoll::HAS_POLLS].blank?
|
|
|
|
|
|
|
|
polls =
|
|
|
|
ActiveModel::ArraySerializer.new(
|
|
|
|
post.polls,
|
|
|
|
each_serializer: PollSerializer,
|
|
|
|
root: false,
|
|
|
|
scope: guardian,
|
|
|
|
).as_json
|
|
|
|
post.publish_message!("/polls/#{post.topic_id}", post_id: post.id, polls: polls)
|
2015-04-27 12:59:29 -04:00
|
|
|
end
|
|
|
|
|
2018-02-28 16:21:52 -05:00
|
|
|
on(:merging_users) do |source_user, target_user|
|
2023-06-14 23:18:51 -04:00
|
|
|
DB.exec(<<-SQL, source_user_id: source_user.id, target_user_id: target_user.id)
|
|
|
|
DELETE FROM poll_votes
|
2023-06-27 06:58:16 -04:00
|
|
|
WHERE user_id = :source_user_id
|
|
|
|
AND EXISTS (
|
|
|
|
SELECT 1
|
2023-06-14 23:18:51 -04:00
|
|
|
FROM poll_votes
|
|
|
|
WHERE user_id = :target_user_id
|
2023-06-27 06:58:16 -04:00
|
|
|
AND poll_votes.poll_id = poll_votes.poll_id
|
|
|
|
);
|
2023-06-14 23:18:51 -04:00
|
|
|
|
|
|
|
UPDATE poll_votes
|
|
|
|
SET user_id = :target_user_id
|
|
|
|
WHERE user_id = :source_user_id;
|
|
|
|
SQL
|
2018-02-28 16:21:52 -05:00
|
|
|
end
|
|
|
|
|
2018-11-19 08:50:00 -05:00
|
|
|
add_to_class(:topic_view, :polls) do
|
|
|
|
@polls ||=
|
|
|
|
begin
|
|
|
|
polls = {}
|
|
|
|
|
|
|
|
post_with_polls =
|
|
|
|
@post_custom_fields.each_with_object([]) do |fields, obj|
|
|
|
|
obj << fields[0] if fields[1][DiscoursePoll::HAS_POLLS]
|
|
|
|
end
|
|
|
|
|
2018-11-20 11:28:41 -05:00
|
|
|
if post_with_polls.present?
|
2024-09-10 11:41:08 -04:00
|
|
|
all_polls = Poll.includes(:poll_options).where(post_id: post_with_polls)
|
|
|
|
Poll.preload!(all_polls, user_id: @user&.id)
|
2024-09-18 05:01:40 -04:00
|
|
|
DiscoursePoll::Poll.preload_serialized_voters!(all_polls)
|
2024-09-10 11:41:08 -04:00
|
|
|
all_polls.each do |p|
|
|
|
|
polls[p.post_id] ||= []
|
|
|
|
polls[p.post_id] << p
|
|
|
|
end
|
2023-01-06 15:42:16 -05:00
|
|
|
end
|
|
|
|
|
2018-11-19 08:50:00 -05:00
|
|
|
polls
|
2017-01-27 04:09:33 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-01-22 20:35:14 -05:00
|
|
|
add_to_serializer(:current_user, :can_create_poll) do
|
|
|
|
scope.user&.staff? || scope.user&.in_any_groups?(SiteSetting.poll_create_allowed_groups_map)
|
|
|
|
end
|
|
|
|
|
2023-04-24 07:17:51 -04:00
|
|
|
add_to_class(PostSerializer, :preloaded_polls) do
|
2018-11-19 08:50:00 -05:00
|
|
|
@preloaded_polls ||=
|
|
|
|
if @topic_view.present?
|
|
|
|
@topic_view.polls[object.id]
|
|
|
|
else
|
2020-09-05 11:14:45 -04:00
|
|
|
Poll.includes(:poll_options).where(post: object)
|
2018-11-19 08:50:00 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-04-24 07:17:51 -04:00
|
|
|
add_to_serializer(:post, :polls, include_condition: -> { preloaded_polls.present? }) do
|
2020-03-20 14:29:00 -04:00
|
|
|
preloaded_polls.map { |p| PollSerializer.new(p, root: false, scope: self.scope) }
|
2018-11-19 08:50:00 -05:00
|
|
|
end
|
|
|
|
|
2023-04-24 07:17:51 -04:00
|
|
|
add_to_serializer(
|
|
|
|
:post,
|
|
|
|
:polls_votes,
|
|
|
|
include_condition: -> do
|
|
|
|
scope.user&.id.present? && preloaded_polls.present? &&
|
|
|
|
preloaded_polls.any? { |p| p.has_voted?(scope.user) }
|
|
|
|
end,
|
|
|
|
) do
|
2018-11-19 08:50:00 -05:00
|
|
|
preloaded_polls
|
|
|
|
.map do |poll|
|
2024-07-17 05:49:14 -04:00
|
|
|
if poll.ranked_choice?
|
|
|
|
user_poll_votes =
|
|
|
|
poll
|
|
|
|
.poll_votes
|
|
|
|
.where(user_id: scope.user.id)
|
|
|
|
.joins(:poll_option)
|
|
|
|
.pluck("poll_options.digest", "poll_votes.rank")
|
|
|
|
.map { |digest, rank| { digest: digest, rank: rank } }
|
|
|
|
else
|
|
|
|
user_poll_votes =
|
|
|
|
poll
|
|
|
|
.poll_votes
|
|
|
|
.where(user_id: scope.user.id)
|
|
|
|
.joins(:poll_option)
|
|
|
|
.pluck("poll_options.digest")
|
|
|
|
end
|
2018-11-19 08:50:00 -05:00
|
|
|
[poll.name, user_poll_votes]
|
|
|
|
end
|
|
|
|
.to_h
|
2016-06-07 06:55:01 -04:00
|
|
|
end
|
|
|
|
|
2023-01-18 14:55:20 -05:00
|
|
|
register_search_advanced_filter(/in:polls/) do |posts, match|
|
|
|
|
if SiteSetting.poll_enabled
|
|
|
|
posts.joins(:polls)
|
|
|
|
else
|
|
|
|
posts
|
|
|
|
end
|
|
|
|
end
|
2015-04-23 13:33:29 -04:00
|
|
|
end
|