comfy_middle_seat 4.5.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -1
  3. data/README.md +37 -1
  4. data/app/controllers/comfy/admin/cms/tag_usage_controller.rb +20 -0
  5. data/app/helpers/comfy/admin/cms/tag_usage_helper.rb +21 -0
  6. data/app/models/comfy/cms/layout.rb +1 -1
  7. data/app/views/comfy/admin/cms/tag_usage/_source.html.haml +2 -0
  8. data/app/views/comfy/admin/cms/tag_usage/index.html.haml +54 -0
  9. data/app/views/layouts/comfy/admin/cms/_left.html.haml +3 -0
  10. data/config/initializers/comfy_middle_seat.rb +3 -3
  11. data/config/locales/ar.yml +20 -0
  12. data/config/locales/ca.yml +20 -0
  13. data/config/locales/cs.yml +20 -0
  14. data/config/locales/da.yml +20 -0
  15. data/config/locales/de.yml +20 -0
  16. data/config/locales/en.yml +20 -0
  17. data/config/locales/es.yml +20 -0
  18. data/config/locales/fi.yml +20 -0
  19. data/config/locales/fr.yml +20 -0
  20. data/config/locales/gr.yml +20 -0
  21. data/config/locales/hr.yml +20 -0
  22. data/config/locales/it.yml +20 -0
  23. data/config/locales/ja.yml +20 -0
  24. data/config/locales/nb.yml +20 -0
  25. data/config/locales/nl.yml +20 -0
  26. data/config/locales/pl.yml +20 -0
  27. data/config/locales/pt-BR.yml +20 -0
  28. data/config/locales/ru.yml +20 -0
  29. data/config/locales/sk.yml +20 -0
  30. data/config/locales/sv.yml +20 -0
  31. data/config/locales/tr.yml +20 -0
  32. data/config/locales/uk.yml +20 -0
  33. data/config/locales/zh-CN.yml +20 -0
  34. data/config/locales/zh-TW.yml +20 -0
  35. data/lib/comfy_middle_seat/configuration.rb +3 -3
  36. data/lib/comfy_middle_seat/content/tag_usage.rb +124 -0
  37. data/lib/comfy_middle_seat/content/tag_usage_report.rb +83 -0
  38. data/lib/comfy_middle_seat/content/tags/helper.rb +1 -1
  39. data/lib/comfy_middle_seat/content/tags/mixins/file_content.rb +6 -10
  40. data/lib/comfy_middle_seat/content.rb +2 -0
  41. data/lib/comfy_middle_seat/routing.rb +1 -0
  42. data/lib/comfy_middle_seat/version.rb +1 -1
  43. data/lib/tasks/cms_tag_usage.rake +32 -0
  44. metadata +8 -1
@@ -65,6 +65,26 @@ nl:
65
65
  files: Bestanden
66
66
 
67
67
 
68
+ tag_usage:
69
+ title: Taggebruik
70
+ scope: Alle opgeslagen inhoud van deze site, ook niet-gepubliceerde inhoud. Geen rendering tijdens uitvoering.
71
+ multisite: Dit rapport voor één site is niet volledig voor globale toelatingslijsten voor meerdere sites.
72
+ partial: Deeltemplates
73
+ helper: Hulpmethoden
74
+ template: Templates
75
+ name: Naam
76
+ status: Huidige status
77
+ sources: Bronnen
78
+ used_in: Gebruikt in
79
+ no_results: Geen overeenkomende tags gevonden.
80
+ incomplete: Onvolledig rapport. Sommige inhoud kon niet worden geanalyseerd; bekijk de onderstaande waarschuwingen.
81
+ warnings: Analysewaarschuwingen
82
+ config: Voorgestelde configuratie
83
+ review: Controleer vóór gebruik. Dit voorstel is geen automatische toestemming of beveiligingsgoedkeuring. Er is geen configuratie gewijzigd.
84
+ explicitly_allowed: Expliciet toegestaan
85
+ allowed_by_default: Standaard toegestaan
86
+ blocked: Geblokkeerd
87
+
68
88
  search:
69
89
  submit: Zoeken
