Allow see posts history to unlogged users
[shapado:piglops-shapado.git] / app / controllers / questions_controller.rb
1 class QuestionsController < ApplicationController
2   before_filter :login_required, :except => [:new, :create, :index, :show, :related_questions, :tags_for_autocomplete, :retag, :retag_to, :random, :history, :diff]
3   before_filter :admin_required, :only => [:move, :move_to]
4   before_filter :moderator_required, :only => [:close]
5   before_filter :check_permissions, :only => [:solve, :unsolve, :destroy]
6   before_filter :check_update_permissions, :only => [:edit, :update, :revert, :remove_attachment]
7   before_filter :set_active_tag
8   before_filter :check_age, :only => [:show]
9   before_filter :check_create_permissions, :only => [:create, :new]
10   before_filter :check_retag_permissions, :only => [:retag, :retag_to]
11
12   tabs :default => :questions, :tags => :tags,
13        :new => :ask_question
14
15   subtabs :index => [[:newest, [:created_at, Mongo::DESCENDING]],
16                      [:hot, [[:hotness, Mongo::DESCENDING], [:views_count, Mongo::DESCENDING]]],
17                      [:votes, [:votes_average, Mongo::DESCENDING]],
18                      [:activity, [:activity_at, :desc]], [:expert, [:created_at, Mongo::DESCENDING]]],
19           :show => [[:votes, [:votes_average, Mongo::DESCENDING]], [:oldest, [:created_at, Mongo::ASCENDING]], [:newest, [:created_at, Mongo::DESCENDING]]]
20   helper :votes
21
22   # GET /questions
23   # GET /questions.xml
24   def index
25     find_questions
26   end
27
28
29   def history
30     @question = current_group.questions.by_slug(params[:id])
31
32     respond_to do |format|
33       format.html
34       format.json { render :json => @question.versions.to_json }
35     end
36   end
37
38   def diff
39     @question = current_group.questions.by_slug(params[:id])
40     @prev = params[:prev]
41     @curr = params[:curr]
42     if @prev.blank? || @curr.blank? || @prev == @curr
43       flash[:error] = "please, select two versions"
44       render :history
45     else
46       if @prev
47         @prev = (@prev == "current" ? "current" : @prev.to_i)
48       end
49
50       if @curr
51         @curr = (@curr == "current" ? "current" : @curr.to_i)
52       end
53     end
54   end
55
56   def revert
57     @question.load_version(params[:version].to_i)
58
59     respond_to do |format|
60       format.html
61     end
62   end
63
64   def related_questions
65     if params[:id]
66       @question = current_group.questions.by_slug(params[:id])
67     elsif params[:question]
68       @question = Question.new(params[:question])
69       @question.group_id = current_group.id
70     end
71
72     text_search = @question.title || ""
73     text_search << (@question.body || "")
74     text_search << @question.tags.join
75     conditions = {group_id: @question.group_id, banned: false}
76     if params[:unanswers]
77       conditions[:answered_with_id] = nil
78     end
79
80     if params[:per_page]
81       conditions[:per_page] = params[:per_page]
82     end
83
84     @questions = Question.filter(text_search, conditions)
85
86     respond_to do |format|
87       format.js do
88         content = ''
89         settings = {}
90         if !@questions.empty?
91           if params[:mini]
92             content = render_to_string(:partial => "questions/question",
93                                      :collection  => @questions,
94                                      :locals => {:mini => true, :lite => true});
95           else
96             content = render_to_string(:partial => "shared/post",
97                                        :locals => {:questions => @questions,
98                                        :for_answers => params[:answers]})
99           end
100         end
101         render :json => {:html => content}.to_json
102       end
103     end
104   end
105
106   def tags_for_autocomplete
107     respond_to do |format|
108       format.js do
109         result = []
110         if q = params[:term]
111           result = Tag.where(:name => /^#{Regexp.escape(q.downcase)}/i,
112                     :group_id => current_group.id).order(:count => :desc)
113         end
114
115         results = result.map do |t|
116           {:caption => "#{t.name} (#{t.count.to_i})", :value => t.name}
117         end
118         # if no results, show default tags
119         if results.empty?
120           results = current_group.default_tags.map  {|tag|{:value=> tag, :caption => tag}}
121           results = [{ :value => q, :caption => q }] + results
122         end
123         render :json => results
124       end
125     end
126   end
127
128   # GET /questions/1
129   # GET /questions/1.xml
130   def show
131     if params[:language]
132       params.delete(:language)
133       head :moved_permanently, :location => url_for(params)
134       return
135     end
136
137     if @question.reward && @question.reward.ends_at < Time.now
138       Jobs::Questions.async.close_reward(@question.id).commit!(1)
139     end
140
141     @tag_cloud = Question.tag_cloud(:_id => @question.id, :banned => false)
142     options = {:banned => false}
143     options[:_id] = {:$ne => @question.answer_id} if @question.answer_id
144     @answers = @question.answers.where(options).
145                                 order_by(current_order).
146                                 without(:_keywords).
147                                 page(params["page"])
148
149     @answer = Answer.new(params[:answer])
150
151     if @question.user != current_user && !is_bot?
152       @question.viewed!(request.remote_ip)
153
154       if (@question.views_count % 10) == 0
155         sweep_question_views
156       end
157     end
158
159     set_page_title(@question.title)
160     add_feeds_url(url_for(:format => "atom"), t("feeds.question"))
161
162     respond_to do |format|
163       format.html { Jobs::Questions.async.on_view_question(@question.id).commit!(5) }
164       format.mobile
165       format.json  { render :json => @question.to_json(:except => %w[_keywords slug watchers]) }
166       format.atom
167     end
168   end
169
170   # GET /questions/new
171   # GET /questions/new.xml
172   def new
173     @question = Question.new(params[:question])
174
175     if params[:from_question]
176       @original_question = Question.minimal.without(:comments).where(:_id => params[:from_question]).first
177
178       if params[:at]
179         @original_answer = @original_question.answers.without(:votes, :versions, :flags, :comments).where(:_id => params[:at]).first
180       end
181     end
182
183     respond_to do |format|
184       format.html # new.html.erb
185       format.mobile
186       format.json  { render :json => @question.to_json }
187     end
188   end
189
190   # GET /questions/1/edit
191   def edit
192   end
193
194   # POST /questions
195   # POST /questions.xml
196   def create
197     @question = Question.new
198     if !params[:tag_input].blank? && params[:question][:tags].blank?
199       params[:question][:tags] = params[:tag_input]
200     end
201
202     @question.group = current_group
203     @question.user = current_user
204     @question.safe_update(%w[title body language tags wiki position attachments], params[:question])
205
206     if params[:original_question_id]
207       @question.follow_up = FollowUp.new
208       @question.follow_up.original_question_id = params[:original_question_id]
209       @question.follow_up.original_answer_id = params[:original_answer_id]
210     end
211
212     @question.anonymous = params[:question][:anonymous] if current_group.enable_anonymous
213
214     if !logged_in?
215       if recaptcha_valid? && params[:user]
216         @user = User.where(:email => params[:user][:email]).first
217         if @user.present?
218           if !@user.anonymous
219             flash[:notice] = I18n.t('questions.create.already_registered', :email => params[:user][:email])
220             return create_draft!
221           else
222             @question.user = @user
223           end
224         elsif current_group.enable_anonymous
225           @user = User.new(:anonymous => true, :login => "Anonymous")
226           @user.safe_update(%w[name email website], params[:user])
227           if @user.name.present? && @user.name.size > 3
228             @user.login = @user.name
229           end
230           @user.save!
231           @question.user = @user
232         else
233           return login_required
234         end
235       elsif !AppConfig.recaptcha["activate"]
236         return create_draft!
237       end
238     end
239
240     return login_required if !@question.user
241
242     respond_to do |format|
243       if (logged_in? ||  (@question.user.valid? && recaptcha_valid?)) && @question.save
244         @question.add_contributor(@question.user)
245
246         sweep_question_views
247         html = nil
248         if params[:facebook]
249           html = render_to_string(:partial => "facebook/question", :object => @question)
250         else
251           html = render_to_string(:partial => "questions/question", :object => @question)
252         end
253
254         Magent::WebSocketChannel.push({id: "newquestion",
255                                        object_id: @question.id,
256                                        name: @question.title,
257                                        html: html,
258                                        channel_id: current_group.slug})
259
260         current_group.tag_list.add_tags(*@question.tags)
261         unless @question.anonymous
262           @question.user.stats.add_question_tags(*@question.tags)
263           @question.user.on_activity(:ask_question, current_group)
264           link = question_url(@question)
265           Jobs::Questions.async.on_ask_question(@question.id, link).commit!
266           Jobs::Mailer.async.on_ask_question(@question.id).commit!
267         end
268
269         Jobs::Tags.async.question_retagged(@question.id, @question.tags, [], Time.now).commit!
270
271         current_group.on_activity(:ask_question)
272         if !@question.removed_tags.blank?
273           flash[:warning] = I18n.t("questions.model.messages.tags_not_added",
274                                    :tags => @question.removed_tags.join(", "),
275                                    :reputation_required => @question.group.reputation_constrains["create_new_tags"])
276         else
277           flash[:notice] = t(:flash_notice, :scope => "questions.create")
278         end
279
280         format.html {
281           if widget = params[:question][:external_widget]
282             flash[:notice] += I18n.t('widgets.ask_question.view_question', :question => question_path(@question))
283             redirect_to embedded_widget_path(:id => widget)
284           else
285             redirect_to(question_path(@question))
286           end
287         }
288         format.json { render :json => @question.to_json(:except => %w[_keywords watchers]), :status => :created}
289         format.js {render :json => {:success => true, :message => flash[:notice], :html => html} }
290       else
291         @question.errors.add(:captcha, "is invalid") unless recaptcha_valid?
292         format.html { render :action => "new" }
293         format.json { render :json => @question.errors+@question.user.errors }
294         format.js { render :json => {:success => false, :message => (@question.errors+@question.user.errors).join(", ")} }
295       end
296     end
297   end
298
299   # PUT /questions/1
300   # PUT /questions/1.xml
301   def update
302     respond_to do |format|
303       if !params[:tag_input].blank? && params[:question][:tags].blank?
304         params[:question][:tags] = params[:tag_input]
305       end
306       @question.safe_update(%w[title body language tags wiki adult_content version_message attachments], params[:question])
307
308       @question.updated_by = current_user
309       @question.last_target = @question
310
311       changes = @question.changes
312       tags_changes = changes["tags"]
313
314       if @question.save
315         @question.add_contributor(current_user)
316
317         sweep_question(@question)
318
319         if tags_changes
320           Jobs::Tags.async.question_retagged(@question.id, tags_changes.last, tags_changes.first, Time.now).commit!
321         end
322
323         Rails.logger.info ">>>>>>>>>>>>>>>> #{changes.inspect}"
324         Magent::WebSocketChannel.push({id: "updatequestion",
325                                        object_id: @question.id,
326                                        name: @question.title,
327                                        changes: changes,
328                                        channel_id: current_group.slug})
329
330         if !@question.removed_tags.blank?
331           flash[:warning] = I18n.t("questions.model.messages.tags_not_added",
332                                    :tags => @question.removed_tags.join(", "),
333                                    :reputation_required => @question.group.reputation_constrains["create_new_tags"])
334         else
335           flash[:notice] = t(:flash_notice, :scope => "questions.update")
336         end
337         format.html { redirect_to(question_path(@question)) }
338         format.json  { head :ok }
339       else
340         format.html { render :action => "edit" }
341         format.json  { render :json => @question.errors, :status => :unprocessable_entity }
342       end
343     end
344   end
345
346   # DELETE /questions/1
347   # DELETE /questions/1.xml
348   def destroy
349     if @question.user_id == current_user.id
350       @question.user.update_reputation(:delete_question, current_group)
351     end
352     sweep_question(@question)
353     @question.answers.each do |answer|
354       sweep_answer(answer)
355     end
356     @question.destroy
357
358     Jobs::Questions.async.on_destroy_question(current_user.id, @question.attributes).commit!
359     Magent::WebSocketChannel.push({id: "destroyquestion",
360                                    object_id: @question.id,
361                                    name: @question.title,
362                                    channel_id: current_group.slug});
363
364     respond_to do |format|
365       format.html { redirect_to(questions_url) }
366       format.json  { head :ok }
367     end
368   end
369
370   def solve
371     @answer = @question.answers.find(params[:answer_id])
372     @question.answer = @answer
373     @question.accepted = true
374     @question.answered_with = @answer if @question.answered_with.nil?
375
376     respond_to do |format|
377       if !@question.subjetive && @question.save
378         sweep_question(@question)
379         sweep_answer(@answer)
380
381         current_user.on_activity(:close_question, current_group)
382         if current_user != @answer.user
383           @answer.user.update_reputation(:answer_picked_as_solution, current_group)
384         end
385
386         Jobs::Questions.async.on_question_solved(@question.id, @answer.id).commit!
387
388         flash[:notice] = t(:flash_notice, :scope => "questions.solve")
389         format.html { redirect_to question_path(@question) }
390         format.json  { head :ok }
391       else
392         @tag_cloud = Question.tag_cloud(:_id => @question.id, :banned => false)
393         options = {:banned => false}
394         options[:_id] = {:$ne => @question.answer_id} if @question.answer_id
395         @answers = @question.answers.where(options).page(params["page"]).order_by(current_order)
396         @answer = Answer.new
397
398         format.html { render :action => "show" }
399         format.json  { render :json => @question.errors, :status => :unprocessable_entity }
400       end
401     end
402   end
403
404   def unsolve
405     @answer_id = @question.answer.id
406     @answer_owner = @question.answer.user
407
408     @question.answer = nil
409     @question.accepted = false
410     @question.answered_with = nil if @question.answered_with == @question.answer
411
412     respond_to do |format|
413       if @question.save
414         sweep_question(@question)
415         sweep_answer(@answer_id)
416
417         flash[:notice] = t(:flash_notice, :scope => "questions.unsolve")
418         current_user.on_activity(:reopen_question, current_group)
419         if current_user != @answer_owner
420           @answer_owner.update_reputation(:answer_unpicked_as_solution, current_group)
421         end
422
423         Jobs::Questions.async.on_question_unsolved(@question.id, @answer_id).commit!
424
425         format.html { redirect_to question_path(@question) }
426         format.json  { head :ok }
427       else
428         @tag_cloud = Question.tag_cloud(:_id => @question.id, :banned => false)
429         options = {:banned => false}
430         options[:_id] = {:$ne => @question.answer_id} if @question.answer_id
431         @answers = @question.answers.where(options).
432                             order_by(current_order).
433                             page(params["page"])
434         @answer = Answer.new
435
436         format.html { render :action => "show" }
437         format.json  { render :json => @question.errors, :status => :unprocessable_entity }
438       end
439     end
440   end
441
442   def follow
443     @question = current_group.questions.by_slug(params[:id])
444     @question.add_follower(current_user)
445     Jobs::Questions.async.on_question_followed(@question.id).commit!
446     flash[:notice] = t("questions.watch.success")
447
448     sweep_question(@question)
449
450     respond_to do |format|
451       format.html {redirect_to question_path(@question)}
452       format.mobile { redirect_to question_path(@question, :format => :mobile) }
453       format.js {
454         render(:json => {:success => true,
455                  :message => flash[:notice] }.to_json)
456       }
457       format.json { head :ok }
458     end
459   end
460
461   def unfollow
462     @question = current_group.questions.by_slug(params[:id])
463     @question.remove_follower(current_user)
464     flash[:notice] = t("questions.unwatch.success")
465
466     sweep_question(@question)
467
468     respond_to do |format|
469       format.html {redirect_to question_path(@question)}
470       format.mobile { redirect_to question_path(@question, :format => :mobile) }
471       format.js {
472         render(:json => {:success => true,
473                  :message => flash[:notice] }.to_json)
474       }
475       format.json { head :ok }
476     end
477   end
478
479   def move
480     @question = current_group.questions.by_slug(params[:id])
481     render
482   end
483
484   def move_to
485     @group = Group.by_slug(params[:question][:group])
486     @question = current_group.questions.by_slug(params[:id])
487
488     if @group
489       @question.group = @group
490
491       if @question.save
492         sweep_question(@question)
493         @question.answers.each do |answer|
494           sweep_answer(answer)
495         end
496
497         Answer.override({"question_id" => @question.id},
498                         {"group_id" => @group.id})
499       end
500       flash[:notice] = t("questions.move_to.success", :group => @group.name)
501       redirect_to question_path(@question)
502     else
503       flash[:error] = t("questions.move_to.group_dont_exists",
504                         :group => params[:question][:group])
505       render :move
506     end
507   end
508
509   def retag_to
510     @question = current_group.questions.by_slug(params[:id])
511
512     @question.tags = params[:question][:tags]
513     @question.updated_by = current_user
514     @question.last_target = @question
515
516     tags_changes = @question.changes["tags"]
517
518     if @question.save
519       sweep_question(@question)
520
521       if (Time.now - @question.created_at) < 8.days
522         @question.on_activity(true)
523       end
524
525       Jobs::Questions.async.on_retag_question(@question.id, current_user.id).commit!
526       if tags_changes
527         Jobs::Tags.async.question_retagged(@question.id, tags_changes.last, tags_changes.first, Time.now).commit!
528       end
529
530       if !@question.removed_tags.blank?
531         flash[:warning] = I18n.t("questions.model.messages.tags_not_added",
532                                  :tags => @question.removed_tags.join(", "),
533                                  :reputation_required => @question.group.reputation_constrains["create_new_tags"])
534       else
535         flash[:notice] = t("questions.retag_to.success", :group => @question.group.name)
536       end
537
538       respond_to do |format|
539         format.html {redirect_to question_path(@question)}
540         format.js {
541           render(:json => {:success => true,
542                    :message => flash[:warning] || flash[:notice], :tags => @question.tags }.to_json)
543         }
544       end
545     else
546       flash[:error] = t("questions.retag_to.failure",
547                         :group => params[:question][:group])
548
549       respond_to do |format|
550         format.html {render :retag}
551         format.js {
552           render(:json => {:success => false,
553                    :message => flash[:error] }.to_json)
554         }
555       end
556     end
557   end
558
559   def retag
560     @question = current_group.questions.by_slug(params[:id])
561     respond_to do |format|
562       format.html {render}
563       format.js {
564         render(:json => {:success => true, :html => render_to_string(:partial => "questions/retag_form",
565                                                    :member  => @question)}.to_json)
566       }
567     end
568   end
569
570   def twitter_share
571     @question = current_group.questions.only([:title, :slug]).by_slug(params[:id])
572     url = question_url(@question)
573     text = "#{current_group.share.starts_with} #{@question.title} - #{url} #{current_group.share.ends_with}"
574
575     Jobs::Users.async.post_to_twitter(current_user.id, text).commit!
576
577     respond_to do |format|
578       format.html {redirect_to url}
579       format.js { render :json => { :ok => true }}
580     end
581   end
582
583   def random
584     conds = {:group_id => current_group.id}
585     conds[:answered] = false if params[:unanswered] && params[:unanswered] != "0"
586     @question = Question.random(conds)
587     if !@question
588       conds.delete(:answered)
589       @question = Question.random(conds)
590     end
591
592     respond_to do |format|
593       format.html {  redirect_to @question.nil? ?  questions_path : question_path(@question) }
594       format.json { render :json => @question }
595     end
596   end
597
598   def remove_attachment
599     @question.attachments.delete(params[:attach_id])
600     @question.save
601     respond_to do |format|
602       format.html { redirect_to edit_question_path(@question) }
603       format.json { render :json => {:ok => true} }
604     end
605   end
606
607   protected
608   def check_permissions
609     @question = current_group.questions.by_slug(params[:id])
610
611     if @question.nil?
612       redirect_to questions_path
613     elsif !(current_user.can_modify?(@question) ||
614            (params[:action] != 'destroy' && @question.can_be_deleted_by?(current_user)) ||
615            current_user.owner_of?(@question.group)) # FIXME: refactor
616       flash[:error] = t("global.permission_denied")
617       redirect_to question_path(@question)
618     end
619   end
620
621   def check_update_permissions
622     @question = current_group.questions.by_slug(params[:id])
623     allow_update = true
624     unless @question.nil?
625       if !current_user.can_modify?(@question)
626         if @question.wiki
627           if !current_user.can_edit_wiki_post_on?(@question.group)
628             allow_update = false
629             reputation = @question.group.reputation_constrains["edit_wiki_post"]
630             flash[:error] = I18n.t("users.messages.errors.reputation_needed",
631                                         :min_reputation => reputation,
632                                         :action => I18n.t("users.actions.edit_wiki_post"))
633           end
634         else
635           if !current_user.can_edit_others_posts_on?(@question.group)
636             allow_update = false
637             reputation = @question.group.reputation_constrains["edit_others_posts"]
638             flash[:error] = I18n.t("users.messages.errors.reputation_needed",
639                                         :min_reputation => reputation,
640                                         :action => I18n.t("users.actions.edit_others_posts"))
641           end
642         end
643         return redirect_to question_path(@question) if !allow_update
644       end
645     else
646       return redirect_to questions_path
647     end
648   end
649
650   def check_retag_permissions
651     @question = current_group.questions.by_slug(params[:id])
652     unless logged_in? && (current_user.can_retag_others_questions_on?(current_group) ||  current_user.can_modify?(@question))
653       reputation = @question.group.reputation_constrains["retag_others_questions"]
654       if !logged_in?
655         flash[:error] = t("questions.show.unauthenticated_retag")
656       else
657         flash[:error] = I18n.t("users.messages.errors.reputation_needed",
658                                :min_reputation => reputation,
659                                :action => I18n.t("users.actions.retag_others_questions"))
660       end
661       respond_to do |format|
662         format.html {redirect_to @question}
663         format.js {
664           render(:json => {:success => false,
665                    :message => flash[:error] }.to_json)
666         }
667       end
668     end
669   end
670
671   def check_create_permissions
672     if logged_in? && !current_user.can_ask_on?(current_group)
673       reputation = current_group.reputation_constrains["ask"]
674
675       flash[:error] = I18n.t("users.messages.errors.reputation_needed",
676                               :min_reputation => reputation,
677                               :action => I18n.t("users.actions.ask"))
678
679       respond_to do |format|
680         format.html {redirect_to questions_path}
681         format.js {
682           render(:json => {:success => false,
683                            :message => flash[:error] }.to_json)
684         }
685       end
686     end
687   end
688
689   def set_active_tag
690     @active_tag = "tag_#{params[:tags]}" if params[:tags]
691     @active_tag
692   end
693
694   def check_age
695     @question = current_group.questions.by_slug(params[:id])
696
697     if @question.nil?
698       @question = current_group.questions.where(:slugs => params[:id]).only(:_id, :slug).first
699       if @question.present?
700         head :moved_permanently, :location => question_url(@question)
701         return
702       elsif params[:id] =~ /^(\d+)/ && (@question = current_group.questions.where(:se_id => $1)).only(:_id, :slug).first
703         head :moved_permanently, :location => question_url(@question)
704       else
705         raise Error404
706       end
707     end
708
709     return if session[:age_confirmed] || is_bot? || !@question.adult_content
710
711     if !logged_in? || (Date.today.year.to_i - (current_user.birthday || Date.today).year.to_i) < 18
712       render :template => "welcome/confirm_age"
713     end
714   end
715
716   def create_draft!
717     draft = Draft.create!(:question => @question)
718     session[:draft] = draft.id
719     login_required
720   end
721 end