active_scaffold 4.4.5 → 4.4.6

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 53bf726d56ef1da11ee5ecdf4bc039b59ef10e74dd1ac9d31e2723e3e27716e1
4
- data.tar.gz: 625f6798df882567a08bac210c6af4c9dbb246364ba739fc0a68bdad695c7b94
3
+ metadata.gz: 2475af8fa6929537031a900309db3ff1c3b91bcb9e911ccd61676aa06bf3009f
4
+ data.tar.gz: 0df3c5893800ffc86159e1a9c73e0b022f5b70a64afcc39e6da59266540f37ba
5
5
  SHA512:
6
- metadata.gz: 1948177220ec25858dc4d4a19e0313db67f8cafc544ab6bfdf5129bbd418918c9d6f4e4af1f134213e39cc97fb2da485972feca80628547f7fcbc9ad1687e2b9
7
- data.tar.gz: 21308ad93ecc7bed4293d82dc4b011eb516e43171d9283ea26fdf24fd67f3283f280ba2c11033c3fdd1008194fef34821a9b134b2c672c8159611eb1b73eac59
6
+ metadata.gz: e37f5f28f8bade8de9c86d69e68166a0ed7d12acde4c0e908c6c92626505b24511578cfb8394d238393659162ef7cc1dcb3916e693630a9192d03ea5acd8fa3e
7
+ data.tar.gz: 2aaae36010f59a87cf009a2ddca97c1f45f620d0493d100ff997f185d732592514acce727b1a929c2411028e6964e20de9007673f874deec8f2eb191a97cb1ae
data/CHANGELOG.rdoc CHANGED
@@ -1,3 +1,28 @@
1
+ = 4.4.6
2
+ - Execute iframe responses by injecting a nonce-aware script into the parent document, avoiding +eval()+ under a strict Content Security Policy
3
+ - Use RecordSelect's named callback registry for the add-existing subform link when available, avoiding +eval()+ under a strict Content Security Policy while retaining compatibility with older RecordSelect releases.
4
+ - Add named callback registries for datepicker and TinyMCE configuration. Inline JavaScript remains supported in 4.4, but successful evaluation emits a browser deprecation warning because support will be removed in ActiveScaffold 4.5.
5
+
6
+ Define callbacks in an application JavaScript file:
7
+
8
+ ActiveScaffold.datepickerCallbacks.availableDate = function(date) {
9
+ return [date.getDay() !== 0, ''];
10
+ };
11
+ ActiveScaffold.tinymceCallbacks.configureEditor = function(editor) {
12
+ editor.on('init', function() { /* ... */ });
13
+ };
14
+
15
+ Reference their names from configuration instead of embedding function source:
16
+
17
+ config.columns[:starts_on].options[:data] = {beforeshowday: 'availableDate'}
18
+
19
+ # config/tinymce.yml
20
+ default:
21
+ setup: configureEditor
22
+
23
+ Migrate warnings before upgrading to 4.5. Non-callback datepicker settings should use JSON-compatible values.
24
+ - Add explicit HTML translations to +as_+. Move translations containing markup from +key+ to +key.html+. Interpolation follows Rails' HTML translation behavior, escaping unsafe values and preserving already HTML-safe values. Legacy HTML translations remain HTML-safe in 4.4 but emit one deprecation warning per key and scope; ordinary translations will no longer be marked HTML-safe in ActiveScaffold 4.5
25
+
1
26
  = 4.4.5
2
27
  - Authorize the PaperTrail deleted-records action and allow overriding its version query with +deleted_records_query+
3
28
  - Deprecate passing optional boolean arguments positionally; keyword arguments will be required in 4.5.