70
90
  clear: Wissen
@@ -65,6 +65,26 @@ pl:
65
65
  files: Pliki
66
66
 
67
67
 
68
+ tag_usage:
69
+ title: Użycie znaczników
70
+ scope: Cała zapisana treść tej witryny, także nieopublikowana. Bez renderowania w czasie wykonania.
71
+ multisite: Ten raport jednej witryny nie jest wyczerpujący dla globalnych list dozwolonych elementów wielu witryn.
72
+ partial: Szablony częściowe
73
+ helper: Metody pomocnicze
74
+ template: Szablony
75
+ name: Nazwa
76
+ status: Aktualny stan
77
+ sources: Źródła
78
+ used_in: Użyto w
79
+ no_results: Nie znaleziono pasujących znaczników.
80
+ incomplete: Raport niekompletny. Nie udało się przeanalizować części treści; sprawdź poniższe ostrzeżenia.
81
+ warnings: Ostrzeżenia analizy
82
+ config: Proponowana konfiguracja
83
+ review: Sprawdź przed użyciem. Propozycja nie oznacza automatycznego zezwolenia ani zatwierdzenia bezpieczeństwa. Konfiguracja nie została zmieniona.
84
+ explicitly_allowed: Jawnie dozwolone
85
+ allowed_by_default: Domyślnie dozwolone
86
+ blocked: Zablokowane
87
+
68
88
  search:
69
89
  submit: Szukaj
70
90
  clear: Wyczyść
@@ -65,6 +65,26 @@ pt-BR:
65
65
  files: Arquivos
66
66
 
67
67
 
68
+ tag_usage:
69
+ title: Uso de tags
70
+ scope: Todo o conteúdo salvo deste site, incluindo o não publicado. Sem renderização em tempo de execução.
71
+ multisite: Este relatório de um único site não é exaustivo para listas globais de permissões de vários sites.
72
+ partial: Parciais
73
+ helper: Helpers
74
+ template: Modelos
75
+ name: Nome
76
+ status: Estado atual
77
+ sources: Fontes
78
+ used_in: Usado em
79
+ no_results: Nenhuma tag correspondente encontrada.
80
+ incomplete: Relatório incompleto. Parte do conteúdo não pôde ser analisada; revise os avisos abaixo.
81
+ warnings: Avisos da análise
82
+ config: Configuração proposta
83
+ review: Revise antes de usar. Esta proposta não é uma permissão automática nem uma aprovação de segurança. Nenhuma configuração foi alterada.
84
+ explicitly_allowed: Permitido explicitamente
85
+ allowed_by_default: Permitido por padrão
86
+ blocked: Bloqueado
87
+
68
88
  search:
69
89
  submit: Pesquisar
70
90
  clear: Limpar
@@ -65,6 +65,26 @@ ru:
65
65
  files: Файлы
66
66
 
67
67
 
68
+ tag_usage:
69
+ title: Использование тегов
70
+ scope: Всё сохранённое содержимое этого сайта, включая неопубликованное. Без рендеринга во время выполнения.
71
+ multisite: Этот отчёт по одному сайту не является полным для глобальных списков разрешений нескольких сайтов.
72
+ partial: Частичные шаблоны
73
+ helper: Вспомогательные методы
74
+ template: Шаблоны
75
+ name: Имя
76
+ status: Текущий статус
77
+ sources: Источники
78
+ used_in: Используется в
79
+ no_results: Соответствующие теги не найдены.
80
+ incomplete: Отчёт неполный. Часть содержимого не удалось проанализировать; проверьте предупреждения ниже.
81
+ warnings: Предупреждения анализа
82
+ config: Предлагаемая конфигурация
83
+ review: Проверьте перед использованием. Предложение не является автоматическим разрешением или подтверждением безопасности. Конфигурация не изменена.
84
+ explicitly_allowed: Явно разрешено
85
+ allowed_by_default: Разрешено по умолчанию
86
+ blocked: Заблокировано
87
+
68
88
  search:
69
89
  submit: Поиск
70
90
  clear: Очистить
@@ -65,6 +65,26 @@ sk:
65
65
  files: Súbory
