Module: Plugins::FrontCache::FrontCacheHelper

Included in:
AdminController
Defined in:
app/apps/plugins/front_cache/front_cache_helper.rb

Constant Summary collapse

FRONT_CACHE_EXPIRATION =

Upper bound on any stored page's life. Stores whose purge is rescued away (RedisCacheStore and MemCacheStore reject the matcher) would otherwise keep retired or never-revisited entries forever — and Redis's default maxmemory-policy is noeviction, so TTL-less bodies would grow until the shared store refuses writes. An expired entry is an ordinary miss: the page is re-rendered and re-cached.

1.week

Instance Method Summary collapse

Instance Method Details

#front_cache_before_loadObject

expire cache for a page after comment registered or updated



111
# File 'app/apps/plugins/front_cache/front_cache_helper.rb', line 111

def front_cache_before_load; end

#front_cache_cleanObject

invalidate all cached pages of the current site



132
133
134
135
136
137
138
139
140
141
142
143
# File 'app/apps/plugins/front_cache/front_cache_helper.rb', line 132

def front_cache_clean
  # Security: never Rails.cache.clear — the store is shared, and clearing it on every POST also
  # destroyed unrelated entries such as the per-IP login brute-force counter (CaptchaHelper),
  # silently defeating it. Bumping the version compared on every page-cache read (ActiveSupport
  # `version:`) retires all of this site's pages on any store, with no store enumeration.
  #
  # The version lives in its own meta key, apart from the front_cache_elements settings hash:
  # save_settings rewrites that hash wholesale from an earlier read, so a version stored inside
  # it could be reverted by a settings save racing a concurrent bump — and a reverted version
  # resurrects a retired generation as servable.
  current_site.set_meta('front_cache_counter', front_cache_version + 1)
end

#front_cache_front_after_loadObject



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'app/apps/plugins/front_cache/front_cache_helper.rb', line 69

