Module: ApplicationHelper

Includes:
Warden::Test::Helpers
Defined in:
app/helpers/application_helper.rb

Overview

Provides helper methods for use by views under ApplicationController (and by extension, every view).

Instance Method Summary collapse

Instance Method Details

#active_search?(param) ⇒ Boolean

Checks if the search parameter specified is the currently active search.

Parameters:

  • param (String)

    The search parameter.

Returns:

  • (Boolean)


73
74
75
# File 'app/helpers/application_helper.rb', line 73

def active_search?(param)
  $active_search_param == param&.to_sym
end

#admin?Boolean

Is the current user an admin on the current community?

Returns:

  • (Boolean)


15
16
17
# File 'app/helpers/application_helper.rb', line 15

def admin?
  user_signed_in? && current_user.is_admin
end

#check_your_post_privilege(post, privilege) ⇒ Boolean

Checks if the current user has a specified privilege on a post.

Parameters:

  • post (Post)

    A post to use as context for the privilege.

  • privilege (String)

    The internal_id of the privilege to query.

Returns:

  • (Boolean)


24
25
26
# File 'app/helpers/application_helper.rb', line 24

def check_your_post_privilege(post, privilege)
  !current_user.nil? && current_user&.has_post_privilege?(privilege, post)
end

#check_your_privilege(privilege) ⇒ Boolean

Check if the current user has a specified privilege.

Parameters:

  • privilege (String)

    The internal_id of the privilege to query.

Returns:

  • (Boolean)


32
33
34
# File 'app/helpers/application_helper.rb', line 32

def check_your_privilege(privilege)
  !current_user.nil? && current_user&.privilege?(privilege)
end

#current_commitArray(String, DateTime)

Get the current active commit information to display in the footer.

Returns:

  • (Array(String, DateTime))

    Two values: the commit hash and the timestamp.



319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'app/helpers/application_helper.rb', line 319

def current_commit
  commit_info = Rails.cache.persistent('current_commit')
  shasum, timestamp = commit_info

  begin
    date = DateTime.iso8601(timestamp)
  rescue
    date = DateTime.parse(timestamp)
  end

  [shasum, date]
rescue
  [nil, nil]
end

#current_userUser?

Redefined Devise current_user helper. Additionally checks for deleted users - if the current user has been soft deleted, this will sign them out. As current_user is called on every page in the header, this has the effect of immediately signing the user out even if they’re signed in when their account is deleted.

Returns:



287
288
289
290
291
292
293
294
295
296
297
# File 'app/helpers/application_helper.rb', line 287

def current_user
  return nil unless defined?(warden)

  @current_user ||= warden.authenticate(scope: :user)
  if @current_user&.deleted? || @current_user&.community_user&.deleted?
    scope = Devise::Mapping.find_scope!(:user)
    logout scope
    @current_user = nil
  end
  @current_user
end

#deleted_item?(item) ⇒ Boolean

Check if the specified item is deleted.

Parameters:

Returns:

  • (Boolean)


235
236
237
238
239
240
241
242
243
244
# File 'app/helpers/application_helper.rb', line 235

def deleted_item?(item)
  case item.class.to_s
  when 'Post', 'Comment', 'CommunityUser'
    item.deleted
  when 'User'
    item.deleted || item.community_user.deleted
  else
    false
  end
end

#direct_request?Boolean?

Check if the current request is a direct user request, or a resource load.

Returns:

  • (Boolean, nil)

    true if the request is direct, false if not, or nil if it cannot be determined



308
309
310
311
312
313
314
# File 'app/helpers/application_helper.rb', line 308

def direct_request?
  if request.headers['Sec-Fetch-Mode'].present? && request.headers['Sec-Fetch-Mode'] == 'navigate'
    true
  elsif request.headers['Sec-Fetch-Mode'].present?
    false
  end
end

Get a link to edit the specified post.

Parameters:

  • post (Post)

    The post to link to.

Returns:

  • (String)


188
189
190
# File 'app/helpers/application_helper.rb', line 188

def generic_edit_link(post)
  edit_post_url(post)
end

Gets a shareable URL to the specified post, taking into account post type.

Parameters:

  • post (Post)

    The post in question.

Returns:

  • (String)


150
151
152
153
154
155
156
# File 'app/helpers/application_helper.rb', line 150

def generic_share_link(post)
  if second_level_post_types.include?(post.post_type_id)
    answer_post_url(id: post.parent_id, answer: post.id, anchor: "answer-#{post.id}")
  else
    post_url(post)
  end
end

Get a shareable link to the specified post in Markdown format.

Parameters:

  • post (Post)

    The post in question.

Returns:

  • (String)

    The Markdown-formatted link.



162
163
164
# File 'app/helpers/application_helper.rb', line 162

def generic_share_link_md(post)
  "[#{post.title}](#{generic_share_link(post)})"