66
66
 
67
67
 
68
+ tag_usage:
69
+ title: Použitie značiek
70
+ scope: Všetok uložený obsah tohto webu vrátane nepublikovaného. Bez vykresľovania počas behu.
71
+ multisite: Tento prehľad jedného webu nie je úplný pre globálne zoznamy povolení viacerých webov.
72
+ partial: Čiastkové šablóny
73
+ helper: Pomocné metódy
74
+ template: Šablóny
75
+ name: Názov
76
+ status: Aktuálny stav
77
+ sources: Zdroje
78
+ used_in: Použité v
79
+ no_results: Nenašli sa žiadne zodpovedajúce značky.
80
+ incomplete: Prehľad nie je úplný. Časť obsahu sa nepodarilo analyzovať; skontrolujte upozornenia nižšie.
81
+ warnings: Upozornenia analýzy
82
+ config: Navrhovaná konfigurácia
83
+ review: Pred použitím skontrolujte. Návrh nie je automatickým povolením ani bezpečnostným schválením. Žiadna konfigurácia nebola zmenená.
84
+ explicitly_allowed: Výslovne povolené
85
+ allowed_by_default: Predvolene povolené
86
+ blocked: Blokované
87
+
68
88
  search:
69
89
  submit: Hľadať
70
90
  clear: Vyčistiť
@@ -65,6 +65,26 @@ sv:
65
65
  files: Filer
66
66
 
67
67
 
68
+ tag_usage:
69
+ title: Tagganvändning
70
+ scope: Allt sparat innehåll på denna webbplats, även opublicerat innehåll. Ingen rendering vid körning.
71
+ multisite: Denna rapport för en webbplats är inte uttömmande för globala tillåtelselistor för flera webbplatser.
72
+ partial: Delvyer
73
+ helper: Hjälpmetoder
74
+ template: Mallar
75
+ name: Namn
76
+ status: Aktuell status
77
+ sources: Källor
78
+ used_in: Används i
79
+ no_results: Inga matchande taggar hittades.
80
+ incomplete: Rapporten är ofullständig. En del innehåll kunde inte analyseras; granska varningarna nedan.
81
+ warnings: Analysvarningar
82
+ config: Föreslagen konfiguration
83
+ review: Granska före användning. Förslaget är inte ett automatiskt tillstånd eller säkerhetsgodkännande. Ingen konfiguration har ändrats.
84
+ explicitly_allowed: Uttryckligen tillåtet
85
+ allowed_by_default: Tillåtet som standard
86
+ blocked: Blockerat
87
+
68
88
  search:
69
89
  submit: Sök
70
90
  clear: Rensa
@@ -65,6 +65,26 @@ tr:
65
65
  files: Dosyalar
66
66
 
67
67
 
68
+ tag_usage:
69
+ title: Etiket kullanımı
70
+ scope: Yayımlanmamış içerik dahil bu sitenin tüm kayıtlı içeriği. Çalışma zamanında işleme yapılmaz.
71
+ multisite: Bu tek site raporu, birden çok sitenin genel izin listeleri için kapsamlı değildir.
72
+ partial: Kısmi şablonlar
73
+ helper: Yardımcı metotlar
74
+ template: Şablonlar
75
+ name: Ad
76
+ status: Geçerli durum
77
+ sources: Kaynaklar
78
+ used_in: Kullanıldığı yerler
79
+ no_results: Eşleşen etiket bulunamadı.
80
+ incomplete: Rapor eksik. Bazı içerikler taranamadı; aşağıdaki uyarıları inceleyin.
81
+ warnings: Tarama uyarıları
82
+ config: Önerilen yapılandırma
83
+ review: Kullanmadan önce inceleyin. Bu öneri otomatik izin veya güvenlik onayı değildir. Hiçbir yapılandırma değiştirilmedi.
84
+ explicitly_allowed: Açıkça izin verilmiş
85
+ allowed_by_default: Varsayılan olarak izin verilmiş
86
+ blocked: Engellenmiş
87
+
68
88
  search:
69
89
  submit: Ara
70
90
  clear: Temizle