@@ -0,0 +1,67 @@
1
+ (function(global) {
2
+ var ActiveScaffold = global.ActiveScaffold = global.ActiveScaffold || {};
3
+ var warned = {};
4
+
5
+ ActiveScaffold.datepickerCallbacks = ActiveScaffold.datepickerCallbacks || {};
6
+ ActiveScaffold.tinymceCallbacks = ActiveScaffold.tinymceCallbacks || {};
7
+
8
+ function warnOnce(key, message) {
9
+ if (warned[key]) return;
10
+
11
+ warned[key] = true;
12
+ if (global.console && typeof global.console.warn === 'function') {
13
+ global.console.warn(message);
14
+ }
15
+ }
16
+
17
+ function evaluateLegacy(value, description, wrap) {
18
+ try {
19
+ var evaluated = global.eval(wrap ? '(' + value + ')' : value);
20
+ warnOnce(description + ':' + value,
21
+ 'DEPRECATION WARNING: JavaScript in ' + description + ' is deprecated and will be removed in ActiveScaffold 4.5. ' +
22
+ 'Register callbacks by name and use JSON-compatible values instead.');
23
+ return {success: true, value: evaluated};
24
+ } catch (error) {
25
+ return {success: false};
26
+ }
27
+ }
28
+
29
+ ActiveScaffold.resolveCallback = function(registryName, value, description) {
30
+ if (typeof value === 'function' || typeof value !== 'string') return value;
31
+
32
+ var registry = ActiveScaffold[registryName] || {};
33
+ if (Object.prototype.hasOwnProperty.call(registry, value)) {
34
+ if (typeof registry[value] === 'function') return registry[value];
35
+
36
+ throw new TypeError('ActiveScaffold.' + registryName + '.' + value + ' must be a function');
37
+ }
38
+
39
+ var legacy = evaluateLegacy(value, description, true);
40
+ if (legacy.success) return legacy.value;
41
+
42
+ throw new Error(description + ' references unknown callback ' + value +
43
+ '. Register it in ActiveScaffold.' + registryName + '.');
44
+ };
45
+
46
+ var datepickerCallbackOptions = {
47
+ afterInject: true,
48
+ beforeShow: true,
49
+ beforeShowDay: true,
50
+ calculateWeek: true,
51
+ onChangeMonthYear: true,
52
+ onClose: true,
53
+ onSelect: true,
54
+ onUpdateDatepicker: true
55
+ };
56
+
57
+ ActiveScaffold.resolveDatepickerSetting = function(option, value) {
58
+ if (typeof value !== 'string') return value;
59
+
60
+ if (datepickerCallbackOptions[option]) {
61
+ return ActiveScaffold.resolveCallback('datepickerCallbacks', value, 'datepicker option ' + option);
62
+ }
63
+
64
+ var legacy = evaluateLegacy(value, 'datepicker option ' + option, false);
65
+ return legacy.success ? legacy.value : value;
66
+ };
67
+ })(window);
@@ -6,11 +6,7 @@ jQuery.extend(Object.getPrototypeOf(jQuery.datepicker), {
6
6
  if(this._defaults.hasOwnProperty(attrName)){
7
7
  var attrValue = $target.data(attrName.toLowerCase());
8
8
  if (attrValue !== undefined) {
9
- try {
10
- inlineSettings[attrName] = eval(attrValue);
11
- } catch (err) {
12
- inlineSettings[attrName] = attrValue;
13
- }
9
+ inlineSettings[attrName] = ActiveScaffold.resolveDatepickerSetting(attrName, attrValue);
14
10
  }
15
11
  }
16
12
  }
@@ -46,4 +42,4 @@ jQuery(document).on('change', 'input.datetime_picker', function(e) {
46
42
  $this.data('first-event', true);
47
43
  e.stopImmediatePropagation();
48
44
  }
49
- });
45
+ });
@@ -8,6 +8,8 @@
8
8
 
9
9
  // Load dependencies based on configuration