end

Get a link to the specified post. Also works for help and policy documents.

Parameters:

  • post (Post)

    The post to link to.

Returns:

  • (String)


196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'app/helpers/application_helper.rb', line 196

def generic_show_link(post)
  if top_level_post_types.include? post.post_type_id
    post_url(post)
  elsif second_level_post_types.include?(post.post_type_id)
    post_url(post.parent, anchor: "answer-#{post.id}")
  else
    case post.post_type_id
    when HelpDoc.post_type_id
      help_url(post.doc_slug)
    when PolicyDoc.post_type_id
      policy_url(post.doc_slug)
    else
      '#'
    end
  end
end

#i18ns(key, **subs) ⇒ String

Translate a given string using I18n#t, after substituting values into the string based on a hash.

Examples:

# In I18n config:
# user_post_count: 'You have :count posts on this community.'
helpers.i18ns('user_post_count', count: @posts.size)
# => 'You have 23 posts on this community.'
# or as appropriate based on locale

Parameters:

  • key (String, Symbol)

    The translation key as passed to I18n#t.

  • subs (Hash{#to_s => #to_s})

    A list of substitutions to apply - keys of the form :name in the string should have a corresponding name entry in this hash and will be substituted for the value.

Returns:

  • (String)


259
260
261
262
263
264
265
# File 'app/helpers/application_helper.rb', line 259

def i18ns(key, **subs)
  s = I18n.t key
  subs.each do |f, r|
    s = s.gsub ":#{f}", r.to_s
  end
  s
end

Creates a link to the community’s default content license based on site settings.

Returns:

  • (ActiveSupport::SafeBuffer)

    The result of the link_to call.



65
66
67
# File 'app/helpers/application_helper.rb', line 65

def license_link
  link_to SiteSetting['ContentLicenseName'], SiteSetting['ContentLicenseLink']
end

#moderator?Boolean

Is the current user a moderator on the current community?

Returns:

  • (Boolean)


8
9
10
# File 'app/helpers/application_helper.rb', line 8

def moderator?
  user_signed_in? && (current_user.is_moderator || current_user.is_admin)
end

#post_history_share_link(post, history, index) ⇒ String

Get a shareable link to a point in the specified post’s history.

Parameters:

  • post (Post)

    The post in question.

  • history (ActiveRecord::Collection<PostHistory>)

    The post’s history.

  • index (Integer)

    The index of the history event to link to.

Returns:

  • (String)


172
173
174
# File 'app/helpers/application_helper.rb', line 172

def post_history_share_link(post, history, index)
  post_history_url(post, anchor: history.size - index)
end

#post_history_share_link_md(post, history, index) ⇒ Object

Get a shareable link to a point in the specified post’s history, in Markdown form. Parameters as for #post_history_share_link.



179
180
181
182
# File 'app/helpers/application_helper.rb', line 179

def post_history_share_link_md(post, history, index)
  rev_num = history.size - index
  "[Revision #{rev_num}#{post.title}](#{post_history_share_link(post, history, index)})"
end

Get a list of network promoted posts, ignoring expired entries.

Returns:

  • (Hash{Integer => Integer})

    A hash of post IDs as keys, and Unix entry timestamp as values.



270
271
272
273
# File 'app/helpers/application_helper.rb', line 270

def promoted_posts
  JSON.parse(RequestContext.redis.get('network/promoted_posts') || '{}')
      .select { |_k, v| DateTime.now.to_i - v <= 3600 * 24 * 28 }
end

#query_url(base_url = nil, **params) ⇒ String

Utility to add additional query parameters to a URI.

Parameters:

  • base_url (String, nil) (defaults to: nil)

    A base URI to which to add parameters. If none is specified then the request URI for the current page will be used.

  • params (Hash{#to_s => #to_s})

    A hash of query parameters to add to the URI.

Returns:

  • (String)

    The stringified URI.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'app/helpers/application_helper.rb', line 42

def query_url(base_url = nil, **params)
  uri = URI.parse(request.original_url)
  query = Rack::Utils.parse_nested_query uri.query

  unless base_url.nil?
    base_uri = URI.parse(base_url)
    base_query = Rack::Utils.parse_nested_query base_uri.query
    query = query.merge(base_query)
    uri.path = base_uri.path
  end

  query = query.merge(params.to_h { |k, v| [k.to_s, v.to_s] })
  uri.query = query.map { |k, v| "#{k}=#{v}" }.join('&')
  uri.to_s
end

#read_only?Boolean

Is the network in read-only mode?

Returns:

  • (Boolean)


278
279
280
# File 'app/helpers/application_helper.rb', line 278

def read_only?
  RequestContext.redis.get('network/read_only') == 'true'
end

#render_markdown(markdown) ⇒ String

Render a markdown string to HTML with consistent options.

Parameters:

  • markdown (String)

    The markdown string to render.

Returns:

  • (String)

    The rendered HTML string.



109
110
111
112
113
# File 'app/helpers/application_helper.rb', line 109

def render_markdown(markdown)
  CommonMarker.render_doc(markdown,
                          [:FOOTNOTES, :LIBERAL_HTML_TAG, :STRIKETHROUGH_DOUBLE_TILDE],
                          [:table, :strikethrough, :autolink]).to_html(:UNSAFE)
end

#second_level_post_typesArray<Integer>

Returns a list of second-level post type IDs.

Returns:

  • (Array<Integer>)


142
143
144
# File 'app/helpers/application_helper.rb', line 142

def second_level_post_types
  post_type_ids(is_top_level: false, has_parent: true)
end

#short_number_to_human(*args, **opts) ⇒ String?

Converts a number to short-form humanized display, i.e. 100,000 = 100k. Parameters as for ActiveSupport::NumberHelper#number_to_human

Returns:

  • (String, nil)

    The humanized number.



97
98
99
100
101
# File 'app/helpers/application_helper.rb', line 97

def short_number_to_human(*args, **opts)
  opts = { units: { thousand: 'k', million: 'm', billion: 'b', trillion: 't', quadrillion: 'qd' },
           format: '%n%u' }.merge(opts)
  ActiveSupport::NumberHelper.number_to_human(*args, **opts)
end


58
59
60
# File 'app/helpers/application_helper.rb', line 58

def (title)
  link_to title, new_user_session_url
end

#split_words_max_length(text, max_length) ⇒ Array<String>

Split a string after a specified number of characters, only breaking at word boundaries.

Parameters:

  • text (String)

    The text to split.

  • max_length (Integer)

    The maximum number of characters to leave in the resulting strings.

Returns:

  • (Array<String>)


218
219
220
221
222
223
224
225
226
227
228
229
# File 'app/helpers/application_helper.rb', line 218

def split_words_max_length(text, max_length)
  words = text.split
  splat = [[]]
  words.each do |word|
    # Unless the current last element has enough space to take the extra word, create a new one.
    unless splat[-1].map { |w| w.length + 1 }.sum - 1 <= max_length - word.length
      splat << []
    end
    splat[-1] << word
  end
  splat.map { |s| s.join(' ') }
end

#stat_panel(heading, value, caption: nil) ⇒ ActiveSupport::SafeBuffer

Creates a panel for display of a single statistic. Used in reports.

Parameters:

  • heading (String)

    A title for the panel.

  • value (#to_s)

    The statistic value.

  • caption (String) (defaults to: nil)

    A short explanatory caption to display.

Returns:

  • (ActiveSupport::SafeBuffer)


83
84
85
86
87
88
89
# File 'app/helpers/application_helper.rb', line 83

def stat_panel(heading, value, caption: nil)
  tag.div class: 'stat-panel' do
    tag.h4(heading, class: 'stat-panel-heading') +
      (caption.nil? ? '' : tag.span(caption, class: 'stat-panel-caption')) +
      tag.span(value, class: 'stat-value')
  end
end

#strip_markdown(markdown) ⇒ String

Strip Markdown formatting out of a text string to use in plain-text only environments. This isn’t a perfect way to strip out Markdown, so it should only be used for non-critical things like page descriptions - things that will later be supplemented by the full formatted content.

Parameters:

  • markdown (String)

    The Markdown string to strip.

Returns:

  • (String)

    The plain-text equivalent.



121
122
123
124
125
126
127
128
129
130
# File 'app/helpers/application_helper.rb', line 121

def strip_markdown(markdown)
  # Remove block-level formatting: headers, hr, references, images, HTML tags
  markdown = markdown.gsub(/(?:^#+ +|^-{3,}|^\[[^\]]+\]: ?.+$|^!\[[^\]]+\](?:\([^)]+\)|\[[^\]]+\])$|<[^>]+>)/, '')

  # Remove inline formatting: bold, italic, strike etc.
  markdown = markdown.gsub(/[*_~]+/, '')

  # Remove links and inline images but replace them with their text/alt text.
  markdown.gsub(/!?\[([^\]]+)\](?:\([^)]+\)|\[[^\]]+\])/, '\1')
end

#top_level_post_typesArray<Integer>

Returns a list of top-level post type IDs.

Returns:

  • (Array<Integer>)


135
136
137
# File 'app/helpers/application_helper.rb', line 135

def top_level_post_types
  post_type_ids(is_top_level: true)
end

#user_signed_in?Boolean

Is there a user signed in on this request?

Returns:

  • (Boolean)


302
303
304
# File 'app/helpers/application_helper.rb', line 302

def user_signed_in?
  !!current_user && !current_user.deleted? && !current_user.community_user&.deleted?
end