@@ -65,6 +65,26 @@ uk:
65
65
  files: Файли
66
66
 
67
67
 
68
+ tag_usage:
69
+ title: Використання тегів
70
+ scope: Увесь збережений вміст цього сайту, включно з неопублікованим. Без рендерингу під час виконання.
71
+ multisite: Цей звіт для одного сайту не є вичерпним для глобальних списків дозволів кількох сайтів.
72
+ partial: Часткові шаблони
73
+ helper: Допоміжні методи
74
+ template: Шаблони
75
+ name: Назва
76
+ status: Поточний стан
77
+ sources: Джерела
78
+ used_in: Використано в
79
+ no_results: Відповідних тегів не знайдено.
80
+ incomplete: Звіт неповний. Частину вмісту не вдалося проаналізувати; перегляньте попередження нижче.
81
+ warnings: Попередження аналізу
82
+ config: Запропонована конфігурація
83
+ review: Перевірте перед використанням. Пропозиція не є автоматичним дозволом або підтвердженням безпеки. Конфігурацію не змінено.
84
+ explicitly_allowed: Явно дозволено
85
+ allowed_by_default: Дозволено за замовчуванням
86
+ blocked: Заблоковано
87
+
68
88
  search:
69
89
  submit: Пошук
70
90
  clear: Очистити
@@ -65,6 +65,26 @@ zh-CN:
65
65
  files: 文件
66
66
 
67
67
 
68
+ tag_usage:
69
+ title: 标签使用情况
70
+ scope: 此站点的所有已保存内容,包括未发布的内容。不进行运行时渲染。
71
+ multisite: 此报告仅涵盖当前站点,不能完整反映多站点全局允许列表的需求。
72
+ partial: 局部模板
73
+ helper: 辅助方法
74
+ template: 模板
75
+ name: 名称
76
+ status: 当前状态
77
+ sources: 来源
78
+ used_in: 使用位置
79
+ no_results: 未找到匹配的标签。
80
+ incomplete: 报告不完整。部分内容无法扫描,请查看下面的警告。
81
+ warnings: 扫描警告
82
+ config: 候选配置
83
+ review: 使用前请审核。此候选配置不代表自动授权或安全批准。未更改任何配置。
84
+ explicitly_allowed: 明确允许
85
+ allowed_by_default: 默认允许
86
+ blocked: 已阻止
87
+
68
88
  search:
69
89
  submit: 搜索
70
90
  clear: 清除
@@ -65,6 +65,26 @@ zh-TW:
65
65
  files: 文件
66
66
 
67
67
 
68
+ tag_usage:
69
+ title: 標籤使用情況
70
+ scope: 此站點的所有已儲存內容,包括未發佈的內容。不進行執行時渲染。
71
+ multisite: 此報告僅涵蓋目前站點,無法完整反映多站點全域允許清單的需求。
72
+ partial: 局部範本
73
+ helper: 輔助方法
74
+ template: 範本
75
+ name: 名稱
76
+ status: 目前狀態
77
+ sources: 來源
78
+ used_in: 使用位置
79
+ no_results: 找不到符合的標籤。
80
+ incomplete: 報告不完整。部分內容無法掃描,請查看下方的警告。
81
+ warnings: 掃描警告
82
+ config: 候選設定
83
+ review: 使用前請審核。此候選設定不代表自動授權或安全核准。未變更任何設定。
84
+ explicitly_allowed: 明確允許
85
+ allowed_by_default: 預設允許
86
+ blocked: 已封鎖
87
+
68
88
  search:
69
89
  submit: 搜尋
70
90
  clear: 清除
@@ -53,9 +53,9 @@ class ComfyMiddleSeat::Configuration
53
53
  # @return [Boolean]
54
54
  attr_accessor :allow_erb
55
55
 
56
- # Whitelist of all helper methods that can be used via {{cms:helper}} tag. By default
57
- # all helpers are allowed except `eval`, `send`, `call` and few others. Empty array
58
- # will prevent rendering of all helpers.
56
+ # Allowlist of helper methods available via {{cms:helper}}. With no array configured,
57
+ # all helpers are allowed except `eval`, `class_eval`, `instance_eval`, and `render`.
58
+ # An explicit array allows only its listed names; an empty array blocks all helpers.
59
59
  attr_accessor :allowed_helpers
