make sure not to save questions with empty tags
[shapado:piglops-shapado.git] / app / helpers / application_helper.rb
1 # Methods added to this helper will be available to all templates in the application.
2 module ApplicationHelper
3   def widget_title(widget)
4     if !(widget.settings||{})["notitle"]
5       "<h3>#{I18n.t(:"widgets.#{widget.name}.title")}</h3>"
6     end
7   end
8
9   def known_languages(user, group)
10     return group.languages unless logged_in?
11     languages = user.preferred_languages & group.languages
12     (languages.empty?)? group.languages : languages
13   end
14
15   def multiauth_dropdown(title)
16     render 'shared/login_menu', :title => title
17   end
18
19   def with_facebook?
20     return true if current_group.share.fb_active
21
22     if request.host =~ Regexp.new("#{AppConfig.domain}$", Regexp::IGNORECASE)
23       AppConfig.facebook["activate"]
24     else
25       false
26     end
27   end
28
29   def language_json
30     languages = []
31     I18n.t('languages').keys.each do |k| languages << {:caption => I18n.t("languages.#{k}"),
32         :value=>I18n.t("languages.#{k}"), :code => k} end
33     languages.to_json
34   end
35
36   def preferred_languages_code(entity, language_method)
37     if logged_in?
38       entity.send(language_method).map do |code|
39         I18n.t("languages.#{code}")+":#{code}"
40       end
41     else
42       if I18n.locale.to_s != current_group.language &&
43           current_group.languages.include?(I18n.locale.to_s)
44         return [I18n.t("languages.#{I18n.locale}")+":#{I18n.locale}"]
45       end
46       return []
47     end
48   end
49
50   def language_desc(langs)
51     (langs.kind_of?(Array) ? langs : [langs]).map do |lang|
52       I18n.t("languages.#{lang}", :default => lang).capitalize
53     end.join(', ')
54   end
55
56   def language_select(f, question, opts = {})
57     languages = current_group.languages
58
59     f.select :language, languages_options(languages), {:selected => selected}, {:class => "select"}.merge(opts)
60   end
61
62   def language_select_tag(name = "language", value = nil, opts = {})
63     languages = logged_in? ? current_user.preferred_languages : current_group.languages
64     select_tag name, options_for_select(languages_options(languages)), {:value => value, :class => "select"}.merge(opts)
65   end
66
67   def languages_options(languages=nil, current_languages = [])
68     languages = AVAILABLE_LANGUAGES-current_languages if languages.blank?
69     locales_options(languages)
70   end
71
72   def locales_options(languages=nil)
73     languages = AVAILABLE_LOCALES if languages.blank?
74
75     languages.collect do |lang|
76       [language_desc(lang), lang]
77     end
78   end
79
80   def locales_roles
81     roles = []
82     Membership::ROLES.each do |role|
83       roles << [I18n.t("roles.#{role}"), role]
84     end
85     roles
86   end
87
88   def tag_cloud(tags = [], options = {}, limit = nil, style = "tag_cloud")
89     if tags.empty?
90       tags = Question.tag_cloud({:group_id => current_group.id, :banned => false}.
91                         merge(language_conditions.merge(language_conditions)), limit)
92     end
93
94     return '' if tags.size <= 2
95
96     # Sizes: xxs xs s l xl xxl
97     css = {1 => "xxs", 2 => "xs", 3 => "s", 4 => "l", 5 => "xl" }
98     max_size = 5
99     min_size = 1
100
101     tag_class = options.delete(:tag_class) || "tag"
102
103     lowest_value = tags.min { |a, b| a["count"].to_i <=> b["count"].to_i }
104     highest_value = tags.max { |a, b| a["count"].to_i <=> b["count"].to_i }
105
106     spread = (highest_value["count"] - lowest_value["count"])
107     spread = 1 if spread == 0
108     ratio = (max_size - min_size) / spread
109
110     render 'shared/tag_cloud', :tags => tags, :css => css,
111                                :lowest_value => lowest_value, :ratio => ratio,
112                                :min_size => min_size, :tag_class => tag_class, :style => style
113   end
114
115   def country_flag(code, name)
116     if code
117       image_tag("flags/flag_#{code.downcase}.gif", :title => name, :alt => "")
118     end
119   end
120
121   def markdown(txt, options = {})
122     raw = options.delete(:raw)
123     body = render_page_links(txt.to_s, options)
124     txt = if raw
125       (defined?(RDiscount) ? RDiscount.new(body) : Maruku.new(body)).to_html
126     else
127       (defined?(RDiscount) ? RDiscount.new(body, :smart, :strict) : Maruku.new(sanitize(body))).to_html
128     end
129
130     if options[:sanitize] != false
131       txt = defined?(Sanitize) ? Sanitize.clean(txt, SANITIZE_CONFIG) : sanitize(txt)
132     end
133     txt.html_safe
134   end
135
136   def render_page_links(text, options = {})
137     group = options[:group]
138     group = current_group if group.nil?
139     in_controller = respond_to?(:logged_in?)
140
141     text.gsub!(/\[\[([^\,\[\'\"]+)\]\]/) do |m|
142       link = $1.split("|", 2)
143       # FIXME mongoid .only(:title, :slug).where()
144       page = Page.by_title(link.first, :group_id => group.id)
145
146
147       if page.present?
148         %@<a href="/pages/#{page.slug}" class="page_link">#{link[1] || page.title}</a>@
149       else
150         %@<a href="/pages/#{link.first.parameterize.to_s}?create=true&title=#{link.first}" class="missing_page">#{link.last}</a>@
151       end
152     end
153
154     return text if !in_controller
155
156     text.gsub(/%(\S+)%/) do |m|
157       case $1
158         when 'site'
159           group.domain
160         when 'site_name'
161           group.name
162         when 'current_user'
163           if logged_in?
164             link_to(current_user.login, user_path(current_user))
165           else
166             "anonymous"
167           end
168         when 'hottest_today'
169           question = Question.where(:activity_at.gt => Time.zone.now.yesterday, :order => "hotness desc, views_count asc", :group_id => group.id, :select => [:slug, :title]).first
170           if question.present?
171             link_to(question.title, question_path(question))
172           end
173         else
174           m
175       end
176     end
177   end
178
179   def format_number(number)
180     return if number.nil?
181
182     if number < 1000
183       number.to_s
184     elsif number >= 1000 && number < 1000000
185       "%.01fK" % (number/1000.0)
186     elsif number >= 1000000
187       "%.01fM" % (number/1000000.0)
188     end
189   end
190
191   def class_for_number(number)
192     return if number.nil?
193
194     if number >= 1000 && number < 10000
195       "medium_number"
196     elsif number >= 10000
197       "big_number"
198     elsif number < 0
199       "negative_number"
200     end
201   end
202
203   def shapado_auto_link(text, options = {})
204     text = auto_link(text, :all,  { "rel" => 'nofollow', :class => 'auto-link' })
205     if options[:link_users]
206       text = TwitterRenderer.auto_link_usernames_or_lists(text, :username_url_base => "#{users_path}/", :suppress_lists => true)
207     end
208
209     text
210   end
211
212   def format_article_date(date, short)
213     now = Time.now
214     if short
215       if date.today?
216         date.strftime("%I:%M %p")
217       elsif now.yesterday.beginning_of_day < date && date < now.yesterday.end_of_day
218         "#{I18n.t("time.yesterday")} #{date.strftime("%I:%M %p")}"
219       else
220         "#{I18n.t("date.abbr_month_names")[date.month]} #{date.day}, #{date.year}"
221       end
222     else
223       I18n.l(date)
224     end
225   end
226
227   def article_date(article, short = true)
228     out = ""
229     out << format_article_date(article.created_at, short)
230   end
231
232   def edited_date(article, short = true)
233     out = ""
234     out << " ("
235     out << t('global.edited')
236     out << " "
237     out << format_article_date(article.updated_at, short)
238     out << ")"
239   end
240
241   def require_js(*files)
242     content_for(:js) { javascript_include_tag(*files) }
243   end
244
245   def require_css(*files)
246     content_for(:css) { stylesheet_link_tag(*files) }
247   end
248
249   def render_tag(tag)
250     %@<span class="tag"><a href="#{questions_path(:tags => tag)}">#{@badge.token}</a></span>@
251   end
252
253   def class_for_question(question)
254     klass = "Question "
255
256     if question.accepted
257       klass << "accepted"
258     elsif !question.answered
259       klass << "unanswered"
260     end
261
262     if logged_in?
263       if current_user.is_preferred_tag?(current_group, *question.tags)
264         klass << " highlight"
265       end
266
267       if current_user == question.user
268         klass << " own_question"
269       end
270     end
271
272     klass
273   end
274
275   def googlean_script(analytics_id, domain)
276     raw %Q{<script type="text/javascript">var _gaq=_gaq||[];_gaq.push(["_setAccount","#{analytics_id}"]);_gaq.push(["_setDomainName","#{domain}"]);_gaq.push(["_setAllowHash","false"]);_gaq.push(["_trackPageview"]);(function(){var ga=document.createElement("script");ga.type="text/javascript";ga.async=true;ga.src=("https:"==document.location.protocol?"https://ssl":"http://www")+".google-analytics.com/ga.js";var s=document.getElementsByTagName("script")[0];s.parentNode.insertBefore(ga,s)})();</script>}
277   end
278
279   def logged_out_language_filter
280     custom_lang = session["user.language_filter"]
281     case custom_lang
282     when "any"
283       languages = "any"
284     else
285       languages = session["user.language_filter"] || I18n.locale.to_s.split('-').first
286     end
287     languages
288   end
289
290   def clean_seo_keywords(tags, text = "")
291     if tags.size < 5
292       text.scan(/\S+/) do |s|
293         word = s.to_s.downcase
294         if word.length > 3 && !tags.include?(word)
295           tags << word
296         end
297
298         break if tags.size >= 5
299       end
300     end
301
302     tags.join(', ')
303   end
304
305   def current_announcements(hide_time = nil)
306     conditions = {:starts_at.lte => Time.zone.now.to_i,
307                   :ends_at.gte => Time.zone.now.to_i,
308                   :group_id.in => [current_group.id, nil]}
309     if hide_time
310       conditions[:updated_at] = {:$gt => hide_time}
311     end
312
313     if logged_in?
314       conditions[:only_anonymous] = false
315     end
316
317     Announcement.where(conditions).order_by(:starts_at.desc)
318   end
319
320   def top_bar_links
321     top_bar = raw(current_group.custom_html.top_bar)
322     return [] if top_bar.blank?
323
324     top_bar.split("\n").map do |line|
325       render_page_links(line.strip)
326     end
327   end
328
329   def gravatar(*args)
330     super(*args).html_safe
331   end
332
333   def include_latex
334     if current_group.enable_latex
335       require_css 'http://fonts.googleapis.com/css?family=UnifrakturMaguntia'
336       jqmath_tags = %{<meta data-jqmath data-jsassets="cssassets.jqmath" data-cssassets="jsassets.jqmath">}
337       raw(jqmath_tags)
338     end
339   end
340
341   def find_answer(question)
342     if question.accepted
343       question.answer
344     else
345       question.answers.order_by(:votes_average.asc).first
346     end
347   end
348
349   def widget_css(widget)
350     "<style type='text/css'>#{widget.settings["custom_external_css"]}</style>"
351   end
352
353   def widget_code(widget)
354     path = embedded_widget_path(:id => widget.id)
355     url = domain_url(:custom => current_group.domain) + path
356     %@<iframe src="#{url}" height="200px"></iframe>@
357   end
358
359   def facebook_avatar(user)
360     image_tag("http://graph.facebook.com/#{user.facebook_id}/picture")
361   end
362
363   def twitter_avatar(user)
364     if user.user_info["twitter"]["image"]
365       image_tag(user.user_info["twitter"]["image"])
366     else
367       gravatar(user.email.to_s, :size => 32)
368     end
369   end
370
371   def identica_avatar(user)
372     image_tag(user.user_info["identica"]["image"])
373   end
374
375   #TODO css for image tag size
376   def linked_in_avatar(user)
377     image_tag(user.user_info["linked_in"]["image"])
378   end
379
380   def suggestion_avatar(suggestion)
381     if suggestion.class == User
382       avatar_tag = if  suggestion.twitter_login?
383                      twitter_avatar(suggestion)
384                    elsif suggestion.identica_login?
385                      identica_avatar(suggestion)
386                    elsif suggestion.linked_in_login?
387                      linked_in_avatar(suggestion)
388                    else
389                      gravatar(suggestion.email.to_s, :size => 32)
390                    end
391     else
392       tag = Tag.where(:name => suggestion[0], :group_id => current_group.id).first
393       avatar_tag = tag_icon_image_link(tag) if tag
394     end
395     avatar_tag
396   end
397
398   def tag_icon_image_link(tag)
399     image_tag(tag_icon_path(current_group, tag)) if tag.has_icon?
400   end
401
402   def common_follower(user, suggestion)
403     if suggestion.class == User
404       suggested_friend = suggestion
405       friend = user.common_follower(suggested_friend)
406     elsif (suggestion[1] && suggestion[1]["followed_by"])
407       friend = suggestion[1]["followed_by"].sample
408     end
409     if friend
410       raw(t('widgets.suggestions.followed_by', :user => "#{link_to friend.login, user_path(friend)}"))
411     end
412   end
413
414   def suggestion_link(suggestion)
415     if suggestion.class == User
416       link_to(suggestion.login, user_path(suggestion))
417     else
418       tag_link(suggestion[0])
419     end
420   end
421
422   def follow_suggestion_link(suggestion)
423     if suggestion.class == User
424       link_to "+ #{t("users.show.follow")} User", follow_user_path(suggestion), :class => "follow_link toggle-action", 'data-class' => "unfollow_link", 'data-text' => t("users.show.unfollow"), 'data-undo' => unfollow_user_path(suggestion), :rel => "nofollow"
425     else
426       follow_tag_link(Tag.where(:name => suggestion[0], :group_id => current_group.id).first)
427     end
428   end
429
430   def follow_tag_link(tag)
431     if logged_in?
432       if current_user.preferred_tags_on(current_group).include?(tag.name)
433         follow_class = 'unfollow-tag toggle-action'
434         follow_data = 'follow-tag'
435         data_title = t("global.follow")
436         title = t("global.unfollow")
437         path = unfollow_tags_users_path(:tags => tag.name)
438         data_undo = follow_tags_users_path(:tags => tag.name)
439       else
440         follow_data = 'unfollow-tag'
441         follow_class = 'follow-tag toggle-action'
442         data_title = t("global.unfollow")
443         title = 'Follow tag'
444         opt = 'add'
445         path = follow_tags_users_path(:tags => tag.name)
446         data_undo = unfollow_tags_users_path(:tags => tag.name)
447       end
448       link_to title, path, :class => follow_class, 'data-tag' => tag.name, 'data-class' => follow_data, 'data-text' => data_title, 'data-undo' => data_undo
449     end
450   end
451
452   def tag_link(tag)
453     link_to h(tag), tag_path(:id => tag), :rel => "tag", :title => t("questions.tags.tooltip", :tag => tag), :class => "tag" unless tag.blank?
454   end
455
456   def widgets_context(controller, action)
457     @widgets_context ||= (controller == "questions" && action == "show" && @question.present?) ? 'question' : 'mainlist'
458   end
459
460   def cache_for(name, *args, &block)
461     cache(cache_key_for(name, *args), &block)
462   end
463
464   def cache_key_for(name, *args)
465     args.unshift([name, current_group.id, params[:controller], params[:action]])
466     if user_signed_in?
467       args += [current_user.role_on(current_group.to_s)]
468       args += current_user.preferred_languages.sort
469     else
470       args << current_group.language
471     end
472
473     args
474   end
475
476   def payment_form(title, options = {})
477     render :partial => "invoices/form", :locals => {:opts => options.merge(:title => title)}
478   end
479 end
480