Class: EmailTemplatesController

Inherits:
CrmController show all
Includes:
Controllers::Destroyable
Defined in:
app/controllers/email_templates_controller.rb

Overview

== Schema Information

Table name: email_templates

id :integer not null, primary key
from :string(255)
to :string(255)
cc :string(255)
bcc :string(255)
resource_type :string(255)
resource_id :integer
body :text
subject :string(255)
creator_id :integer
updater_id :integer
description :string(255)
state :string(255)
css :text
stylesheet :string(255)
created_at :datetime not null
updated_at :datetime not null
system_code :string(20)
category :string
template :string
preview_text :string

Constant Summary

Constants included from Controllers::ReferenceFindable

Controllers::ReferenceFindable::ID_EMBEDDED_PATTERNS

Constants included from Controllers::AnalyticsEvents

Controllers::AnalyticsEvents::MAX_QUEUED_EVENTS, Controllers::AnalyticsEvents::SESSION_KEY

Constants included from Controllers::ErrorRendering

Controllers::ErrorRendering::NON_CONTENT_PATH_PREFIXES

Instance Method Summary collapse

Methods included from Controllers::Destroyable

#destroy, #perform_destroy

Methods inherited from CrmController

#access_denied, #context_id, #context_object, #crm_home_path, #current_ability, #default_url_options, #download_temp, #get_tempfile_path_for_download, #init_status_job_collector, #initialize_crm_lazy_chunks, #persist_enqueued_status_jobs, #record_not_found, #redirect_to_job_or_fallback, #render_edit_action, #set_context, #set_download_path, #stash_file_for_temp_download, #sync_admin_presence_cookie, #touch_employee_last_seen

Methods inherited from ApplicationController

#account_impersonated?, #add_to_flash, #after_sign_in_path_for, #bypass_forgery_protection?, #chat_enabled?, #cloudflare_cleared?, #default_catalog, #default_url_options, #enable_turbo_frames, #find_publication, #fix_invalid_accept_header, #init_js_utils, #is_globals_call?, #layout_by_resource, #locale_store, #redirect_to, #require_employee_for_crm, #set_base_host, #set_real_ip, #set_report_errors_for, #should_render_layout?, #skip_layout_for_turbo_frame?, #stamp_impersonation_context, #tab_frame_breakout_request?, #warmlyyours_canada_ip?, #warmlyyours_ip?, #y

Methods included from Controllers::ReturnPathHandling

#check_for_return_path, #redirect_to_return_path_or_default

Methods included from Controllers::AnalyticsEvents

#consume_queued_analytics_events, #registration_lead_type, #track_event

Methods included from Controllers::DeviceDetection

#device_detector, #is_ie?

Methods included from Controllers::SubdomainDetection

#is_crm_request?, #is_www_request?, #json_request?

Methods included from Controllers::TurboSafeRedirect

#redirect_to

Methods included from Controllers::TrackingDetection

#bot_request?, #gdpr_country?, #gdpr_country_data, #prevent_bots, #set_tracking_cookie, #track_visitor?

Methods included from Controllers::AcceleratedFileSending

#send_file_accelerated, #send_upload_accelerated

Methods included from Controllers::ErrorRendering

#excp_string, #mail_to_for_error_reporting, #render_400, #render_404, #render_406, #render_410, #render_500, #render_invalid_authenticity_token, #render_ip_spoof_error, #render_unpermitted_parameters, #safe_referer_or_fallback

Methods included from Controllers::TurnstileVerification

#load_turnstile_script_tag, #turnstile_lazy_widget, #turnstile_script_tag, #turnstile_widget, #validate_turnstile!

Methods included from Controllers::CloudflareCaching

edge_cached, #edge_cached_action?, #reset_cloudflare_cache, #set_cloudflare_cache, #skip_edge_cache!, #skip_session

Methods included from Controllers::Webpackable

#preload_webpack_fonts, #webpack_css_include, #webpack_css_url, #webpack_js_include, #wpd_is_running?

Methods included from Controllers::Localizable

#cloudflare_country_locale, #determine_request_locale, #geocoder_locale, #guest_user_locale_check, #locale_optional_www_auth_path?, #param_locale, #set_locale, #set_request_locale, #skip_localization?, #warmlyyours_ip_locale

Methods included from Controllers::Authenticable