60
60
 
61
61
  # Whitelist of partials paths that can be used via {{cms:partial}} tag. All partials
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Inventory raw CMS content without expanding tags or consulting rendered caches.
4
+ class ComfyMiddleSeat::Content::TagUsage
5
+ BATCH_SIZE = 1000
6
+ KINDS = {
7
+ 'partial' => 'partial',
8
+ 'render_partial' => 'partial',
9
+ 'helper' => 'helper',
10
+ 'template' => 'template',
11
+ 'render_template' => 'template'
12
+ }.freeze
13
+
14
+ def initialize(sites: nil, scope: nil)
15
+ @sites = sites || Comfy::Cms::Site.all
16
+ @scope = scope || (sites.nil? ? 'all_sites' : 'selected_sites')
17
+ end
18
+
19
+ def call
20
+ @renderer = ComfyMiddleSeat::Content::Renderer.new(nil)
21
+ @entries = {}
22
+ @warnings = []
23
+ @source_count = 0
24
+ @allowlists = %w[partial helper template].to_h do |kind|
25
+ allowed = ComfyMiddleSeat.config.public_send("allowed_#{kind}s")
26
+ next [kind, nil] unless allowed.is_a?(Array)
27
+
28
+ # Only helper rendering stringifies allowlists; view paths use exact membership.
29
+ [kind, kind == 'helper' ? allowed.map(&:to_s) : allowed.dup]
30
+ end
31
+
32
+ sites = site_details
33
+ site_ids = sites.map { |site| site[:id] }
34
+ scan_content(Comfy::Cms::Layout.where(site_id: site_ids), 'layout')
35
+ scan_content(Comfy::Cms::Snippet.where(site_id: site_ids), 'snippet')
36
+ scan_fragments(Comfy::Cms::Page.where(site_id: site_ids), 'page')
37
+ scan_fragments(Comfy::Cms::Translation.joins(:page).where(comfy_cms_pages: { site_id: site_ids }), 'translation')
38
+
39
+ {
40
+ scope: @scope,
41
+ sites: sites,
42
+ source_count: @source_count,
43
+ complete: @warnings.empty?,
44
+ entries: @entries.sort_by { |key, _entry| key }.map { |_key, entry| entry.merge(sources: entry[:sources].values) },
45
+ warnings: @warnings
46
+ }
47
+ end
48
+
49
+ private
50
+
51
+ def site_details
52
+ sites = []
53
+ collect = ->(site) { sites << { id: site.id, identifier: site.identifier, label: site.label } }
54
+ if @sites.respond_to?(:find_each)
55
+ @sites.find_each(batch_size: BATCH_SIZE, &collect)
56
+ else
57
+ @sites.each(&collect)
58
+ end
59
+ sites.uniq { |site| site[:id] }.sort_by { |site| site[:id] }
60
+ end
61
+
62
+ def scan_content(records, type)
63
+ records.select(:id, :site_id, :label, :content).readonly.find_each(batch_size: BATCH_SIZE) do |record|
64
+ scan(record.content, {
65
+ site_id: record.site_id, type: type, id: record.id,
66
+ label: record.label, field: 'content', page_id: nil
67
+ })
68
+ end
69
+ end
70
+
71
+ def scan_fragments(records, type)
72
+ table = records.klass.table_name
73
+ columns = ["#{table}.id", "#{table}.label", 'comfy_cms_pages.site_id']
74
+ columns << "#{table}.page_id" if type == 'translation'
75
+
76
+ # Pluck owner metadata rather than loading pages, their caches or associations.
77
+ records.in_batches(of: BATCH_SIZE) do |batch|
78
+ sources = batch.pluck(*columns).to_h do |id, label, site_id, page_id|
79
+ [id, { site_id: site_id, type: type, id: id, label: label, page_id: page_id }]
80
+ end
81
+ fragments = Comfy::Cms::Fragment.where(record_type: records.klass.polymorphic_name, record_id: sources.keys)
82
+ fragments.select(:id, :record_id, :identifier, :content).readonly.find_each(batch_size: BATCH_SIZE) do |fragment|
83
+ content = fragment.content
84
+ next unless content.is_a?(String)
85
+
86
+ scan(content, sources.fetch(fragment.record_id).merge(field: fragment.identifier))
87
+ end
88
+ end
89
+ end
90
+
91
+ def scan(content, source)
92
+ @source_count += 1
93
+ @renderer.tokenize(content).each do |token|
94
+ next unless token.is_a?(Hash) && (kind = KINDS[token[:tag_class]])
95
+
96
+ scan_token(token, kind, source)
97
+ end
98
+ rescue ComfyMiddleSeat::Content::Renderer::SyntaxError => e
99
+ @warnings << { source: source, message: "Cannot tokenize source: #{e.message}" }
100
+ end
101
+
102
+ def scan_token(token, kind, source)
103
+ name = ComfyMiddleSeat::Content::ParamsParser.new(token[:tag_params]).params.first
104
+ unless name.is_a?(String) && name.present?
105
+ @warnings << { source: source, message: "Missing, blank or non-string name in #{token[:source]}" }
106
+ return
107
+ end
108
+
109
+ entry = @entries[[kind, name]] ||= { kind: kind, name: name, status: status(kind, name), sources: {} }
110
+ entry[:sources][source] = source
111
+ rescue ComfyMiddleSeat::Content::ParamsParser::Error => e
112
+ @warnings << { source: source, message: "Cannot parse #{token[:source]}: #{e.message}" }
113
+ end
114
+
115
+ def status(kind, name)
116
+ if (allowed = @allowlists[kind])
117
+ allowed.include?(name) ? 'explicitly_allowed' : 'blocked'
118
+ elsif kind == 'helper' && ComfyMiddleSeat::Content::Tags::Helper::BLACKLIST.include?(name)
119
+ 'blocked'
120
+ else
121
+ 'allowed_by_default'
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ class ComfyMiddleSeat::Content::TagUsageReport
6
+ KINDS = %w[partial helper template].freeze
7
+ STATUSES = {
8
+ 'explicitly_allowed' => 'Explicitly allowed',
9
+ 'allowed_by_default' => 'Allowed by default',
10
+ 'blocked' => 'Blocked'
11
+ }.freeze
12
+
13
+ def initialize(report)
14
+ @report = report
15
+ end
16
+
17
+ def json
18
+ JSON.pretty_generate(@report)
19
+ end
20
+
21
+ def text
22
+ lines = [
23
+ "Tag usage — #{scope}",
24
+ "Sites: #{@report[:sites].map { |site| site[:identifier].inspect }.join(', ')}",
25
+ "Sources scanned: #{@report[:source_count]}",
26
+ @report[:complete] ? 'Scan complete.' : 'Scan incomplete; review warnings.',
27
+ 'Counts are saved source fields, not runtime executions.'
28
+ ]
29
+ KINDS.each do |kind|
30
+ lines << "\n#{kind.capitalize}s"
31
+ entries = entries_for(kind)
32
+ lines << ' No matching tags found.' if entries.empty?
33
+ entries.each do |entry|
34
+ lines << " #{entry[:name].inspect} — #{STATUSES.fetch(entry[:status])} — #{entry[:sources].size} source(s)"
35
+ end
36
+ end
37
+ "#{lines.join("\n")}\n"
38
+ end
39
+
40
+ def config
41
+ lines = [
42
+ '# Candidate allowlists only. Review every name before applying.',
43
+ '# Discovery is not security approval; blocked names are included.',
44
+ '# Replacing allowlists blocks omitted names, including existing approved uses.',
45
+ "# Scope: #{scope}. Sites: #{@report[:sites].map { |site| site[:identifier].inspect }.join(', ')}"
46
+ ]
47
+ if @report[:scope] != 'all_sites'
48
+ lines << '# Site-filtered report: NOT a complete application-wide allowlist.'
49
+ end
50
+ lines << '# INCOMPLETE SCAN: resolve warnings before using these lists.' unless @report[:complete]
51
+ lines << 'ComfyMiddleSeat.configure do |config|'
52
+ KINDS.each do |kind|
53
+ names = entries_for(kind).map { |entry| entry[:name] }.uniq.sort
54
+ if names.empty?
55
+ lines << " config.allowed_#{kind}s = []"
56
+ else
57
+ lines << " config.allowed_#{kind}s = ["
58
+ names.each { |name| lines << " #{name.inspect}," }
59
+ lines << ' ]'
60
+ end
61
+ end
62
+ lines << 'end'
63
+ "#{lines.join("\n")}\n"
64
+ end
65
+
66
+ def warnings
67
+ @report[:warnings].map do |warning|
68
+ source = warning[:source]
69
+ location = "site #{source[:site_id]}, #{source[:type]} #{source[:id]}, field #{source[:field].inspect}"
70
+ "Warning (#{location}): #{warning[:message].inspect}"
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def scope
77
+ @report[:scope] == 'all_sites' ? 'all sites' : 'selected sites'
78
+ end
79
+
80
+ def entries_for(kind)
81
+ @report[:entries].select { |entry| entry[:kind] == kind }.sort_by { |entry| entry[:name] }
82
+ end
83
+ end
@@ -41,7 +41,7 @@ class ComfyMiddleSeat::Content::Tags::Helper < ComfyMiddleSeat::Content::Tag
41
41
  def render
