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