#access_denied, #authenticate_account, #authenticate_account!, #authenticate_account_from_login_token!, #check_is_a_manager, #check_is_a_sales_manager, #check_is_an_admin, #check_is_an_employee, #check_party, #clear_mismatched_guest_user, #create_guest_user, #credentials?, #current_or_guest_user, #current_or_guest_user_id_read_only, #current_user, #devise_mapping, #fully_logged_in?, #generate_bot_id, #guest_user, #identifiable?, #init_current_user, #initialize_guest, #load_context_user, #logging_in, #resource, #resource_name, #restrict_access_for_non_employees, #scrubbed_request_path, #user_object, #warn_on_session_guest_id_leak

Methods included from UrlsHelper

#catalog_breadcrumb_links, #catalog_link, #catalog_link_for_product_line, #catalog_link_for_sku, #cms_link, #delocalized_path, #path_to_sales_product_sku, #path_to_sales_product_sku_for_product_line, #path_to_sales_product_sku_for_product_line_slug, #product_line_from_catalog_link, #protocol_neutral_url, #sanitize_external_url, #valid_external_url?

Instance Method Details

#bodyvoid

This method returns an undefined value.

Renders the template body in preview mode, without layout.



220
221
222
223
224
225
226
227
# File 'app/controllers/email_templates_controller.rb', line 220

def body
  @email_template = EmailTemplate.find(params[:id])
  authorize! :read, EmailTemplate
  @body = @email_template.body
  @preview_mode = true
  @title = @email_template.subject
  render template: '/communication_mailer/email', layout: false
end

#browser_previewObject

Render the email template directly in the browser (no iframe)
Used for previewing how the email will look in email clients



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'app/controllers/email_templates_controller.rb', line 299

def browser_preview
  @email_template = EmailTemplate.find(params[:id])

  # Build merge options for rendering
  cb = CommunicationBuilder.new(
    sender_party_id: params[:sender_party_id] || @context_user&.id,
    recipient_party_id: params[:recipient_party_id],
    resource_id: params[:resource_id],
    resource_type: params[:resource_type],
    email_template: @email_template,
    merge_options: { ignore_errors: true }
  )

  # Determine which version to render (v4 if available and ready, otherwise v3)
  version = params[:version] || (@email_template.redactor_4_ready? ? 'v4' : 'v3')

  html = if version == 'v4' && @email_template.body_v4_email.present?
           @email_template.render_body_v4(cb.merge_options)
         else
           @email_template.render_body(cb.merge_options)
         end

  render html: html.html_safe, layout: false
end

#copyvoid

This method returns an undefined value.

Duplicates an email template into the new form; legacy Redactor 3 templates are refused.



203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'app/controllers/email_templates_controller.rb', line 203

def copy
  authorize! :create, EmailTemplate
  @email_template = EmailTemplate.find(params[:id])

  # Block copying of Redactor 3 templates to prevent legacy content from spreading
  unless @email_template.redactor_4_ready?
    redirect_to @email_template, alert: 'Cannot copy legacy Redactor 3 templates. Please migrate this template to Redactor 4 first.'
    return
  end

  @email_template = @email_template.deep_dup
  render action: :new
end

#createvoid

This method returns an undefined value.

Creates an email template, optionally continuing to the send-preview flow.



154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'app/controllers/email_templates_controller.rb', line 154

def create
  authorize! :create, EmailTemplate
  @email_template = EmailTemplate.new(params[:email_template])
  if @email_template.save
    if params[:commit] == 'Save and send preview'
      redirect_to send_preview_email_template_path(@email_template), info: 'Email Template was successfully created.'
    else
      redirect_to @email_template, info: 'Email Template was successfully created.'
    end
  else
    render action: :new, status: :unprocessable_content
  end
end

#do_send_previewvoid

This method returns an undefined value.

Sends preview emails of the template to the submitted addresses.



327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'app/controllers/email_templates_controller.rb', line 327