42
42
  whitelist = ComfyMiddleSeat.config.allowed_helpers
43
43
  if whitelist.is_a?(Array)
44
- content if whitelist.map!(&:to_s).member?(method_name)
44
+ content if whitelist.map(&:to_s).member?(method_name)
45
45
  else
46
46
  content unless BLACKLIST.member?(method_name)
47
47
  end
@@ -24,22 +24,18 @@ module ComfyMiddleSeat::Content::Tags::Mixins
24
24
  url_helpers.rails_blob_path(file, only_path: true)
25
25
  end
26
26
 
27
+ helpers = ActionController::Base.helpers
28
+
29
+ # Blob and variant URLs are not pipeline assets; HTML builders keep them
30
+ # relative instead of applying image_tag's asset-host settings.
27
31
  case as
28
32
  when 'link'
29
- "<a href='#{attachment_url}'#{html_class_attribute} target='_blank'>#{label}</a>"
33
+ helpers.content_tag(:a, label, href: attachment_url, class: @class.presence, target: '_blank', rel: 'noopener')
30
34
  when 'image'
31
- "<img src='#{attachment_url}'#{html_class_attribute} alt='#{label}' title='#{label}'/>"
35
+ helpers.tag.img(src: attachment_url, class: @class.presence, alt: label, title: label)
32
36
  else