10
10
  const loadDependencies = async () => {
11
+ await loadScript(RAILS_ASSET_URL('/active_scaffold/callback_registry.js'));
12
+
11
13
  // Always load these
12
14
  await loadScript(RAILS_ASSET_URL('/jquery.ba-throttle-debounce.js'));
13
15
  await loadScript(RAILS_ASSET_URL('/jquery.visible.min.js'));
@@ -0,0 +1,9 @@
1
+ (function(global) {
2
+ global.RecordSelect.registerCallback('active_scaffold.add_existing', function(id) {
3
+ global.ActiveScaffold.record_select_onselect(
4
+ this.obj.attr('data-edit-associated-url'),
5
+ this.obj.attr('data-active-scaffold-id'),
6
+ id
7
+ );
8
+ });
9
+ })(window);
@@ -40,7 +40,7 @@
40
40
  var id = jQuery(this).attr('id');
41
41
  if (tinymce && tinymce.majorVersion >= 6) {
42
42
  settings.selector = '#' + id;
43
- var userSetup = settings.setup ? eval(settings.setup) : function () {};
43
+ var userSetup = settings.setup ? ActiveScaffold.resolveCallback('tinymceCallbacks', settings.setup, 'TinyMCE setup') : function () {};
44
44
  settings.setup = function(editor) {
45
45
  userSetup(editor); // run what the user already had
46
46
 
@@ -7,6 +7,7 @@ module ActiveScaffold
7
7
  autoload :JqueryUiThemeGenerator, 'active_scaffold/assets/jquery_ui_theme_generator'
8
8
 
9
9
  CORE_STYLESHEETS = ['active_scaffold/core'].freeze
10
+ CALLBACK_REGISTRY_JAVASCRIPT = 'active_scaffold/callback_registry'
10
11
  CORE_JAVASCRIPTS = ['jquery.ba-throttle-debounce', 'jquery.visible.min', 'active_scaffold/jquery.editinplace', 'active_scaffold/active_scaffold'].freeze
11
12
 
12
13
  class << self
@@ -34,17 +35,17 @@ module ActiveScaffold
34
35
  def active_scaffold_javascripts(load = :all)
35
36
  case load
36
37
  when :all
37
- jquery_ui_javascripts + CORE_JAVASCRIPTS + ActiveScaffold.javascripts + bridge_javascripts
38
+ [CALLBACK_REGISTRY_JAVASCRIPT] + jquery_ui_javascripts + CORE_JAVASCRIPTS + ActiveScaffold.javascripts + bridge_javascripts
38
39
  when :deps
39
- jquery_ui_javascripts + ActiveScaffold.javascripts + bridge_javascripts
40
+ [CALLBACK_REGISTRY_JAVASCRIPT] + jquery_ui_javascripts + ActiveScaffold.javascripts + bridge_javascripts
40
41
  when :core
41
- CORE_JAVASCRIPTS
42
+ [CALLBACK_REGISTRY_JAVASCRIPT] + CORE_JAVASCRIPTS
42
43
  when :plugins
43
44
  ActiveScaffold.javascripts
44
45
  when :bridges
45
46
  bridge_javascripts
46
47
  when :jquery_ui
47
- jquery_ui_javascripts
48
+ [CALLBACK_REGISTRY_JAVASCRIPT] + jquery_ui_javascripts
48
49
  else
49
50
  raise ArgumentError, "Unknown load mode: #{load.inspect}"
50
51
  end
@@ -10,6 +10,12 @@ class ActiveScaffold::Bridges::RecordSelect < ActiveScaffold::DataStructures::Br
10
10
  end
11
11
 
12
12
  def self.javascripts
13
- 'record_select'
13
+ assets = ['record_select']
14
+ assets << 'active_scaffold/record_select_bridge' if callback_registry?
15
+ assets
16
+ end
17
+
18
+ def self.callback_registry?
19
+ defined?(::RecordSelect) && ::RecordSelect.respond_to?(:callback_registry?) && ::RecordSelect.callback_registry?
14
20
  end
15
21
  end
@@ -1,12 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_support/html_safe_translation'
4
+ require 'active_scaffold/translation_html_safety'
5
+
3
6
  class Object
4
7
  def as_(key, options = {})
5
8
  if key.present?
6
- scope = [:active_scaffold, *options.delete(:scope)]
7
- options = options.reverse_merge(scope: scope, default: key.is_a?(String) ? key : key.to_s.titleize)
8
- text = I18n.t(key.to_s, **options).html_safe
9
- # text = nil if text.include?('translation missing:')
9
+ options = options.merge(scope: [:active_scaffold, *options[:scope]])
10
+ options[:default] = key.is_a?(String) ? key : key.to_s.titleize unless options.key?(:default)
11
+
12
+ missing_html_translation = Object.new
13
+ html = ActiveSupport::HtmlSafeTranslation.translate(
14
+ "#{key}.html", **options, default: missing_html_translation
15
+ )
16
+ text =
17
+ if html.equal?(missing_html_translation)
18
+ translation = I18n.t(key.to_s, **options)
19
+ ActiveScaffold::TranslationHtmlSafety.check_once(key, options[:scope], translation) if translation.is_a?(String)
20
+ translation.html_safe # rubocop:disable Rails/OutputSafety
21
+ else
22
+ html
23
+ end
10
24
  end
11
25
  text || key
12
26
  end
@@ -128,7 +128,22 @@ module ActiveScaffold
128
128
  end
129
129
 
130
130
  def record_select_params_for_add_existing(association, edit_associated_url_options, record)
131
- {onselect: "ActiveScaffold.record_select_onselect(#{url_for(edit_associated_url_options).to_json}, #{active_scaffold_id.to_json}, id);"}
131
+ if ActiveScaffold::Bridges::RecordSelect.callback_registry?
132
+ {
133
+ onselect: 'active_scaffold.add_existing',
134
+ html: {
135
+ data: {
136
+ edit_associated_url: url_for(edit_associated_url_options),
137
+ active_scaffold_id: active_scaffold_id
138
+ }
139
+ }
140
+ }
141
+ else
142
+ {
143
+ onselect: "ActiveScaffold.record_select_onselect(#{url_for(edit_associated_url_options).to_json}, " \
144
+ "#{active_scaffold_id.to_json}, id);"
145
+ }
146
+ end
132
147
  end
133
148
  end
134
149
  end
@@ -19,16 +19,10 @@ module ActiveScaffold
19
19
  end
20
20
  response.status = 200 if (300...400).cover? response.status
21
21
 
22
- # Eval in parent scope and replace document location of this frame
23
- # so back button doesn't replay action on targeted forms
24
- # loc = document.location to be set after parent is updated for IE
25
- # with(window.parent) - pull in variables from parent window
26
- # setTimeout - scope the execution in the windows parent for safari
27
- # window.eval - legal eval for Opera
28
- script = "<html><body><script type='text/javascript' charset='utf-8'>
29
- var loc = document.location;
30
- with(window.parent) { setTimeout(function() { window.eval('#{self.class.helpers.escape_javascript script}'); window.loc && loc.replace('about:blank'); }, 1) }
31
- </script></body></html>"
22
+ # Inject the response into the parent document so it executes in the parent scope.
23
+ # Reuse its CSP nonce when one is available, then replace the frame location so the
24
+ # back button doesn't replay the action on targeted forms.
25
+ script = responds_to_parent_html(script)
32
26
 
33
27
  # Clear out the previous render to prevent double render and then render
34
28
  if respond_to?(:erase_results, true)
@@ -41,5 +35,24 @@ module ActiveScaffold
41
35
  render html: script.html_safe, content_type: 'text/html' # rubocop:disable Rails/OutputSafety
42
36
  end
43
37
  alias respond_to_parent responds_to_parent
38
+
39
+ private
40
+
41
+ def responds_to_parent_html(response_javascript)
42
+ escaped_response = self.class.helpers.escape_javascript(response_javascript)
43
+ "<html><body><script type='text/javascript' charset='utf-8'>
44
+ var loc = document.location;
45
+ var responseJavaScript = '#{escaped_response}';
46
+ var parentDocument = window.parent.document;
47
+ var script = parentDocument.createElement('script');
48
+ var nonceSource = parentDocument.querySelector('script[nonce]');
49
+
50
+ if (nonceSource) script.nonce = nonceSource.nonce;
51
+ script.text = responseJavaScript;
52
+ parentDocument.head.appendChild(script);
53
+ script.remove();
54
+ loc.replace('about:blank');
55
+ </script></body></html>"
56
+ end
44
57
  end
45
58
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent/map'
4
+
5
+ module ActiveScaffold
6
+ module TranslationHtmlSafety
7
+ HTML_PATTERN = /<[^>]+>|&(?:[a-z][a-z0-9]+|#\d+|#x[0-9a-f]+);/i
8
+
9
+ @checked_translations = Concurrent::Map.new
10
+
11
+ class << self
12
+ def check_once(key, scope, translation)
13
+ translation_key_parts = [*scope, key].freeze
14
+ return unless @checked_translations.put_if_absent(translation_key_parts, true).nil?
15
+ return unless translation.match?(HTML_PATTERN)
16
+
17
+ translation_key = translation_key_parts.join('.')
18
+ ActiveScaffold.deprecator.warn(
19
+ "HTML in translation #{translation_key} is deprecated. Move it to #{translation_key}.html. " \
20
+ 'Ordinary translations will no longer be marked HTML-safe in ActiveScaffold 4.5.'
21
+ )
22
+ end
23
+ end
24
+ end
25
+ end
@@ -4,7 +4,7 @@ module ActiveScaffold
4
4
  module Version
5
5
  MAJOR = 4
6
6
  MINOR = 4
7
- PATCH = 5
7
+ PATCH = 6
8
8
  FIX = nil
9
9
 
10
10
  STRING = [MAJOR, MINOR, PATCH, FIX].compact.join('.')
@@ -176,11 +176,7 @@
176
176
  if (this._defaults.hasOwnProperty(attrName)) {
177
177
  var attrValue = $input.data(attrName.toLowerCase());
178
178
  if (attrValue !== undefined) {
179
- try {
180
- inlineSettings[attrName] = eval(attrValue);
181
- } catch (err) {
182
- inlineSettings[attrName] = attrValue;
183
- }
179
+ inlineSettings[attrName] = ActiveScaffold.resolveDatepickerSetting(attrName, attrValue);
184
180
  }
185
181
  }
186
182
  }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_scaffold
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.4.5
4
+ version: 4.4.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Many, see README
@@ -110,10 +110,12 @@ files:
110
110
  - app/assets/javascripts/active_scaffold.js.erb
111
111
  - app/assets/javascripts/active_scaffold/active_scaffold.js
112
112
  - app/assets/javascripts/active_scaffold/active_scaffold_chosen.js
113
+ - app/assets/javascripts/active_scaffold/callback_registry.js
113
114
  - app/assets/javascripts/active_scaffold/date_picker_bridge.js
114
115
  - app/assets/javascripts/active_scaffold/draggable_lists.js
115
116
  - app/assets/javascripts/active_scaffold/jquery.editinplace.js
116
117
  - app/assets/javascripts/active_scaffold/load.js
118
+ - app/assets/javascripts/active_scaffold/record_select_bridge.js
117
119
  - app/assets/javascripts/active_scaffold/tiny_mce_bridge.js
118
120
  - app/assets/stylesheets/active_scaffold-ie.scss
119
121
  - app/assets/stylesheets/active_scaffold.scss.erb
@@ -360,6 +362,7 @@ files:
360
362
  - lib/active_scaffold/responds_to_parent.rb
361
363
  - lib/active_scaffold/tableless.rb
362
364
  - lib/active_scaffold/testing/assert_embedded_load.rb
365
+ - lib/active_scaffold/translation_html_safety.rb
363
366
  - lib/active_scaffold/version.rb
364
367
  - lib/generators/active_scaffold/controller/USAGE
365
368
  - lib/generators/active_scaffold/controller/controller_generator.rb