def front_cache_front_after_load
  cache_key = front_cache_plugin_cache_key
  return unless @_plugin_do_cache && flash.keys.blank?

  body =
    response
    .body.gsub(/csrf-token" content="(.*?)"/, 'csrf-token" content="{{form_authenticity_token}}"')
    .gsub(
      /name="authenticity_token" value="(.*?)"/, 'name="authenticity_token" value="{{form_authenticity_token}}"'
    )
  args = { data: body }
  hooks_run('front_cache_writing_cache', args)
  front_cache_plugin_cache_create(cache_key, args[:data])
  Rails.logger.info "Camaleon CMS - cache saved as: #{front_cache_plugin_get_path(cache_key)}"
end

#front_cache_front_before_loadObject

cache all pages configured in this plugin's settings for public users



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'app/apps/plugins/front_cache/front_cache_helper.rb', line 11

def front_cache_front_before_load
  # invalidate the site's cached pages on the first request after a (re)start, unless
  # preserve_cache_on_restart is checked in the plugin settings
  if current_site.get_option('refresh_cache')
    front_cache_clean unless (current_site.get_meta('front_cache_elements') || {})[:preserve_cache_on_restart]
    current_site.set_option('refresh_cache', false)
  end

  # avoid cache if the current visitor is logged in, or we're in the development or test environment
  return if signin? || Rails.env.development? || Rails.env.test? || !request.get?

  cache_key = front_cache_plugin_cache_key
  # Fail closed on a missing settings meta: front_cache runs on every frontend request, so an
  # absent front_cache_elements (never seeded, or hand-deleted) must degrade to "cache nothing"
  # rather than raise NoMethodError and 500 the whole public site.
  @caches = current_site.get_meta('front_cache_elements') || {}
  # Single read: the old exist?-then-get pair issued two store reads, and an entry vanishing
  # between them (a concurrent admin purge, TTL expiry) left .gsub running on nil — a
  # visitor-facing 500.
  cached_body = flash.keys.blank? ? front_cache_get(cache_key) : nil
  if cached_body # recover cache item
    Rails.logger.info "Camaleon CMS - readed cache: #{front_cache_plugin_get_path(cache_key)}"
    response.headers['PLUGIN_FRONT_CACHE'] = 'TRUE'
    args = { data: cached_body.gsub('{{form_authenticity_token}}', form_authenticity_token) }
    hooks_run('front_cache_reading_cache', args)
    # rubocop:disable Rails/OutputSafety -- This replays a trusted cached page body that was already rendered by Rails.
    render html: args[:data].html_safe
    # rubocop:enable Rails/OutputSafety
    return
  end

  @_plugin_do_cache = false
  # cache paths and home page
  paths = @caches[:paths] || []
  if paths.include?(request.original_url) || paths.include?(request.path_info) ||
     front_cache_plugin_match_path_patterns?(request.original_url, request.path_info) ||
     (params[:action] == 'index' && params[:controller] == 'camaleon_cms/frontend' && @caches[:home].present?)
    @_plugin_do_cache = true
  elsif params[:action] == 'post' && params[:controller] == 'camaleon_cms/frontend' && params[:draft_id].blank?
    # the_post is a single-record lookup (eager: false) -- no listing preloads for one post
    # Never cache non-public posts. A password-protected post is unlocked per session (visibility_post
    # audit M2), but the page cache is keyed on the URL alone, so caching an unlocked render would
    # serve the protected body to visitors who never entered the password. Private posts are already
    # excluded; password posts must be too.
    if (post = current_site.the_post(params[:slug])) && post.can_visit? && !%w[private
                                                                               password].include?(post.visibility)
      if (@caches[:skip_posts] || []).include?(post.id.to_s)
        @_plugin_do_cache = false
      elsif (@caches[:post_types] || []).include?(post.post_type_id.to_s) ||
            (@caches[:posts] || []).include?(post.id.to_s)
        @_plugin_do_cache = true
      end
    end
  end

  response.headers['PLUGIN_FRONT_CACHE'] = 'TRUE' if @_plugin_do_cache
end

#front_cache_on_active(_plugin) ⇒ Object

on install plugin



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'app/apps/plugins/front_cache/front_cache_helper.rb', line 86

def front_cache_on_active(_plugin)
  return if current_site.get_meta('front_cache_elements', nil).present?

  current_site.set_meta(
    'front_cache_elements',
    {
      paths: [],
      posts: [],
      post_types: [current_site.post_types.where(slug: 'page').first.id],
      skip_posts: [],
      home: true,
      cache_login: true
    }
  )
end

#front_cache_on_inactive(_plugin) ⇒ Object

on uninstall plugin



103
104
105
# File 'app/apps/plugins/front_cache/front_cache_helper.rb', line 103

def front_cache_on_inactive(_plugin)
  # current_site.delete_meta("front_cache_elements")
end

#front_cache_on_render(_args) ⇒ Object

cache actions (for logged users)



108
# File 'app/apps/plugins/front_cache/front_cache_helper.rb', line 108

def front_cache_on_render(_args); end

#front_cache_plugin_options(arg) ⇒ Object



113
114
115
116
# File 'app/apps/plugins/front_cache/front_cache_helper.rb', line 113

def front_cache_plugin_options(arg)
  arg[:links] << link_to(t('plugin.front_cache.settings'), admin_plugins_front_cache_settings_path)
  arg[:links] << link_to(t('plugin.front_cache.clean_cache'), admin_plugins_front_cache_clean_path)
end

#front_cache_post_requestsObject

invalidate the page cache on any content-changing request. PUT and DELETE count: a permanent post deletion rides DELETE (Rack::MethodOverride makes request.post? false for _method=delete forms), and without a bump the deleted page kept being served from cache.



121
122
123
124
125
126
127
128
129
# File 'app/apps/plugins/front_cache/front_cache_helper.rb', line 121

def front_cache_post_requests
  return unless request.post? || request.put? || request.patch? || request.delete?
  # A draft autosave (posts/drafts) writes a private draft_child buffer, never published
  # output, so it must not retire the public page cache — otherwise an open editor's
  # per-minute autosave keeps the whole site's cache from ever warming.
  return if params[:controller].to_s.end_with?('posts/drafts')

  front_cache_clean
end