comfy_middle_seat 4.4.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -1
  3. data/README.md +56 -2
  4. data/app/assets/javascripts/comfy/admin/cms/cms_tag_completions.js +3 -3
  5. data/app/controllers/comfy/admin/cms/tag_usage_controller.rb +20 -0
  6. data/app/helpers/comfy/admin/cms/tag_usage_helper.rb +21 -0
  7. data/app/models/comfy/cms/layout.rb +1 -1
  8. data/app/views/comfy/admin/cms/tag_usage/_source.html.haml +2 -0
  9. data/app/views/comfy/admin/cms/tag_usage/index.html.haml +54 -0
  10. data/app/views/layouts/comfy/admin/cms/_left.html.haml +3 -0
  11. data/config/initializers/comfy_middle_seat.rb +3 -3
  12. data/config/locales/ar.yml +20 -0
  13. data/config/locales/ca.yml +20 -0
  14. data/config/locales/cs.yml +20 -0
  15. data/config/locales/da.yml +20 -0
  16. data/config/locales/de.yml +20 -0
  17. data/config/locales/en.yml +20 -0
  18. data/config/locales/es.yml +20 -0
  19. data/config/locales/fi.yml +20 -0
  20. data/config/locales/fr.yml +20 -0
  21. data/config/locales/gr.yml +20 -0
  22. data/config/locales/hr.yml +20 -0
  23. data/config/locales/it.yml +20 -0
  24. data/config/locales/ja.yml +20 -0
  25. data/config/locales/nb.yml +20 -0
  26. data/config/locales/nl.yml +20 -0
  27. data/config/locales/pl.yml +20 -0
  28. data/config/locales/pt-BR.yml +20 -0
  29. data/config/locales/ru.yml +20 -0
  30. data/config/locales/sk.yml +20 -0
  31. data/config/locales/sv.yml +20 -0
  32. data/config/locales/tr.yml +20 -0
  33. data/config/locales/uk.yml +20 -0
  34. data/config/locales/zh-CN.yml +20 -0
  35. data/config/locales/zh-TW.yml +20 -0
  36. data/lib/comfy_middle_seat/configuration.rb +3 -3
  37. data/lib/comfy_middle_seat/content/tag_usage.rb +124 -0
  38. data/lib/comfy_middle_seat/content/tag_usage_report.rb +83 -0
  39. data/lib/comfy_middle_seat/content/tags/asset.rb +12 -17
  40. data/lib/comfy_middle_seat/content/tags/helper.rb +1 -1
  41. data/lib/comfy_middle_seat/content/tags/mixins/file_content.rb +6 -10
  42. data/lib/comfy_middle_seat/content/tags/partial.rb +3 -3
  43. data/lib/comfy_middle_seat/content/tags/template.rb +3 -3
  44. data/lib/comfy_middle_seat/content.rb +2 -0
  45. data/lib/comfy_middle_seat/routing.rb +1 -0
  46. data/lib/comfy_middle_seat/version.rb +1 -1
  47. data/lib/tasks/cms_tag_usage.rake +32 -0
  48. metadata +8 -1
@@ -65,6 +65,26 @@ ja:
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 @@ nb:
65
65
  files: Filer
66
66
 
67
67
 
68
+ tag_usage:
69
+ title: Bruk av tagger
70
+ scope: Alt lagret innhold på dette nettstedet, også upublisert innhold. Ingen rendering under kjøring.
71
+ multisite: Denne rapporten for ett nettsted er ikke uttømmende for globale tillatelseslister på tvers av nettsteder.
72
+ partial: Delvisninger
73
+ helper: Hjelpemetoder
74
+ template: Maler
75
+ name: Navn
76
+ status: Gjeldende status
77
+ sources: Kilder
78
+ used_in: Brukt i
79
+ no_results: Ingen samsvarende tagger funnet.
80
+ incomplete: Rapporten er ufullstendig. Noe innhold kunne ikke analyseres; se advarslene nedenfor.
81
+ warnings: Analyseadvarsler
82
+ config: Foreslått konfigurasjon
83
+ review: Gjennomgå før bruk. Forslaget er ikke automatisk tillatelse eller sikkerhetsgodkjenning. Ingen konfigurasjon er endret.
84
+ explicitly_allowed: Uttrykkelig tillatt
85
+ allowed_by_default: Tillatt som standard
86
+ blocked: Blokkert
87
+
68
88
  search:
69
89
  submit: Søk
70
90
  clear: Tøm
@@ -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
@@ -30,28 +30,23 @@ class ComfyMiddleSeat::Content::Tags::Asset < ComfyMiddleSeat::Content::Tag
30
30
  def content
31
31
  return '' unless layout
32
32
 
33
- base = ComfyMiddleSeat.config.public_cms_path || ''
34
- unless base.ends_with?('/')
35
- base += '/'
36
- end
33
+ url_helpers = Rails.application.routes.url_helpers
34
+ options = { site_id: context.site.id, identifier: layout.identifier, cache_buster: layout.cache_buster, format: @type }
35
+ helpers = ActionController::Base.helpers
37
36
 
37
+ # These are CMS endpoints, not pipeline assets. Use HTML builders to avoid
38
+ # adding asset-host prefixes or applying asset-pipeline settings to the URL.
38
39
  case @type
39
40
  when 'css'
40
- out = "#{base}cms-css/#{context.site.id}/#{@identifier}/#{layout.cache_buster}.css"
41
- if @as == 'tag'
42
- out = "<link href='#{out}' media='screen' rel='stylesheet' type='text/css' />"
43
- end
44
- out
41
+ path = url_helpers.comfy_cms_render_css_path(**options)
42
+ @as == 'tag' ? helpers.tag.link(href: path, media: 'screen', rel: 'stylesheet', type: 'text/css') : path
45
43
  when 'js'
46
- out = "#{base}cms-js/#{context.site.id}/#{@identifier}/#{layout.cache_buster}.js"
47
- if @as == 'tag'
48
- out = "<script src='#{out}' type='text/javascript'></script>"
49
- end
50
- out
44
+ path = url_helpers.comfy_cms_render_js_path(**options)
45
+ @as == 'tag' ? helpers.content_tag(:script, '', src: path, type: 'text/javascript') : path
51
46
  end
52
47
  end
53
48
  end
54
49
 
55
- ComfyMiddleSeat::Content::Renderer.register_tag(
56
- :asset, ComfyMiddleSeat::Content::Tags::Asset
57
- )
50
+ %i[asset layout_asset].each do |name|
51
+ ComfyMiddleSeat::Content::Renderer.register_tag(name, ComfyMiddleSeat::Content::Tags::Asset)
52
+ 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
@@ -42,6 +42,6 @@ class ComfyMiddleSeat::Content::Tags::Partial < ComfyMiddleSeat::Content::Tag
42
42
  end
43
43
  end
44
44
 
45
- ComfyMiddleSeat::Content::Renderer.register_tag(
46
- :partial, ComfyMiddleSeat::Content::Tags::Partial
47
- )
45
+ %i[partial render_partial].each do |name|
46
+ ComfyMiddleSeat::Content::Renderer.register_tag(name, ComfyMiddleSeat::Content::Tags::Partial)
47
+ end
@@ -37,6 +37,6 @@ class ComfyMiddleSeat::Content::Tags::Template < ComfyMiddleSeat::Content::Tag
37
37
  end
38
38
  end
39
39
 
40
- ComfyMiddleSeat::Content::Renderer.register_tag(
41
- :template, ComfyMiddleSeat::Content::Tags::Template
42
- )
40
+ %i[template render_template].each do |name|
41
+ ComfyMiddleSeat::Content::Renderer.register_tag(name, ComfyMiddleSeat::Content::Tags::Template)
42
+ 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'