def do_send_preview
  @email_template = EmailTemplate.find(params[:id])
  @source_id = params[:source_id]
  # Tom Select multi-input submits `email[email_address][]=...` (Array); the
  # legacy textarea submitted a single comma/semicolon-separated string. Accept
  # both and flatten down to one email per slot.
  multi_emails = params.dig(:email, :email_address)
  emails = Array.wrap(multi_emails)
                .flat_map { |v| v.to_s.split(/[,;\s]+/) }
                .map(&:squish)
                .filter_map(&:presence)
  # Determine if we should force v4 rendering
  force_v4 = params[:version] == 'v4'

  # Process "Preview with Real Data" parameters
  sender_party_id = params[:sender_party_id].presence || @context_user.id
  recipient_party_id = nil
  resource_type = nil
  resource_id = nil

  # Look up recipient party from customer email
  if params[:recipient].present?
    contact_point = ContactPoint.where(detail: params[:recipient]).where.not(party_id: nil).first
    recipient_party_id = contact_point&.party_id
  end

  if (result = resolve_resource_from_params)
    found, resource_type, fallback_party_id = result
    resource_id = found.id
    recipient_party_id ||= fallback_party_id
  end

  communications = []
  failed_emails = []
  emails.each do |email|
    comm = CommunicationBuilder.new(
      sender_party_id: sender_party_id,
      recipient_party_id: recipient_party_id,
      email_template_id: @email_template.id,
      source_id: @source_id,
      resource_type: resource_type,
      resource_id: resource_id,
      emails: email,
      recipient_party: ContactPoint.where(detail: email).where.not(party_id: nil).first.try(:party),
      force_v4: force_v4
    ).create(send_immediately: true, skip_suppression_check: true)
    if comm.persisted?
      communications << comm
    else
      failed_emails << { email: email, errors: comm.errors.full_messages.join(', ') }
    end
  end

  messages = []
  if communications.any?
    links = communications.map { |c| view_context.link_to("Communication id: #{c.id} [#{c.state}]", communication_path(c.id)) }.join(', ')
    messages << "Preview email has been sent. #{links}".html_safe
  end
  if failed_emails.any?
    failed_msgs = failed_emails.map { |f| "#{f[:email]}: #{f[:errors]}" }.join('; ')
    messages << "Failed to send preview to: #{failed_msgs}"
  end

  if failed_emails.any? && communications.empty?
    flash[:error] = messages.join(' ')
  elsif failed_emails.any?
    flash[:warning] = messages.join(' ')
  else
    flash[:info] = messages.join(' ')
  end

  redirect_to_return_path_or_default(email_template_path(@email_template))
end

#editvoid

This method returns an undefined value.

Renders the edit form for an email template.



146
147
148
149
# File 'app/controllers/email_templates_controller.rb', line 146

def edit
  @email_template = EmailTemplate.find(params[:id])
  authorize! :update, EmailTemplate
end

#exportvoid

This method returns an undefined value.

Exports email templates as a YAML download.



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'app/controllers/email_templates_controller.rb', line 92

def export
  authorize! :export, EmailTemplate
  require 'tempfile'
  begin
    file = Tempfile.new('foo')
    email_templates = EmailTemplate.all
    email_templates = email_templates.where(id: params[:email_template_ids]) if params[:email_template_ids].present?
    # pass the file handle as the second parameter to dump
    file.write(email_templates.to_yaml)
    file.flush
    file.fsync
    send_file_accelerated(file.path, download: true, file_name: "email_template_export_#{Time.current.to_i}.yaml")
  ensure
    file.close
  end
end

#importvoid

This method returns an undefined value.

Imports email templates from an uploaded YAML file.



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'app/controllers/email_templates_controller.rb', line 112

def import
  authorize! :import, EmailTemplate
  uploaded_io = params[:file]
  email_templates = YAML.load(uploaded_io.read)
  res = []
  email_templates.each do |et|
    email_template = EmailTemplate.where('description ILIKE ?', et.description).first
    if email_template
      email_template.update(et.attributes)
    else
      email_template = EmailTemplate.create(et.attributes)
    end

    if email_template.errors.empty?
      res << email_template.id
    else
      flash[:error] ||= ''
      flash[:error] << "Email Template #{et.id} could not be saved: #{email_template.errors_to_s}. "
    end
  end
  redirect_to email_templates_path, info: "Email Templates #{res.join} were successfully imported"
end

#improve_styleObject

POST /email_templates/:id/improve_style
Improves styling of existing Redactor 4 content using AI without changing content



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'app/controllers/email_templates_controller.rb', line 262

def improve_style
  @email_template = EmailTemplate.find(params[:id])
  authorize! :update, @email_template

  body_v4 = params[:body_v4]

  migrator = EmailTemplate::ContentMigrator.new
  result = migrator.improve_style(body_v4:)

  if result[:success]
    render json: {
      success: true,
      body_v4: result[:body_v4]
    }
  else
    render json: {
      success: false,
      error: result[:error] || 'Unknown error during style improvement'
    }, status: :unprocessable_content
  end
rescue StandardError => e
  render json: {
    success: false,
    error: "Style improvement failed: #{e.message}"
  }, status: :internal_server_error
end

#inactivevoid

This method returns an undefined value.

Archives an email template.



189
190
191
192
193
194
195
196
197
198
# File 'app/controllers/email_templates_controller.rb', line 189