33
37
  attachment_url
34
38
  end
35
39
  end
36
-
37
- private
38
-
39
- def html_class_attribute
40
- return if @class.blank?
41
-
42
- " class='#{@class}'"
43
- end
44
40
  end
45
41
  end
@@ -9,3 +9,5 @@ require_relative 'content/params_parser'
9
9
  require_relative 'content/tag'
10
10
  require_relative 'content/tags'
11
11
  require_relative 'content/block'
12
+ require_relative 'content/tag_usage'
13
+ require_relative 'content/tag_usage_report'
@@ -47,6 +47,7 @@ module ComfyMiddleSeat::Routing
47
47
  end
48
48
 
49
49
  resources :files, concerns: [:with_reorder]
50
+ get :tag_usage, to: 'tag_usage#index'
50
51
 
51
52
  resources :layouts do
52
53
  concerns :with_reorder
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ComfyMiddleSeat
4
- VERSION = '4.5.0'
4
+ VERSION = '4.6.0'
5
5
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :comfy do
4
+ namespace :cms do
5
+ # The development application and engine share a root, so Rails loads this file twice.
6
+ if Rake::Task.task_defined?('comfy:cms:tag_usage') && Rake::Task['comfy:cms:tag_usage'].actions.any? { |action| action.source_location&.first == __FILE__ }
7
+ next
8
+ end
9
+
10
+ desc 'Inventory CMS partials, helpers and templates (SITE=identifier FORMAT=text|json|config)'
11
+ task tag_usage: :environment do
12
+ format = ENV.fetch('FORMAT', 'text')
13
+ abort 'FORMAT must be text, json or config.' unless %w[text json config].include?(format)
14
+
15
+ sites = Comfy::Cms::Site.all
16
+ scope = 'all_sites'
17
+ if ENV.key?('SITE')
18
+ identifier = ENV.fetch('SITE')
19
+ sites = sites.where(identifier: identifier)
20
+ abort "Unknown site identifier: #{identifier.inspect}" unless sites.exists?
21
+
22
+ scope = 'selected_sites'
23
+ end
24
+
25
+ report = ComfyMiddleSeat::Content::TagUsage.new(sites: sites, scope: scope).call
26
+ output = ComfyMiddleSeat::Content::TagUsageReport.new(report)
27
+ $stdout.puts output.public_send(format)
28
+ output.warnings.each { |warning| warn warning }
29
+ abort 'Tag usage scan incomplete; resolve warnings and scan again.' unless report[:complete]
30
+ end
31
+ end
32
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: comfy_middle_seat
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.5.0
4
+ version: 4.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oleg Khabarov
@@ -416,12 +416,14 @@ files:
416
416
  - app/controllers/comfy/admin/cms/revisions/translation_controller.rb
