discourse/plugins/poll/plugin.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

252 lines
6.7 KiB
Ruby
Raw Permalink Normal View History

# frozen_string_literal: true
2014-02-04 21:53:09 -05:00
# name: poll
# about: Official poll plugin for Discourse
# version: 1.0
# authors: Vikhyat Korrapati (vikhyat), Régis Hanol (zogstrip)
# 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"
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
Upgrade to FontAwesome 5 (take two) (#6673) * Add missing icons to set * Revert FA5 revert This reverts commit 42572ff * use new SVG syntax in locales * Noscript page changes (remove login button, center "powered by" footer text) * Cast wider net for SVG icons in settings - include any _icon setting for SVG registry (offers better support for plugin settings) - let themes store multiple pipe-delimited icons in a setting - also replaces broken onebox image icon with SVG reference in cooked post processor * interpolate icons in locales * Fix composer whisper icon alignment * Add support for stacked icons * SECURITY: enforce hostname to match discourse hostname This ensures that the hostname rails uses for various helpers always matches the Discourse hostname * load SVG sprite with pre-initializers * FIX: enable caching on SVG sprites * PERF: use JSONP for SVG sprites so they are served from CDN This avoids needing to deal with CORS for loading of the SVG Note, added the svg- prefix to the filename so we can quickly tell in dev tools what the file is * Add missing SVG sprite JSONP script to CSP * Upgrade to FA 5.5.0 * Add support for all FA4.7 icons - adds complete frontend and backend for renamed FA4.7 icons - improves performance of SvgSprite.bundle and SvgSprite.all_icons * Fix group avatar flair preview - adds an endpoint at /svg-sprites/search/:keyword - adds frontend ajax call that pulls icon in avatar flair preview even when it is not in subset * Remove FA 4.7 font files
2018-11-26 16:49:57 -05:00
register_svg_icon "far fa-check-square"
enabled_site_setting :poll_enabled
hide_plugin
2014-02-04 21:53:09 -05:00
after_initialize do
module ::DiscoursePoll
PLUGIN_NAME ||= "poll"
DATA_PREFIX ||= "data-poll-"
HAS_POLLS ||= "has_polls"
DEFAULT_POLL_NAME ||= "poll"
2014-02-04 21:53:09 -05:00
class Engine < ::Rails::Engine
engine_name PLUGIN_NAME
isolate_namespace DiscoursePoll
2014-02-04 21:53:09 -05:00
end
class Error < StandardError
end
end
require_relative "app/controllers/polls_controller.rb"
require_relative "app/models/poll_option.rb"
require_relative "app/models/poll_vote.rb"
require_relative "app/models/poll.rb"
require_relative "app/serializers/poll_option_serializer.rb"
require_relative "app/serializers/poll_serializer.rb"
require_relative "jobs/regular/close_poll.rb"
require_relative "lib/poll.rb"
require_relative "lib/polls_updater.rb"
require_relative "lib/polls_validator.rb"
require_relative "lib/post_validator.rb"
2014-02-04 21:53:09 -05:00
DiscoursePoll::Engine.routes.draw do
put "/vote" => "polls#vote"
delete "/vote" => "polls#remove_vote"
put "/toggle_status" => "polls#toggle_status"
2016-06-09 09:33:17 -04:00
get "/voters" => "polls#voters"
get "/grouped_poll_results" => "polls#grouped_poll_results"
2014-02-04 21:53:09 -05:00
end
Discourse::Application.routes.append { mount ::DiscoursePoll::Engine, at: "/polls" }
2014-02-04 21:53:09 -05: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] }
reloadable_patch do
Post.class_eval do
attr_accessor :extracted_polls
2014-02-04 21:53:09 -05:00
has_many :polls, dependent: :destroy
2014-02-04 21:53:09 -05:00
after_save do
polls = self.extracted_polls
self.extracted_polls = nil
next if polls.blank? || !polls.is_a?(Hash)
post = self
2014-02-04 21:53:09 -05:00
Poll.transaction do
polls.values.each { |poll| DiscoursePoll::Poll.create!(post.id, poll) }
post.custom_fields[DiscoursePoll::HAS_POLLS] = true
post.save_custom_fields(true)
end
end
2014-02-04 21:53:09 -05:00
end
User.class_eval { has_many :poll_votes, dependent: :delete_all }
end
validate(:post, :validate_polls) do |force = nil|
return unless self.raw_changed? || force
2014-02-04 21:53:09 -05:00
validator = DiscoursePoll::PollsValidator.new(self)
return unless (polls = validator.validate_polls)
2014-02-04 21:53:09 -05:00
if polls.present?
validator = DiscoursePoll::PostValidator.new(self)
return unless validator.validate_post
end
# are we updating a post?
if self.id.present?
DiscoursePoll::PollsUpdater.update(self, polls)
else
self.extracted_polls = polls
end
true
end
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
on(:reduce_cooked) do |fragment, post|
if post.nil? || post.trashed?
fragment.css(".poll, [data-poll-name]").each(&:remove)
else
post_url = post.full_url
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
on(:reduce_excerpt) do |doc, options|
post = options[:post]
2019-05-29 18:02:33 -04:00
replacement =
(
if post&.url.present?
"<a href='#{UrlHelper.normalized_encode(post.url)}'>#{I18n.t("poll.poll")}</a>"
else
I18n.t("poll.poll")
end
)
2019-05-29 18:02:33 -04:00
doc.css("div.poll").each { |poll| poll.replace(replacement) }
end
on(:post_created) do |post, _opts, user|
guardian = Guardian.new(user)
DiscoursePoll::Poll.schedule_jobs(post)
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)
end
2018-02-28 16:21:52 -05:00
on(:merging_users) do |source_user, target_user|
DB.exec(<<-SQL, source_user_id: source_user.id, target_user_id: target_user.id)
DELETE FROM poll_votes
WHERE user_id = :source_user_id
AND EXISTS (
SELECT 1
FROM poll_votes
WHERE user_id = :target_user_id
AND poll_votes.poll_id = poll_votes.poll_id
);
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
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
if post_with_polls.present?
Poll
.where(post_id: post_with_polls)
.each do |p|
polls[p.post_id] ||= []
polls[p.post_id] << p
end
end
polls
end
end
add_to_class(PostSerializer, :preloaded_polls) do
@preloaded_polls ||=
if @topic_view.present?
@topic_view.polls[object.id]
else
Poll.includes(:poll_options).where(post: object)
end
end
add_to_serializer(:post, :polls, include_condition: -> { preloaded_polls.present? }) do
preloaded_polls.map { |p| PollSerializer.new(p, root: false, scope: self.scope) }
end
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
preloaded_polls
.map do |poll|
user_poll_votes =
poll
.poll_votes
.where(user_id: scope.user.id)
.joins(:poll_option)
.pluck("poll_options.digest")
[poll.name, user_poll_votes]
end
.to_h
2016-06-07 06:55:01 -04:00
end
register_search_advanced_filter(/in:polls/) do |posts, match|
if SiteSetting.poll_enabled
posts.joins(:polls)
else
posts
end
end
end