def inactive
  @email_template = EmailTemplate.find(params[:id])
  authorize! :update, EmailTemplate
  if @email_template.update(state: 'archived')
    flash[:info] = 'Email Template was successfully updated.'
    redirect_to_return_path_or_default email_template_path(@email_template)
  else
    render action: :edit, status: :unprocessable_content
  end
end

#indexvoid

This method returns an undefined value.

Lists email templates, scoped to the user's own unless they can manage all.



49
50
51
52
53
54
55
56
57
# File 'app/controllers/email_templates_controller.rb', line 49

def index
  @q = ViewEmailTemplate.includes(:resource)
  @q = @q.where("(resource_type = 'Employee' and resource_id = ?) or resource_type = 'Company' or resource_type IS NULL", @context_user.id) if cannot?(:manage, EmailTemplate)
  @q = @q.where('referenced_in_campaign = true') if params[:referenced_in_campaign].to_b
  @q = @q.where('referenced_in_activity = true') if params[:referenced_in_activity].to_b
  @q = @q.ransack(params[:q])
  @q.sorts = 'created_at DESC' if @q.sorts.empty?
  @pagy, @email_templates = pagy(@q.result, limit: 100)
end

#migrate_contentObject

POST /email_templates/:id/migrate_content
Converts legacy Redactor 3 content to Redactor 4 format using AI



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'app/controllers/email_templates_controller.rb', line 231

def migrate_content
  @email_template = EmailTemplate.find(params[:id])
  authorize! :update, @email_template

  migrator = EmailTemplate::ContentMigrator.new
  result = migrator.process(
    body: @email_template.body,
    css: @email_template.css
  )

  if result[:success]
    render json: {
      success: true,
      body_v4: result[:body_v4],
      css_v4: result[:css_v4]
    }
  else
    render json: {
      success: false,
      error: result[:error] || 'Unknown error during conversion'
    }, status: :unprocessable_content
  end
rescue StandardError => e
  render json: {
    success: false,
    error: "Conversion failed: #{e.message}"
  }, status: :internal_server_error
end

#newvoid

This method returns an undefined value.

Builds a new email template owned by the current employee.



138
139
140
141
# File 'app/controllers/email_templates_controller.rb', line 138

def new
  authorize! :create, EmailTemplate
  @email_template = EmailTemplate.new(belongs_to_resource: "Employee|#{current_user.id}", stylesheet: EmailTemplate::DEFAULT_STYLESHEET, template: EmailTemplate::DEFAULT_TEMPLATE)
end

#send_previewvoid

This method returns an undefined value.

Renders the send-preview form for an email template.



292
293
294
295
# File 'app/controllers/email_templates_controller.rb', line 292

def send_preview
  @email_template = EmailTemplate.find(params[:id])
  @source_id = params[:source_id]
end

#showvoid

This method returns an undefined value.

Shows one email template with optional preview recipient/resource context.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'app/controllers/email_templates_controller.rb', line 62

def show
  @email_template = EmailTemplate.find(params[:id])
  if params[:recipient].present?
    @recipient = params[:recipient].strip
    if @recipient.include?('@')
      cp = ContactPoint.where(detail: @recipient).where.not(party_id: nil).first
      @recipient_party_id = cp.party.id if cp
    end
  end
  if (result = resolve_resource_from_params)
    found, @resource_type, fallback_party_id = result
    @resource_id = found.id
    @recipient_party_id ||= fallback_party_id
  end
  @resource_type_param = params[:resource_type]
  @resource_ref = params[:resource_ref]
  @sender_party_id = params[:sender_party_id]

  @last_used_communication = @email_template.communications.order(created_at: :desc).select(:id, :created_at, :subject).first
  @usage_count_30d = @email_template.communications.where(created_at: 30.days.ago..).count

  respond_to do |format|
    format.html # show.html.erb
    format.json { render json: @email_template }
  end
end

#updatevoid

This method returns an undefined value.

Updates an email template, optionally continuing to the send-preview flow.



171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'app/controllers/email_templates_controller.rb', line 171

def update
  @email_template = EmailTemplate.find(params[:id])
  authorize! :update, EmailTemplate
  if @email_template.update(params[:email_template])
    if params[:commit] == 'Save and send preview'
      redirect_to send_preview_email_template_path(@email_template), info: 'Email Template was successfully updated.'
    else
      flash[:info] = 'Email Template was successfully updated.'
      redirect_to_return_path_or_default email_template_path(@email_template)
    end
  else
    render action: :edit, status: :unprocessable_content
  end
end