-
Notifications
You must be signed in to change notification settings - Fork 41
/
site_template.rb
813 lines (679 loc) · 24 KB
/
site_template.rb
1
2
3
4
5
6
7
8
9
10
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
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
259
260
261
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
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
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
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
# Copyright (C) 2009 Pascal Rettig.
require 'radius'
require 'pp'
class SiteTemplate < DomainModel
validates_presence_of :name
has_many :site_template_zones, :dependent => :destroy, :order => :position
has_many :site_features, :dependent => :destroy, :order => 'name'
has_many :site_template_rendered_parts, :dependent => :destroy, :order => :idx
belongs_to :domain_file
has_many :child_templates, :class_name => "SiteTemplate", :foreign_key => :parent_id
belongs_to :parent_template, :class_name=>"SiteTemplate", :foreign_key => :parent_id
belongs_to :admin_user, :class_name => 'EndUser',:foreign_key => 'modified_by'
track_editor_changes
has_options :template_type, [ ['Site Theme','site'], ['Mail Theme','mail'] ]
serialize :options
def self.site_template_options
self.select_options(:conditions =>{ :template_type => 'site' })
end
def self.mail_template_options
self.select_options(:conditions =>{ :template_type => 'mail' })
end
class SiteTemplateContext < Radius::Context
def tag_missing(tag,attr,&block)
''
end
end
class InitialParserContext < SiteTemplateContext
# Make sure we expand tags even if we have a
# a bad tag underneat
def render_tag(name, attributes = {}, &block)
if name =~ /^(.+?):(.+)$/
render_tag($1) { render_tag($2, attributes, &block) }
else
tag_definition_block = @definitions[qualified_tag_name(name.to_s)]
if tag_definition_block
stack(name, attributes, block) do |tag|
tag_definition_block.call(tag).to_s
end
else
stack(name, attributes, block) do |tag|
tag.expand.to_s
end
end
end
end
def initialize
@variables = []
@zones = []
@translations = []
super
end
attr_accessor :variables
attr_accessor :zones
attr_accessor :translations
end
def self.create_default_template
SiteTemplate.create(:name => 'Default Theme'.t,:template_html => "<cms:zone name='Main'/>") unless SiteTemplate.find(:first)
end
# Override domain file setting so we can
# update the old one if necessary
def domain_file_id=(df_id)
@old_domain_file_id = self.domain_file_id
self.write_attribute(:domain_file_id,df_id)
end
# Make sure the updating works with setting the file as well as the id
def domain_file=(df); self.domain_file_id=(df.id); end
protected
def initial_parser_context
parser_context = SiteTemplate::InitialParserContext.new do |c|
c.define_tag 'var' do |tag|
c.variables <<
[ tag.attr['name'],
tag.attr['label'] || tag.attr['name'].humanize,
tag.attr['type'] || 'string',
(tag.attr['desc'] || tag.attr['description']).to_s,
(tag.attr['pri'] || 100).to_i,
tag.attr['trans'] || false,
tag.attr['default'] || ''
]
''
end
c.define_tag 'trans' do |tag|
c.translations << tag.expand
end
c.define_tag 'zone' do |tag|
c.zones << tag.attr['name']
end
end
end
public
# Update the associated zones and options
def update_zones_and_options
zones = []
tpl = self
parser_context = initial_parser_context
template_parser = Radius::Parser.new(parser_context, :tag_prefix => 'cms')
parsing_errors = []
begin
template_parser.parse(self.style_struct)
rescue Exception => err
parsing_errors << ('Error Parsing Structural Styles of %s:' / self.name) + err.to_s.t
end
begin
template_parser.parse(self.style_design)
rescue Exception => err
parsing_errors << ('Error Parsing Design Styles of %s:' / self.name) + err.to_s.t
end
self.site_features.each do |feature|
begin
template_parser.parse(feature.body)
rescue Exception => err
parsing_errors << ('Error Parsing Feature %s:' / feature.name) + err.to_s.t
end
end
begin
template_parser.parse(self.template_html)
rescue Exception => err
parsing_errors << ('Error Parsing Template HTML of %s:' / self.name) + err.to_s.t
end
parent_zones = parser_context.zones.clone
self.child_templates.each do |child|
begin
template_parser.parse(child.template_html)
rescue Exception => err
parsing_errors << ('Error Parsing Template HTML of %s:' / child.name) + err.to_s.t
end
end
output_variables = []
existing_variables = {}
parser_context.variables.each do |var|
if(existing_variables[var[0]])
existing_idx = existing_variables[var[0]];
merged_var = (0..var.length-1).collect do |idx|
if(!output_variables[existing_idx][idx].to_s.empty?)
output_variables[existing_idx][idx]
else
var[idx]
end
end
output_variables[existing_idx] = merged_var
else
output_variables << var
existing_variables[var[0]] = output_variables.length-1
end
end
sorted_variables = output_variables.sort_by do |var|
[ var[4].to_i,var[1].to_s ]
end
update_zones(parent_zones)
update_options(sorted_variables)
update_localize(parser_context.translations);
parsing_errors
end
# Get each feature it's own set of options,
# So it doesn't have to go back to the template to render it
def update_feature_options(feature_id=nil)
options = self.options
parser_errors = []
self.site_features.each do |feature|
if(feature_id.blank? || feature.id == feature_id)
opts= { :values => {},
:localize_values => {},
:localize => []
}
translations = []
feature_context = InitialParserContext.new do |c|
c.define_tag 'var' do |tag|
opts[:values][tag.attr['name']] = options[:values][tag.attr['name']]
''
end
c.define_tag 'trans' do |tag|
translations << tag.expand
''
end
end
parser = Radius::Parser.new(feature_context, :tag_prefix => 'cms')
begin
parser.parse(feature.body)
rescue Exception
''
end
opts[:values].each do |key,def_val|
options[:localize_values].each do |lang,lang_arr|
opts[:localize_values][lang] ||= {}
opts[:localize_values][lang][key] = lang_arr[key]
end
end
opts[:localize] = translations.collect do |trans|
(options[:localize] || []).detect do |t|
t[0] == trans
end
end
feature.save
end
end
end
# Save the user values for variables
def update_option_values(values)
saved_values = {}
self.options[:options].each do |opt|
var_name = opt[0]
if values && !values[var_name].blank?
saved_values[var_name] = values[var_name]
elsif opt[6]
if opt[2] == 'image'
parent = self.domain_file
img = DomainFile.find_by_file_path(parent.file_path + "/" + opt[6]) if parent
saved_values[var_name] = img.id if img
else
saved_values[var_name] = opt[6]
end
end
end
self.options[:values] = saved_values
end
def set_localization(localize_values,translate,translations)
(localize_values || {}).each do |lang,values|
self.update_localized_option_values(values,lang)
end
(translate || {}).each do |lang,translate|
@site_template.update_language_translations(lang,
translate,
translations[lang]
)
end
end
# Save the localized values for variables
def update_localized_option_values(values,lang)
self.options[:localize_values] ||= {}
lang_values = self.options[:localize_values][lang] || {}
self.options[:options].each do |opt|
var_name = opt[0]
if(opt[5])
lang_values[var_name] = values[var_name].empty? ? nil : values[var_name]
end
end
self.options[:localize_values][lang] = lang_values
end
def update_language_translations(lang,translate,translation)
self.options[:localize] = self.options[:localize].collect do |loc|
translate.each do |idx,tr|
if tr == loc[0]
loc[1] ||= {}
loc[1][lang] = translation[idx]
end
end
loc
end
end
def update_zone_order!(zone_order)
zone_order.each_with_index do |zone_id,idx|
unless zone_id.empty?
self.site_template_zones.find_by_id(zone_id).update_attribute(:position,idx+1)
end
end
end
def localized_values(lang)
langs = self.options[:localize_values] || {}
langs[lang] || {}
end
def localized_options
loc_opt = []
self.options[:options] ||= []
self.options[:options].each do |opt|
if(opt[5])
loc_opt << opt
end
end
loc_opt
end
def self.render_template_head(site_template_id,lang)
parts = rendered_parts(site_template_id,lang,'head')
return '' unless parts
parts[0].body
end
def self.render_template_html(site_template_id,lang,&block)
(rendered_parts(site_template_id,lang) || []).each do |part|
yield part
end
end
def self.render_template_css(site_template_id,lang,all = true)
parts = rendered_parts(site_template_id,lang,'css')
return '' unless parts
if all.to_s == 'struct'
parts[0].body
elsif(all)
parts[0].body + parts[1].body
else
parts[1].body
end
end
def full_styles_hash(lang,override=true)
styles = design_style_details(lang,override) + structural_style_details(lang,override)
styles_hash = { }
styles.each do |style|
styles_hash[style[0]] = style[2].map { |elm| elm.join(':') + ";" }.join('')
end
styles_hash
end
# Return an array of styles from text
def design_style_details(lang,override = true)
css = override ? replace_images(self.style_design) : SiteTemplate.render_template_css(self.id,lang,false)
Util::CssParser.parse_full(css)
end
def structural_style_details(lang,override = true)
css = override ? replace_images(self.style_struct) : SiteTemplate.render_template_css(self.id,lang,'struct')
Util::CssParser.parse_full(css)
end
# Return a list of the general style classes in the
# the Design Styles
def self.css_design_styles(site_template_id,lang)
css = SiteTemplate.render_template_css(site_template_id,lang,false)
Util::CssParser.parse_names(css,['classes']).sort
end
def self.css_styles(site_template_id,lang)
css = SiteTemplate.render_template_css(site_template_id,lang,true)
Util::CssParser.parse_names(css).sort
end
def css_id
self.parent_id.blank? ? self.id : self.parent_id
end
def render_html(lang,&block)
SiteTemplate.render_template_html(self.id,lang,&block)
end
def render_css(lang,all = true)
SiteTemplate.render_css(self.id,lang,all)
end
def rendered_parts(lang,part='html')
parts = self.site_template_rendered_parts.find(:all,
:conditions => ['language = ? AND part=?',lang,part ])
return parts if parts.length > 0
SiteTemplateRenderedPart.transaction do
SiteTemplateRenderedPart.delete_all(
['site_template_id= ? AND language = ?',self.id,lang])
if create_rendered_parts(lang)
return rendered_parts(lang,part)
end
end
return nil
end
def render_variable(variable,value,language)
info = self.options[:options].find { |vr| vr[0] == variable }
if info
if info[5]
value = self.options[:localize_values][variable] || self.options[:values][variable]
elsif !value
value = self.options[:values][variable]
end
case info[2]
when 'color':
value
when 'image':
img = DomainFile.find_by_id(value)
img ? img.image_tag : '/images/spacer.gif'
when 'src':
img = DomainFile.find_by_id(value)
img ? img.url : ''
when 'url':
value
else
value
end
else
value.to_s
end
end
def self.add_standard_parsing!(context,options = {})
lang = options[:language]
values = options[:values] || {}
localize_values = options[:localize_values]
translations = options[:localize]
if localize_values
localize_values = localize_values[lang] || {}
else
localize_value = {}
end
translations ||= []
context.define_tag 'var' do |tag|
if tag.attr['trans']
# check language specific var,
val = (localize_values[tag.attr['name']] || values[tag.attr['name']] ).to_s
# else send normal var
else
# send normal var
val = values[tag.attr['name']].to_s
end
case tag.attr['type'].to_s
when 'color':
val
when 'image':
img = DomainFile.find_by_id(val)
if img
img.url
else
''
end
else
val
end
end
context.define_tag 'trans' do |tag|
txt = tag.expand
if options[:default_feature]
txt.t
else
result = translations.detect do |t|
t[0] == txt
end
if result
if result[1][lang].to_s.empty?
txt
else
result[1][lang]
end
else
txt
end
end
end
end
protected
def self.rendered_parts(site_template_id,lang,part='html')
parts = SiteTemplateRenderedPart.find(:all,
:conditions => ['site_template_id=? AND language = ? AND part=?',site_template_id,lang,part ])
return parts if parts.length > 0
self.find(site_template_id).rendered_parts(lang,part)
end
module ParsingMethods
def replace_images(body)
output_body = ''
re = Regexp.new("(['\"\(\>])images\/([a-zA-Z0-9_\\-\\/. ]+?)(['\"\<\)])" ,Regexp::IGNORECASE | Regexp::MULTILINE)
parent_folder = self.domain_file
if parent_folder
while( mtch = re.match(body) )
output_body += mtch.pre_match
# do search by parent folder id
pieces = mtch[2].split("/")
folder = parent_folder
while pieces.length > 1
folder = parent_folder.children.find_by_name(pieces.shift)
end
img = folder.children.find_by_name(pieces[0]) if folder
if img
if self.is_a?(SiteTemplate) && self.template_type != 'site'
output_body += mtch[1] + Configuration.domain_link(img.url()) + mtch[3]
else
output_body += mtch[1] + img.url() + mtch[3]
end
else
output_body += mtch[1] + '/images/no_image.gif' + mtch[3]
end
body = mtch.post_match
end
output_body += body.to_s
else
output_body = body.to_s
end
output_body
end
end
include SiteTemplate::ParsingMethods
def create_rendered_parts(lang)
return false unless Configuration.languages.include?(lang)
# find each images/, replace with actual image if it exists
# Else replace with no_image image
unless self.parent_id
struct_css = replace_images(self.style_struct)
design_css = replace_images(self.style_design)
head_html = replace_images(self.head.to_s)
end
output_body = replace_images(self.template_html)
body = self.template_html
if self.parent_id
values = self.parent_template.options[:values]
localize_values = self.parent_template.options[:localize_values]
translations = self.parent_template.options[:localize]
else
values = self.options[:values]
localize_values = self.options[:localize_values]
translations = self.options[:localize]
end
# get a rendering context
parser_context = SiteTemplateContext.new()
SiteTemplate.add_standard_parsing!(parser_context,:values => values,
:language => lang,
:localize_values => localize_values,
:localize => translations)
parser_context.define_tag 'zone' do |tag|
"<cms:zone:#{tag.attr['name']}>"
end
parser_context.define_tag 'var' do |tag|
"<cms:var:#{tag.attr['name']}:#{tag.attr['type']}>"
end
parser = Radius::Parser.new(parser_context, :tag_prefix => 'cms')
# parse css
struct_css = parser.parse(struct_css)
self.site_template_rendered_parts.create(
:zone_position => -1,
:part => 'css',
:body => struct_css,
:language => lang,
:idx => 1)
# insert RenderedPart
design_css = parser.parse(design_css)
self.site_template_rendered_parts.create(
:zone_position => -1,
:part => 'css',
:body => design_css,
:language => lang,
:idx => 2)
head_html = parser.parse(head_html)
self.site_template_rendered_parts.create(
:zone_position => -1,
:part => 'head',
:body => head_html,
:language => lang,
:idx => 1
)
# parse html
body = parser.parse(output_body)
re = Regexp.new("\<cms\:(zone|var)\:([^>]+)\>",Regexp::IGNORECASE | Regexp::MULTILINE)
output_body = ''
part_idx = 1
while( mtch = re.match(body) )
match_type = mtch[1]
if(match_type == 'zone')
zone_name = mtch[2]
zone = self.site_template_zones.find_by_name(zone_name) || self.site_template_zones.create(:name => zone_name)
self.site_template_rendered_parts.create(
:zone_position => zone.position,
:part => 'html',
:body => mtch.pre_match,
:language => lang,
:idx => part_idx)
else
vals = mtch[2].split(":")
match_name = vals[0]
match_type = vals[1].blank? ? 'string' : vals[1]
self.site_template_rendered_parts.create(
:zone_position => -1,
:part => 'html',
:body => mtch.pre_match,
:language => lang,
:idx => part_idx,
:variable => match_name)
end
part_idx+=1
body = mtch.post_match
end
self.site_template_rendered_parts.create(
:zone_position => -1,
:part => 'html',
:body => body,
:language => lang,
:idx => part_idx)
return true
end
def update_zones(zones)
existing_zones = self.site_template_zones.to_a
# Get rid of any zones no longer in the template
existing_zones.each do |existing_zone|
if zones.include?(existing_zone.name)
zones.delete(existing_zone.name)
else
existing_zone.destroy
end
end
# And add any new ones to the end
zones.each do |zone|
self.site_template_zones.create(:name => zone)
end
end
def update_options(opts)
self.options ||= {}
self.options[:options] = opts
end
def before_create
self.options ||= {}
self.options = { :options => self.options[:options] || [],
:presets => self.options[:presets] || [],
:values => self.options[:values] || {},
:localize_values => self.options[:localize_values] || {},
:localize => self.options[:localize] || []
}
end
def after_save
self.site_template_rendered_parts.clear
if self.domain_file_id != @old_domain_file_id
old_fold = DomainFile.find_by_id(@old_domain_file_id) if @old_domain_file_id
old_fold.update_attribute(:special,'') if old_fold
new_fold = DomainFile.find_by_id(self.domain_file_id)
new_fold.update_attribute(:special,'template') if new_fold
# Resave all the child site features that don't have their own set image folder
self.site_features.find(:all,:conditions => 'image_folder_id IS NULL').each do |feature|
feature.save
end
self.site_features.find(:all,:conditions => 'image_folder_id IS NULL').each do |feature|
feature.save
end
end
end
def update_localize(translate)
# Remove duplicates
translate.uniq!
cur = self.options[:localize] || []
localize = translate.collect do |loc|
trans = {}
cur.each do |exist|
trans = exist[1] if exist[0] == loc
end
[ loc, trans ]
end
self.options[:localize] = localize
end
public
def export_to_bundle(bundler)
bundler.add_folder(self.domain_file) if self.domain_file
data = self.attributes.slice('name', 'description', 'template_html', 'options', 'style_struct', 'style_design', 'template_type', 'head', 'doctype', 'partial', 'lightweight', 'preprocessor', 'domain_file_id')
data['features'] = self.site_features.collect { |feature| feature.export_to_bundle(bundler) }.compact
data['children'] = self.child_templates.collect { |child| child.export_to_bundle(bundler) }
data['zones'] = self.site_template_zones.collect { |zone| zone.name }
data
end
def self.import_bundle(bundler, data, opts={})
# Get the new images folder
domain_file_id = data['domain_file_id'] ? bundler.get_new_input_id(DomainFile, data['domain_file_id']) : nil
# Create the site template
site_template = nil
site_template = SiteTemplate.find_by_parent_id_and_name(data['parent_id'], data['name']) if opts[:replace_same]
site_template ||= SiteTemplate.new(:parent_id => data['parent_id'], :name => data['name'])
site_template.update_attributes data.slice('description', 'template_html', 'options', 'style_struct', 'style_design', 'template_type', 'head', 'doctype', 'partial', 'lightweight', 'preprocessor').merge('domain_file_id' => domain_file_id)
# Create the zones
site_template.site_template_zones.clear
data['zones'].each_with_index do |name, idx|
site_template.site_template_zones.create :name => name, :position => (idx+1)
end
# Create the features
site_template.site_features.clear
data['features'].each do |feature|
image_folder_id = feature['image_folder_id'] ? bundler.get_new_input_id(DomainFile, feature['image_folder_id']) : nil
site_template.site_features.create feature.merge('image_folder_id' => image_folder_id)
end
# Create the templates children
data['children'].each do |child|
child['parent_id'] = site_template.id
SiteTemplate.import_bundle bundler, child, opts
end
site_template
end
def apply_to_site(version, opts={})
version.root_node.push_modifier('template') do |mod|
mod.options.template_id = self.id
mod.move_to_top
mod.save
end
# Apply theme features to existing paragraphs
if opts[:features]
feature_hash = SiteFeature.feature_hash
revisions = {}
self.site_features.each do |feature|
next unless feature_hash[feature.feature_type]
feature_hash[feature.feature_type].each do |info|
PageParagraph.live_paragraphs.with_feature(*info).group_by(&:page_revision_id).each do |page_revision_id, paragraphs|
revisions[page_revision_id] ||= []
revisions[page_revision_id] += paragraphs.map { |para| [para.identity_hash, feature.id] }
end
end
end
revisions.each do |page_revision_id, paragraphs|
rv = PageRevision.find(page_revision_id).create_temporary
paragraphs.each do |info|
para = rv.page_paragraphs.detect { |p| p.identity_hash == info[0] }
para.update_attribute :site_feature_id, info[1]
end
rv.make_real
end
end
end
end