417
417
  - app/controllers/comfy/admin/cms/sites_controller.rb
418
418
  - app/controllers/comfy/admin/cms/snippets_controller.rb
419
+ - app/controllers/comfy/admin/cms/tag_usage_controller.rb
419
420
  - app/controllers/comfy/admin/cms/translations_controller.rb
420
421
  - app/controllers/comfy/cms/assets_controller.rb
421
422
  - app/controllers/comfy/cms/base_controller.rb
422
423
  - app/controllers/comfy/cms/content_controller.rb
423
424
  - app/controllers/concerns/comfy/paginate.rb
424
425
  - app/controllers/concerns/comfy/reorder_action.rb
426
+ - app/helpers/comfy/admin/cms/tag_usage_helper.rb
425
427
  - app/helpers/comfy/admin/cms_helper.rb
426
428
  - app/helpers/comfy/cms_helper.rb
427
429
  - app/models/comfy/cms/categorization.rb
@@ -476,6 +478,8 @@ files:
476
478
  - app/views/comfy/admin/cms/snippets/edit.html.haml
477
479
  - app/views/comfy/admin/cms/snippets/index.html.haml
478
480
  - app/views/comfy/admin/cms/snippets/new.html.haml
481
+ - app/views/comfy/admin/cms/tag_usage/_source.html.haml
482
+ - app/views/comfy/admin/cms/tag_usage/index.html.haml
479
483
  - app/views/comfy/admin/cms/translations/_form.html.haml
480
484
  - app/views/comfy/admin/cms/translations/_sidebar.html.haml
481
485
  - app/views/comfy/admin/cms/translations/edit.html.haml
@@ -578,6 +582,8 @@ files:
578
582
  - lib/comfy_middle_seat/content/params_parser.rb
579
583
  - lib/comfy_middle_seat/content/renderer.rb
580
584
  - lib/comfy_middle_seat/content/tag.rb
585
+ - lib/comfy_middle_seat/content/tag_usage.rb
586
+ - lib/comfy_middle_seat/content/tag_usage_report.rb
581
587
  - lib/comfy_middle_seat/content/tags.rb
582
588
  - lib/comfy_middle_seat/content/tags/asset.rb
583
589
  - lib/comfy_middle_seat/content/tags/audio.rb
@@ -643,6 +649,7 @@ files:
643
649
  - lib/generators/comfy/scaffold/templates/views/show.haml.tt
644
650
  - lib/tasks/cms_assets.rake
645
651
  - lib/tasks/cms_seeds.rake
652
+ - lib/tasks/cms_tag_usage.rake
646
653
  - package-lock.json
647
654
  - package.json
648
655
  - public/favicon.ico