knitkit 2.0.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 (165) hide show
  1. data/GPL-3-LICENSE +674 -0
  2. data/README.md +6 -0
  3. data/Rakefile +30 -0
  4. data/app/assets/javascripts/knitkit/application.js +9 -0
  5. data/app/assets/stylesheets/knitkit/application.css +7 -0
  6. data/app/controllers/knitkit/articles_controller.rb +7 -0
  7. data/app/controllers/knitkit/base_controller.rb +45 -0
  8. data/app/controllers/knitkit/blogs_controller.rb +27 -0
  9. data/app/controllers/knitkit/comments_controller.rb +18 -0
  10. data/app/controllers/knitkit/erp_app/desktop/app_controller.rb +135 -0
  11. data/app/controllers/knitkit/erp_app/desktop/articles_controller.rb +144 -0
  12. data/app/controllers/knitkit/erp_app/desktop/comments_controller.rb +43 -0
  13. data/app/controllers/knitkit/erp_app/desktop/content_controller.rb +27 -0
  14. data/app/controllers/knitkit/erp_app/desktop/file_assets_controller.rb +145 -0
  15. data/app/controllers/knitkit/erp_app/desktop/image_assets_controller.rb +28 -0
  16. data/app/controllers/knitkit/erp_app/desktop/inquiries_controller.rb +68 -0
  17. data/app/controllers/knitkit/erp_app/desktop/position_controller.rb +20 -0
  18. data/app/controllers/knitkit/erp_app/desktop/theme_controller.rb +272 -0
  19. data/app/controllers/knitkit/erp_app/desktop/versions_controller.rb +135 -0
  20. data/app/controllers/knitkit/erp_app/desktop/website_controller.rb +177 -0
  21. data/app/controllers/knitkit/erp_app/desktop/website_nav_controller.rb +143 -0
  22. data/app/controllers/knitkit/erp_app/desktop/website_section_controller.rb +116 -0
  23. data/app/controllers/knitkit/unauthorized_controller.rb +8 -0
  24. data/app/controllers/knitkit/website_sections_controller.rb +17 -0
  25. data/app/helpers/application_helper.rb +132 -0
  26. data/app/models/article.rb +14 -0
  27. data/app/models/blog.rb +21 -0
  28. data/app/models/comment.rb +24 -0
  29. data/app/models/content.rb +158 -0
  30. data/app/models/extensions/user.rb +5 -0
  31. data/app/models/published_element.rb +9 -0
  32. data/app/models/published_website.rb +118 -0
  33. data/app/models/theme.rb +239 -0
  34. data/app/models/website.rb +457 -0
  35. data/app/models/website_host.rb +3 -0
  36. data/app/models/website_inquiry.rb +11 -0
  37. data/app/models/website_inquiry_mailer.rb +12 -0
  38. data/app/models/website_nav.rb +17 -0
  39. data/app/models/website_nav_item.rb +21 -0
  40. data/app/models/website_section.rb +152 -0
  41. data/app/models/website_section_content.rb +4 -0
  42. data/app/views/knitkit/articles/index.html.erb +3 -0
  43. data/app/views/knitkit/articles/show.html.erb +4 -0
  44. data/app/views/knitkit/blogs/_add_comment.html.erb +28 -0
  45. data/app/views/knitkit/blogs/_comment.html.erb +8 -0
  46. data/app/views/knitkit/blogs/index.html.erb +51 -0
  47. data/app/views/knitkit/blogs/index.rss.builder +23 -0
  48. data/app/views/knitkit/blogs/show.html.erb +22 -0
  49. data/app/views/knitkit/unauthorized/index.html.erb +4 -0
  50. data/app/views/knitkit/website_sections/index.html.erb +6 -0
  51. data/app/views/layouts/knitkit/base.html.erb +59 -0
  52. data/app/views/menus/knitkit/_default_menu.html.erb +23 -0
  53. data/app/views/menus/knitkit/_default_section_menu.html.erb +27 -0
  54. data/app/views/menus/knitkit/_default_sub_menu.html.erb +45 -0
  55. data/app/views/menus/knitkit/_default_sub_section_menu.html.erb +49 -0
  56. data/app/views/shared/knitkit/_bread_crumb.html.erb +7 -0
  57. data/app/views/shared/knitkit/_footer.html.erb +3 -0
  58. data/app/views/website_inquiry_mailer/inquiry.erb +3 -0
  59. data/app/widgets/contact_us/base.rb +86 -0
  60. data/app/widgets/contact_us/javascript/contact_us.js +13 -0
  61. data/app/widgets/contact_us/views/_contact_form.html.erb +36 -0
  62. data/app/widgets/contact_us/views/error.html.erb +10 -0
  63. data/app/widgets/contact_us/views/index.html.erb +1 -0
  64. data/app/widgets/contact_us/views/layouts/base.html.erb +7 -0
  65. data/app/widgets/contact_us/views/success.html.erb +4 -0
  66. data/app/widgets/google_map/base.rb +48 -0
  67. data/app/widgets/google_map/javascript/google_map.js +174 -0
  68. data/app/widgets/google_map/views/index.html.erb +41 -0
  69. data/app/widgets/login/base.rb +61 -0
  70. data/app/widgets/login/javascript/login.js +162 -0
  71. data/app/widgets/login/views/index.html.erb +37 -0
  72. data/app/widgets/login/views/layouts/base.html.erb +14 -0
  73. data/app/widgets/login/views/login_header.html.erb +9 -0
  74. data/app/widgets/login/views/reset_password.html.erb +26 -0
  75. data/app/widgets/manage_profile/base.rb +327 -0
  76. data/app/widgets/manage_profile/javascript/manage_profile.js +11 -0
  77. data/app/widgets/manage_profile/views/_contact_information_form.html.erb +180 -0
  78. data/app/widgets/manage_profile/views/_password_form.html.erb +20 -0
  79. data/app/widgets/manage_profile/views/_user_information_form.html.erb +30 -0
  80. data/app/widgets/manage_profile/views/contact_type_in_use.html.erb +4 -0
  81. data/app/widgets/manage_profile/views/default_type_error.html.erb +5 -0
  82. data/app/widgets/manage_profile/views/error.html.erb +3 -0
  83. data/app/widgets/manage_profile/views/index.html.erb +46 -0
  84. data/app/widgets/manage_profile/views/layouts/base.html.erb +1 -0
  85. data/app/widgets/manage_profile/views/password_blank.html.erb +4 -0
  86. data/app/widgets/manage_profile/views/password_invalid.html.erb +4 -0
  87. data/app/widgets/manage_profile/views/password_success.html.erb +3 -0
  88. data/app/widgets/manage_profile/views/success.html.erb +3 -0
  89. data/app/widgets/reset_password/base.rb +42 -0
  90. data/app/widgets/reset_password/javascript/reset_password.js +13 -0
  91. data/app/widgets/reset_password/views/index.html.erb +31 -0
  92. data/app/widgets/reset_password/views/layouts/base.html.erb +1 -0
  93. data/app/widgets/search/base.rb +80 -0
  94. data/app/widgets/search/javascript/search.js +31 -0
  95. data/app/widgets/search/views/_search.html.erb +39 -0
  96. data/app/widgets/search/views/index.html.erb +3 -0
  97. data/app/widgets/search/views/layouts/base.html.erb +24 -0
  98. data/app/widgets/search/views/show.html.erb +48 -0
  99. data/app/widgets/signup/base.rb +72 -0
  100. data/app/widgets/signup/javascript/signup.js +13 -0
  101. data/app/widgets/signup/views/_signup_form.html.erb +36 -0
  102. data/app/widgets/signup/views/error.html.erb +11 -0
  103. data/app/widgets/signup/views/index.html.erb +1 -0
  104. data/app/widgets/signup/views/layouts/base.html.erb +7 -0
  105. data/app/widgets/signup/views/success.html.erb +4 -0
  106. data/config/environment.rb +0 -0
  107. data/config/routes.rb +41 -0
  108. data/db/data_migrations/20110509223702_add_publisher_role.rb +11 -0
  109. data/db/data_migrations/20110816153456_add_knitkit_application.rb +31 -0
  110. data/db/data_migrations/upgrade/20111011203718_create_paths_for_sections.rb +13 -0
  111. data/db/data_migrations/upgrade/20111216192114_add_secured_models_to_menu_items.rb +18 -0
  112. data/db/data_migrations/upgrade/20111216202819_set_contents_iid_to_permalink_where_null.rb +12 -0
  113. data/db/migrate/20110211002317_setup_knitkit.rb +259 -0
  114. data/db/migrate/upgrade/20111014190442_update_contents.rb +25 -0
  115. data/db/migrate/upgrade/20111014201502_add_published_by_to_published_elements.rb +22 -0
  116. data/db/migrate/upgrade/20111017133851_add_iid_to_section.rb +21 -0
  117. data/db/migrate/upgrade/20111027145006_add_in_menu_to_section.rb +19 -0
  118. data/lib/knitkit.rb +12 -0
  119. data/lib/knitkit/engine.rb +26 -0
  120. data/lib/knitkit/extensions.rb +18 -0
  121. data/lib/knitkit/extensions/action_controller/theme_support/acts_as_themed_controller.rb +88 -0
  122. data/lib/knitkit/extensions/active_record/acts_as_commentable.rb +28 -0
  123. data/lib/knitkit/extensions/active_record/acts_as_publishable.rb +38 -0
  124. data/lib/knitkit/extensions/active_record/theme_support/has_many_themes.rb +34 -0
  125. data/lib/knitkit/extensions/compass/widgets/base.rb +53 -0
  126. data/lib/knitkit/extensions/core/array.rb +5 -0
  127. data/lib/knitkit/extensions/railties/action_view.rb +187 -0
  128. data/lib/knitkit/extensions/railties/theme_support/asset_tag_helper.rb +198 -0
  129. data/lib/knitkit/extensions/railties/theme_support/theme_file_resolver.rb +44 -0
  130. data/lib/knitkit/routing_filter/section_router.rb +55 -0
  131. data/lib/knitkit/syntax_validator.rb +32 -0
  132. data/lib/knitkit/version.rb +3 -0
  133. data/lib/tasks/knitkit_tasks.rake +4 -0
  134. data/public/images/knitkit/bullet.png +0 -0
  135. data/public/images/knitkit/content.png +0 -0
  136. data/public/images/knitkit/footer.png +0 -0
  137. data/public/images/knitkit/graphic.png +0 -0
  138. data/public/images/knitkit/greyFadeDown.png +0 -0
  139. data/public/images/knitkit/link.png +0 -0
  140. data/public/images/knitkit/logo.png +0 -0
  141. data/public/images/knitkit/menu.png +0 -0
  142. data/public/images/knitkit/menu_select.png +0 -0
  143. data/public/images/knitkit/search.png +0 -0
  144. data/public/javascripts/datepicker.js +440 -0
  145. data/public/javascripts/erp_app/desktop/applications/knitkit/articles_grid_panel.js +473 -0
  146. data/public/javascripts/erp_app/desktop/applications/knitkit/center_region.js +576 -0
  147. data/public/javascripts/erp_app/desktop/applications/knitkit/comments_grid_panel.js +217 -0
  148. data/public/javascripts/erp_app/desktop/applications/knitkit/east_region.js +26 -0
  149. data/public/javascripts/erp_app/desktop/applications/knitkit/file_assets_panel.js +109 -0
  150. data/public/javascripts/erp_app/desktop/applications/knitkit/image_assets_data_view.js +30 -0
  151. data/public/javascripts/erp_app/desktop/applications/knitkit/image_assets_panel.js +160 -0
  152. data/public/javascripts/erp_app/desktop/applications/knitkit/inquiries_grid_panel.js +46 -0
  153. data/public/javascripts/erp_app/desktop/applications/knitkit/module.js +46 -0
  154. data/public/javascripts/erp_app/desktop/applications/knitkit/publish_window.js +92 -0
  155. data/public/javascripts/erp_app/desktop/applications/knitkit/published_grid_panel.js +220 -0
  156. data/public/javascripts/erp_app/desktop/applications/knitkit/section_articles_grid_panel.js +613 -0
  157. data/public/javascripts/erp_app/desktop/applications/knitkit/themes_tree_panel.js +573 -0
  158. data/public/javascripts/erp_app/desktop/applications/knitkit/versions_grid_panel.js +664 -0
  159. data/public/javascripts/erp_app/desktop/applications/knitkit/west_region.js +2161 -0
  160. data/public/javascripts/erp_app/desktop/applications/knitkit/widgets_panel.js +52 -0
  161. data/public/stylesheets/datepicker.css +121 -0
  162. data/public/stylesheets/erp_app/desktop/applications/knitkit/knitkit.css +114 -0
  163. data/public/stylesheets/extjs/resources/css/knitkit_extjs_4.css +219 -0
  164. data/public/stylesheets/knitkit/style.css +394 -0
  165. metadata +289 -0
@@ -0,0 +1,5 @@
1
+ User.class_eval do
2
+ has_many :comments, :dependent => :nullify
3
+
4
+ alias :approved_comments :comments
5
+ end
@@ -0,0 +1,9 @@
1
+ class PublishedElement < ActiveRecord::Base
2
+ belongs_to :published_website
3
+ belongs_to :published_element_record, :polymorphic => true
4
+ belongs_to :published_by, :class_name => "User"
5
+
6
+ def published_by_username
7
+ self.published_by.username rescue ''
8
+ end
9
+ end
@@ -0,0 +1,118 @@
1
+ class PublishedWebsite < ActiveRecord::Base
2
+ belongs_to :website
3
+ belongs_to :published_by, :class_name => "User"
4
+ has_many :published_elements, :dependent => :destroy
5
+
6
+ def published_by_username
7
+ self.published_by.username rescue ''
8
+ end
9
+
10
+ def self.activate(website, version, current_user)
11
+ published_websites = self.where('website_id = ?', website.id).all
12
+ published_websites.each do |published_website|
13
+ if published_website.version == version
14
+ published_website.active = true
15
+ published_website.published_by = current_user
16
+ else
17
+ published_website.active = false
18
+ end
19
+ published_website.save
20
+ end
21
+ end
22
+
23
+ def publish(comment, current_user)
24
+ new_publication = clone_publication(1, comment, current_user)
25
+ elements = []
26
+
27
+ #get a publish sections
28
+ website_sections = new_publication.website.website_sections
29
+ website_sections = website_sections | website_sections.collect{|section| section.descendants}.flatten
30
+ website_sections.each do |website_section|
31
+ if new_publication.published_elements.where('published_element_record_id = ? and (published_element_record_type = ? or published_element_record_type = ?)', website_section.id, website_section.class.to_s, website_section.class.superclass.to_s).first.nil?
32
+ published_element = PublishedElement.new
33
+ published_element.published_website = new_publication
34
+ published_element.published_element_record = website_section
35
+ published_element.version = website_section.version
36
+ published_element.published_by = current_user
37
+ published_element.save
38
+ end
39
+
40
+ elements = elements | website_section.contents
41
+ end
42
+
43
+ #make sure all elements have published_element objects
44
+ elements.each do |element|
45
+ if new_publication.published_elements.where('published_element_record_id = ? and (published_element_record_type = ? or published_element_record_type = ?)', element.id, element.class.to_s, element.class.superclass.to_s).first.nil?
46
+ published_element = PublishedElement.new
47
+ published_element.published_website = new_publication
48
+ published_element.published_element_record = element
49
+ published_element.version = element.version
50
+ published_element.published_by = current_user
51
+ published_element.save
52
+ end
53
+ end
54
+
55
+ #get latest version for all elements
56
+ new_publication.published_elements.each do |published_element|
57
+ published_element.version = published_element.published_element_record.version
58
+ published_element.save
59
+ end
60
+
61
+ #check if we want to auto active this publication
62
+ if new_publication.website.auto_activate_publication?
63
+ PublishedWebsite.activate(new_publication.website, new_publication.version, current_user)
64
+ end
65
+ end
66
+
67
+ def publish_element(comment, element, version, current_user)
68
+ new_publication = clone_publication(0.1, comment, current_user)
69
+
70
+ published_element = new_publication.published_elements.where('published_element_record_id = ? and (published_element_record_type = ? or published_element_record_type = ?)', element.id, element.class.to_s, element.class.superclass.to_s).first
71
+
72
+ unless published_element.nil?
73
+ published_element.version = version
74
+ published_element.published_by = current_user
75
+ published_element.save
76
+ else
77
+ new_published_element = PublishedElement.new
78
+ new_published_element.published_website = new_publication
79
+ new_published_element.published_element_record = element
80
+ new_published_element.version = version
81
+ new_published_element.published_by = current_user
82
+ new_published_element.save
83
+ end
84
+
85
+ #check if we want to auto active this publication
86
+ if new_publication.website.auto_activate_publication?
87
+ PublishedWebsite.activate(new_publication.website, new_publication.version, current_user)
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def clone_publication(version_increment, comment, current_user)
94
+ #create new PublishedWebsite with comment
95
+ published_website = PublishedWebsite.new
96
+ published_website.website = self.website
97
+ if version_increment == 1
98
+ published_website.version = (self.version.to_i + version_increment)
99
+ else
100
+ published_website.version = (self.version + version_increment).round(6)
101
+ end
102
+ published_website.published_by = current_user
103
+ published_website.comment = comment
104
+ published_website.save
105
+
106
+ #create new PublishedWebsiteElements
107
+ published_elements.each do |published_element|
108
+ new_published_element = PublishedElement.new
109
+ new_published_element.published_website = published_website
110
+ new_published_element.published_element_record = published_element.published_element_record
111
+ new_published_element.version = published_element.version
112
+ new_published_element.published_by = current_user
113
+ new_published_element.save
114
+ end
115
+
116
+ published_website
117
+ end
118
+ end
@@ -0,0 +1,239 @@
1
+ require 'yaml'
2
+ require 'fileutils'
3
+
4
+ class Theme < ActiveRecord::Base
5
+ THEME_STRUCTURE = ['stylesheets', 'javascripts', 'images', 'templates']
6
+ BASE_LAYOUTS_VIEWS_PATH = "#{Knitkit::Engine.root.to_s}/app/views"
7
+ KNITKIT_WEBSITE_STYLESHEETS_PATH = "#{Knitkit::Engine.root.to_s}/public/stylesheets/knitkit"
8
+ KNITKIT_WEBSITE_IMAGES_PATH = "#{Knitkit::Engine.root.to_s}/public/images/knitkit"
9
+
10
+ has_file_assets
11
+
12
+ class << self
13
+ def root_dir
14
+ @@root_dir ||= "#{Rails.root}/public"
15
+ end
16
+
17
+ def base_dir(website)
18
+ "#{root_dir}/sites/site-#{website.id}/themes"
19
+ end
20
+
21
+ def import(file, website)
22
+ name_and_id = file.original_filename.to_s.gsub(/(^.*(\\|\/))|(\.zip$)/, '')
23
+ theme_name = name_and_id.split('[').first
24
+ theme_id = name_and_id.split('[').last.gsub(']','')
25
+ return false unless valid_theme?(file)
26
+ Theme.create(:name => theme_name, :theme_id => theme_id, :website => website).tap do |theme|
27
+ theme.import(file)
28
+ end
29
+ end
30
+
31
+ def make_tmp_dir
32
+ random = Time.now.to_i.to_s.split('').sort_by { rand }
33
+ Pathname.new(Rails.root.to_s + "/tmp/themes/tmp_#{random}/").tap do |dir|
34
+ FileUtils.mkdir_p(dir) unless dir.exist?
35
+ end
36
+ end
37
+
38
+ def valid_theme?(file)
39
+ valid = false
40
+ Zip::ZipFile.open(file.path) do |zip|
41
+ zip.sort.each do |entry|
42
+ entry.name.split('/').each do |file|
43
+ valid = true if THEME_STRUCTURE.include?(file)
44
+ end
45
+ end
46
+ end
47
+ valid
48
+ end
49
+ end
50
+
51
+ belongs_to :website
52
+
53
+ has_permalink :name, :theme_id, :scope => :website_id,
54
+ :only_when_blank => false, :sync_url => true
55
+
56
+ validates :name, :presence => {:message => 'Name cannot be blank'}
57
+ validates_uniqueness_of :theme_id, :scope => :website_id
58
+
59
+ before_destroy :delete_theme_files!
60
+
61
+ def path
62
+ "#{self.class.base_dir(website)}/#{theme_id}"
63
+ end
64
+
65
+ def url
66
+ "sites/site-#{website.id}/themes/#{theme_id}"
67
+ end
68
+
69
+ def activate!
70
+ update_attributes! :active => true
71
+ end
72
+
73
+ def deactivate!
74
+ update_attributes! :active => false
75
+ end
76
+
77
+ def themed_widgets
78
+ widgets = []
79
+ ErpApp::Widgets::Base.installed_widgets.each do |widget_name|
80
+ widgets << widget_name unless self.files.where("directory like '/#{File.join(self.url, 'widgets', widget_name)}%'").empty?
81
+ end
82
+ widgets
83
+ end
84
+
85
+ def non_themed_widgets
86
+ already_themed_widgets = self.themed_widgets
87
+ widgets = []
88
+ ErpApp::Widgets::Base.installed_widgets.each do |widget_name|
89
+ widgets << widget_name unless already_themed_widgets.include?(widget_name)
90
+ end
91
+ widgets
92
+ end
93
+
94
+ def create_layouts_for_widget(widget)
95
+ file_support = ErpTechSvcs::FileSupport::Base.new
96
+ views_location = "::Widgets::#{widget.camelize}::Base".constantize.views_location
97
+ create_theme_files_for_directory_node(file_support.build_tree(views_location, :preload => true), :widgets, :path_to_replace => views_location, :widget_name => widget)
98
+ end
99
+
100
+ def about
101
+ %w(name author version homepage summary).inject({}) do |result, key|
102
+ result[key] = send(key)
103
+ result
104
+ end
105
+ end
106
+
107
+ def import(file)
108
+ file_support = ErpTechSvcs::FileSupport::Base.new(:storage => ErpTechSvcs::FileSupport.options[:storage])
109
+ file = ActionController::UploadedTempfile.new("uploaded-theme").tap do |f|
110
+ f.puts file.read
111
+ f.original_filename = file.original_filename
112
+ f.read # no idea why we need this here, otherwise the zip can't be opened
113
+ end unless file.path
114
+
115
+ theme_root = Theme.find_theme_root(file)
116
+
117
+ Zip::ZipFile.open(file.path) do |zip|
118
+ zip.each do |entry|
119
+ if entry.name == 'about.yml'
120
+ #TODO
121
+ #FIXME this does not work for some reason
122
+ data = ''
123
+ entry.get_input_stream { |io| data = io.read }
124
+ data = StringIO.new(data) if data.present?
125
+ about = YAML.load(data)
126
+ self.author = about['author'] if about['author']
127
+ self.version = about['version'] if about['version']
128
+ self.homepage = about['homepage'] if about['homepage']
129
+ self.summary = about['summary'] if about['summary']
130
+ else
131
+ name = entry.name.sub(/__MACOSX\//, '')
132
+ name = Theme.strip_path(entry.name, theme_root)
133
+ data = ''
134
+ entry.get_input_stream { |io| data = io.read }
135
+ data = StringIO.new(data) if data.present?
136
+ theme_file = self.files.where("name = ? and directory = ?", File.basename(name), File.join("",self.url,File.dirname(name))).first
137
+ unless theme_file.nil?
138
+ theme_file.data = data
139
+ theme_file.save
140
+ else
141
+ self.add_file(data, File.join(file_support.root,self.url,name)) rescue next
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ def export
149
+ file_support = ErpTechSvcs::FileSupport::Base.new(:storage => ErpTechSvcs::FileSupport.options[:storage])
150
+ tmp_dir = Theme.make_tmp_dir
151
+ (tmp_dir + "#{name}[#{theme_id}].zip").tap do |file_name|
152
+ file_name.unlink if file_name.exist?
153
+ Zip::ZipFile.open(file_name, Zip::ZipFile::CREATE) do |zip|
154
+ files.each {|file|
155
+ contents = file_support.get_contents(File.join(file_support.root,file.directory,file.name))
156
+ relative_path = file.directory.sub("/#{url}",'')
157
+ path = FileUtils.mkdir_p(File.join(tmp_dir,relative_path))
158
+ full_path = File.join(path,file.name)
159
+ File.open(full_path, 'w+') {|f| f.puts(contents) }
160
+ zip.add(File.join(relative_path[1..relative_path.length],file.name), full_path) if ::File.exists?(full_path)
161
+ }
162
+ ::File.open(tmp_dir + 'about.yml', 'w') { |f| f.puts(about.to_yaml) }
163
+ zip.add('about.yml', tmp_dir + 'about.yml')
164
+ end
165
+ end
166
+ end
167
+
168
+ def has_template?(directory, name)
169
+ self.templates.find{|item| item.directory == File.join(path,directory).gsub(Rails.root.to_s, '') and item.name == name}
170
+ end
171
+
172
+ class << self
173
+ def find_theme_root(file)
174
+ theme_root = ''
175
+ Zip::ZipFile.open(file.path) do |zip|
176
+ zip.each do |entry|
177
+ entry.name.sub!(/__MACOSX\//, '')
178
+ if theme_root = root_in_path(entry.name)
179
+ break
180
+ end
181
+ end
182
+ end
183
+ theme_root
184
+ end
185
+
186
+ def root_in_path(path)
187
+ root_found = false
188
+ theme_root = ''
189
+ path.split('/').each do |piece|
190
+ if piece == 'about.yml' || THEME_STRUCTURE.include?(piece)
191
+ root_found = true
192
+ else
193
+ theme_root += piece + '/' if !piece.match('\.') && !root_found
194
+ end
195
+ end
196
+ root_found ? theme_root : false
197
+ end
198
+
199
+ def strip_path(file_name, path)
200
+ file_name.sub(path, '')
201
+ end
202
+ end
203
+
204
+ def delete_theme_files!
205
+ file_support = ErpTechSvcs::FileSupport::Base.new(:storage => ErpTechSvcs::FileSupport.options[:storage])
206
+ file_support.delete_file(File.join(file_support.root,self.url), :force => true)
207
+ end
208
+
209
+ def create_theme_files!
210
+ file_support = ErpTechSvcs::FileSupport::Base.new
211
+ create_theme_files_for_directory_node(file_support.build_tree(BASE_LAYOUTS_VIEWS_PATH, :preload => true), :templates, :path_to_replace => BASE_LAYOUTS_VIEWS_PATH)
212
+ create_theme_files_for_directory_node(file_support.build_tree(KNITKIT_WEBSITE_STYLESHEETS_PATH, :preload => true), :stylesheets, :path_to_replace => KNITKIT_WEBSITE_STYLESHEETS_PATH)
213
+ create_theme_files_for_directory_node(file_support.build_tree(KNITKIT_WEBSITE_IMAGES_PATH, :preload => true), :images, :path_to_replace => KNITKIT_WEBSITE_IMAGES_PATH)
214
+ end
215
+
216
+ private
217
+
218
+ def create_theme_files_for_directory_node(node, type, options={})
219
+ node[:children].each do |child_node|
220
+ child_node[:leaf] ? save_theme_file(child_node[:id], type, options) : create_theme_files_for_directory_node(child_node, type, options)
221
+ end
222
+ end
223
+
224
+ def save_theme_file(path, type, options)
225
+ contents = IO.read(path)
226
+ contents.gsub!("../../images/knitkit","../images") unless path.scan('style.css').empty?
227
+ contents.gsub!("<%= static_stylesheet_link_tag('knitkit/style.css') %>","<%= theme_stylesheet_link_tag('#{self.theme_id}','style.css') %>") unless path.scan('base.html.erb').empty?
228
+
229
+ path = case type
230
+ when :widgets
231
+ path.gsub(options[:path_to_replace], "/#{self.url}/widgets/#{options[:widget_name]}")
232
+ else
233
+ path.gsub(options[:path_to_replace], "/#{self.url}/#{type.to_s}")
234
+ end
235
+
236
+ self.add_file(contents, path)
237
+ end
238
+
239
+ end
@@ -0,0 +1,457 @@
1
+ class Website < ActiveRecord::Base
2
+ after_destroy :remove_sites_directory, :remove_website_role
3
+ after_create :setup_website
4
+
5
+ has_file_assets
6
+
7
+ has_many :published_websites, :dependent => :destroy
8
+ has_many :website_hosts, :dependent => :destroy
9
+ has_many :website_navs, :dependent => :destroy
10
+ has_many :website_inquiries, :dependent => :destroy
11
+
12
+ alias :hosts :website_hosts
13
+
14
+ has_many :website_sections, :dependent => :destroy, :order => :lft do
15
+ def paths
16
+ collect{|website_section| website_section.paths}.flatten
17
+ end
18
+
19
+ #have to do a sort by to override what awesome nested set does for the order by (lft)
20
+ def positioned
21
+ where('parent_id is null').sort_by!(&:position)
22
+ end
23
+
24
+ def update_paths!
25
+ all.each{|section| section.self_and_descendants.each{|_section| _section.update_path!}}
26
+ end
27
+ end
28
+ alias :sections :website_sections
29
+
30
+ has_many :themes, :dependent => :destroy do
31
+ def active
32
+ where('active = 1')
33
+ end
34
+ end
35
+
36
+ def all_section_paths
37
+ ActiveRecord::Base.connection.execute("select path from website_sections where website_id = #{self.id}").collect{|row| row['path']}
38
+ end
39
+
40
+ def self.find_by_host(host)
41
+ website = nil
42
+ website_host = WebsiteHost.find_by_host(host)
43
+ website = website_host.website unless website_host.nil?
44
+ website
45
+ end
46
+
47
+ def deactivate_themes!
48
+ themes.each do |theme|
49
+ theme.deactivate!
50
+ end
51
+ end
52
+
53
+ def publish_element(comment, element, version, current_user)
54
+ self.published_websites.last.publish_element(comment, element, version, current_user)
55
+ end
56
+
57
+ def publish(comment, current_user)
58
+ self.published_websites.last.publish(comment, current_user)
59
+ end
60
+
61
+ def set_publication_version(version, current_user)
62
+ PublishedWebsite.activate(self, version, current_user)
63
+ end
64
+
65
+ def active_publication
66
+ self.published_websites.all.find{|item| item.active}
67
+ end
68
+
69
+ def role
70
+ Role.iid(website_role_iid)
71
+ end
72
+
73
+ def setup_website
74
+ PublishedWebsite.create(:website => self, :version => 0, :active => true, :comment => 'New Site Created')
75
+ Role.create(:description => "Website #{self.title}", :internal_identifier => website_role_iid)
76
+ end
77
+
78
+ def remove_sites_directory
79
+ file_support = ErpTechSvcs::FileSupport::Base.new(:storage => ErpTechSvcs::FileSupport.options[:storage])
80
+ begin
81
+ file_support.delete_file(File.join(file_support.root,"sites/site-#{self.id}"), :force => true)
82
+ rescue
83
+ #do nothing it may not exist
84
+ end
85
+ end
86
+
87
+ def remove_website_role
88
+ role.destroy if role
89
+ end
90
+
91
+ def setup_default_pages
92
+ # create default sections for each widget using widget layout
93
+ widget_classes = [
94
+ ::Widgets::ContactUs::Base,
95
+ ::Widgets::Search::Base,
96
+ ::Widgets::ManageProfile::Base,
97
+ ::Widgets::Login::Base,
98
+ ::Widgets::Signup::Base,
99
+ ::Widgets::ResetPassword::Base
100
+ ]
101
+ widget_classes.each do |widget_class|
102
+ website_section = WebsiteSection.new
103
+ website_section.title = widget_class.title
104
+ website_section.in_menu = true unless ["Login", "Sign Up", "Reset Password"].include?(widget_class.title)
105
+ website_section.layout = widget_class.base_layout
106
+ website_section.save
107
+ #make manage profile secured
108
+ website_section.add_role(self.role) if widget_class.title == 'Manage Profile'
109
+ self.website_sections << website_section
110
+ end
111
+ self.save
112
+ self.website_sections.update_paths!
113
+ end
114
+
115
+ def export_setup
116
+ setup_hash = {
117
+ :name => name,
118
+ :hosts => hosts.collect(&:host),
119
+ :title => title,
120
+ :subtitle => subtitle,
121
+ :email => email,
122
+ :auto_activate_publication => auto_activate_publication,
123
+ :email_inquiries => email_inquiries,
124
+ :sections => [],
125
+ :images => [],
126
+ :files => [],
127
+ :website_navs => []
128
+ }
129
+
130
+ setup_hash[:sections] = sections.positioned.collect do |website_section|
131
+ build_section_hash(website_section)
132
+ end
133
+
134
+ setup_hash[:website_navs] = website_navs.collect do |website_nav|
135
+ {
136
+ :name => website_nav.name,
137
+ :items => website_nav.items.positioned.map{|website_nav_item| build_menu_item_hash(website_nav_item)}
138
+ }
139
+ end
140
+
141
+ self.files.where("directory like '%/sites/site-#{self.id}/images%'").all.each do |image_asset|
142
+ setup_hash[:images] << {:path => image_asset.directory, :name => image_asset.name}
143
+ end
144
+
145
+ self.files.where("directory like '%/sites/site-#{self.id}/file_assets%'").all.each do |file_asset|
146
+ setup_hash[:files] << {:path => file_asset.directory, :name => file_asset.name}
147
+ end
148
+
149
+ setup_hash
150
+ end
151
+
152
+ def export
153
+ tmp_dir = Website.make_tmp_dir
154
+ file_support = ErpTechSvcs::FileSupport::Base.new(:storage => ErpTechSvcs::FileSupport.options[:storage])
155
+
156
+ sections_path = Pathname.new(File.join(tmp_dir,'sections'))
157
+ FileUtils.mkdir_p(sections_path) unless sections_path.exist?
158
+
159
+ articles_path = Pathname.new(File.join(tmp_dir,'articles'))
160
+ FileUtils.mkdir_p(articles_path) unless articles_path.exist?
161
+
162
+ excerpts_path = Pathname.new(File.join(tmp_dir,'excerpts'))
163
+ FileUtils.mkdir_p(excerpts_path) unless excerpts_path.exist?
164
+
165
+ image_assets_path = Pathname.new(File.join(tmp_dir,'images'))
166
+ FileUtils.mkdir_p(image_assets_path) unless image_assets_path.exist?
167
+
168
+ file_assets_path = Pathname.new(File.join(tmp_dir,'files'))
169
+ FileUtils.mkdir_p(file_assets_path) unless file_assets_path.exist?
170
+
171
+ sections.each do |website_section|
172
+ unless website_section.layout.blank?
173
+ File.open(File.join(sections_path,"#{website_section.title}.rhtml"), 'w+') {|f| f.puts(website_section.layout) }
174
+ end
175
+ end
176
+
177
+ contents = sections.collect(&:contents).flatten.uniq
178
+ contents.each do |content|
179
+ File.open(File.join(articles_path,"#{content.title}.html"), 'w+') {|f| f.puts(content.body_html) }
180
+ unless content.excerpt_html.blank?
181
+ File.open(File.join(excerpts_path,"#{content.title}.html"), 'w+') {|f| f.puts(content.excerpt_html) }
182
+ end
183
+ end
184
+
185
+ self.files.where("directory like '%/sites/site-#{self.id}/images%'").all.each do |image_asset|
186
+ contents = file_support.get_contents(File.join(file_support.root,image_asset.directory,image_asset.name))
187
+ File.open(File.join(image_assets_path,image_asset.name), 'w+') {|f| f.puts(contents) }
188
+ end
189
+
190
+ self.files.where("directory like '%/sites/site-#{self.id}/file_assets%'").all.each do |file_asset|
191
+ contents = file_support.get_contents(File.join(file_support.root,file_asset.directory,file_asset.name))
192
+ File.open(File.join(file_assets_path,file_asset.name), 'w+') {|f| f.puts(contents) }
193
+ end
194
+
195
+ files = []
196
+
197
+ Dir.entries(sections_path).each do |entry|
198
+ next if entry =~ /^\./
199
+ files << {:path => File.join(sections_path,entry), :name => File.join('sections/',File.basename(entry))}
200
+ end
201
+
202
+ Dir.entries(articles_path).each do |entry|
203
+ next if entry =~ /^\./
204
+ files << {:path => File.join(articles_path,entry), :name => File.join('articles/',File.basename(entry))}
205
+ end
206
+
207
+ Dir.entries(excerpts_path).each do |entry|
208
+ next if entry =~ /^\./
209
+ files << {:path => File.join(excerpts_path,entry), :name => File.join('excerpts/',File.basename(entry))}
210
+ end
211
+
212
+ Dir.entries(image_assets_path).each do |entry|
213
+ next if entry =~ /^\./
214
+ files << {:path => File.join(image_assets_path,entry), :name => File.join('images/',File.basename(entry))}
215
+ end
216
+
217
+ Dir.entries(file_assets_path).each do |entry|
218
+ next if entry =~ /^\./
219
+ files << {:path => File.join(file_assets_path,entry), :name => File.join('files/',File.basename(entry))}
220
+ end
221
+
222
+ files.uniq!
223
+
224
+ (tmp_dir + "#{name}.zip").tap do |file_name|
225
+ file_name.unlink if file_name.exist?
226
+ Zip::ZipFile.open(file_name, Zip::ZipFile::CREATE) do |zip|
227
+ files.each { |file| zip.add(file[:name], file[:path]) if File.exists?(file[:path]) }
228
+ File.open(tmp_dir + 'setup.yml', 'w') { |f| f.puts(export_setup.to_yaml) }
229
+ zip.add('setup.yml', tmp_dir + 'setup.yml')
230
+ end
231
+ end
232
+ end
233
+
234
+ class << self
235
+ def make_tmp_dir
236
+ random = Time.now.to_i.to_s.split('').sort_by { rand }
237
+ Pathname.new(Rails.root + "/tmp/website_export/tmp_#{random}/").tap do |dir|
238
+ FileUtils.mkdir_p(dir) unless dir.exist?
239
+ end
240
+ end
241
+
242
+ def import(file, current_user)
243
+ file_support = ErpTechSvcs::FileSupport::Base.new(:storage => ErpTechSvcs::FileSupport.options[:storage])
244
+ message = ''
245
+ success = true
246
+
247
+ file = ActionController::UploadedTempfile.new("uploaded-theme").tap do |f|
248
+ f.puts file.read
249
+ f.original_filename = file.original_filename
250
+ f.read # no idea why we need this here, otherwise the zip can't be opened
251
+ end unless file.path
252
+
253
+ entries = []
254
+ setup_hash = nil
255
+
256
+ Zip::ZipFile.open(file.path) do |zip|
257
+ zip.each do |entry|
258
+ next if entry.name =~ /__MACOSX\//
259
+ if entry.name =~ /setup.yml/
260
+ data = ''
261
+ entry.get_input_stream { |io| data = io.read }
262
+ data = StringIO.new(data) if data.present?
263
+ setup_hash = YAML.load(data)
264
+ else
265
+ type = entry.name.split('/')[(entry.name.split('/').count - 2)]
266
+ name = entry.name.split('/').last
267
+ next if name.nil?
268
+ data = ''
269
+ entry_hash = {:type => type, :name => name}
270
+ entries << entry_hash unless name == 'sections' || name == 'articles' || name == 'excerpts'
271
+ entries.uniq!
272
+ data = entry.get_input_stream.read
273
+ entry_hash[:data] = data
274
+ end
275
+ end
276
+ end
277
+
278
+ if Website.find_by_name(setup_hash[:name]).nil?
279
+ website = Website.create(
280
+ :name => setup_hash[:name],
281
+ :title => setup_hash[:title],
282
+ :subtitle => setup_hash[:subtitle],
283
+ :email => setup_hash[:email],
284
+ :email_inquiries => setup_hash[:email_inquiries],
285
+ :auto_activate_publication => setup_hash[:auto_activate_publication]
286
+ )
287
+
288
+ #set default publication published by user
289
+ first_publication = website.published_websites.first
290
+ first_publication.published_by = current_user
291
+ first_publication.save
292
+
293
+ begin
294
+
295
+ #handle images
296
+ setup_hash[:images].each do |image_asset|
297
+ content = entries.find{|entry| entry[:type] == 'images' and entry[:name] == image_asset[:name]}[:data]
298
+ website.add_file(content, File.join(file_support.root, image_asset[:path].sub(/site-[0-9][0-9]*/, "site-#{website.id}"), image_asset[:name]))
299
+ end
300
+
301
+ #handle files
302
+ setup_hash[:files].each do |file_asset|
303
+ content = entries.find{|entry| entry[:type] == 'files' and entry[:name] == file_asset[:name]}[:data]
304
+ website.add_file(content, File.join(file_support.root, file_asset[:path].sub(/site-[0-9][0-9]*/, "site-#{website.id}"), file_asset[:name]))
305
+ end
306
+
307
+ #handle hosts
308
+ setup_hash[:hosts].each do |host|
309
+ website.hosts << WebsiteHost.create(:host => host)
310
+ website.save
311
+ end
312
+
313
+ #handle sections
314
+ setup_hash[:sections].each do |section_hash|
315
+ build_section(section_hash, entries, website)
316
+ end
317
+ website.website_sections.update_paths!
318
+
319
+ #handle website_navs
320
+ setup_hash[:website_navs].each do |website_nav_hash|
321
+ website_nav = WebsiteNav.new(:name => website_nav_hash[:name])
322
+ website_nav_hash[:items].each do |item|
323
+ website_nav.website_nav_items << build_menu_item(item)
324
+ end
325
+ website.website_navs << website_nav
326
+ end
327
+
328
+ website.publish("Website Imported", current_user)
329
+
330
+ rescue Exception=>ex
331
+ website.destroy unless website.nil?
332
+ raise ex
333
+ end
334
+
335
+ website.save
336
+ success = true
337
+ else
338
+ message = 'Website already exists with that name'
339
+ success = false
340
+ end
341
+
342
+ return success, message
343
+ end
344
+
345
+ protected
346
+
347
+ def build_menu_item(hash)
348
+ website_item = WebsiteNavItem.new(
349
+ :title => hash[:title],
350
+ :url => hash[:url],
351
+ :position => hash[:position]
352
+ )
353
+ unless hash[:linked_to_item_type].blank?
354
+ website_item.linked_to_item = WebsiteSection.find_by_path(hash[:linked_to_item_path])
355
+ end
356
+ website_item.save
357
+ hash[:items].each do |item|
358
+ child_website_item = build_menu_item(item)
359
+ child_website_item.move_to_child_of(website_item)
360
+ end
361
+ unless hash[:roles].empty?
362
+ hash[:roles].each do |role|
363
+ website_item.add_role(Role.find_by_internal_identifier(role)) rescue nil #do nothing if the role does not exist
364
+ end
365
+ end
366
+ website_item
367
+ end
368
+
369
+ def build_section(hash, entries, website)
370
+ klass = hash[:type].constantize
371
+ section = klass.new(:title => hash[:name], :in_menu => hash[:in_menu], :position => hash[:position])
372
+ section.permalink = hash[:permalink]
373
+ section.path = hash[:path]
374
+ unless entries.find{|entry| entry[:type] == 'sections' and entry[:name] == "#{hash[:name]}.rhtml"}.nil?
375
+ section.layout = entries.find{|entry| entry[:type] == 'sections' and entry[:name] == "#{hash[:name]}.rhtml"}[:data]
376
+ end
377
+ hash[:articles].each do |article_hash|
378
+ article = Article.find_by_title(article_hash[:name])
379
+ if article.nil?
380
+ article = Article.new(:title => article_hash[:name], :display_title => article_hash[:display_title])
381
+ article.tag_list = article_hash[:tag_list].split(',').collect{|t| t.strip() } unless article_hash[:tag_list].blank?
382
+ article.body_html = entries.find{|entry| entry[:type] == 'articles' and entry[:name] == "#{article_hash[:name]}.html"}[:data]
383
+ unless entries.find{|entry| entry[:type] == 'excerpts' and entry[:name] == "#{article_hash[:name]}.html"}.nil?
384
+ article.excerpt_html = entries.find{|entry| entry[:type] == 'excerpts' and entry[:name] == "#{article_hash[:name]}.html"}[:data]
385
+ end
386
+ end
387
+ section.contents << article
388
+ section.save
389
+ article.update_content_area_and_position_by_section(section, article_hash[:content_area], article_hash[:position])
390
+ end
391
+ website.website_sections << section
392
+ section.save
393
+ hash[:sections].each do |section_hash|
394
+ child_section = build_section(section_hash, entries, website)
395
+ child_section.move_to_child_of(section)
396
+ end
397
+ unless hash[:roles].empty?
398
+ hash[:roles].each do |role|
399
+ section.add_role(Role.find_by_internal_identifier(role)) rescue nil #do nothing if the role does not exist
400
+ end
401
+ end
402
+ section
403
+ end
404
+
405
+ end
406
+
407
+ private
408
+
409
+ def website_role_iid
410
+ "website_#{self.name.underscore.gsub("'","").gsub(",","")}_access"
411
+ end
412
+
413
+ def build_section_hash(section)
414
+ section_hash = {
415
+ :name => section.title,
416
+ :has_layout => !section.layout.blank?,
417
+ :type => section.class.to_s,
418
+ :in_menu => section.in_menu,
419
+ :articles => [],
420
+ :roles => section.roles.collect{|role| role.internal_identifier},
421
+ :path => section.path,
422
+ :permalink => section.permalink,
423
+ :internal_identifier => section.internal_identifier,
424
+ :position => section.position,
425
+ :sections => section.children.each.map{|child| build_section_hash(child)}
426
+ }
427
+
428
+ section.contents.each do |content|
429
+ content_area = content.content_area_by_website_section(section)
430
+ position = content.position_by_website_section(section)
431
+ section_hash[:articles] << {
432
+ :name => content.title,
433
+ :tag_list => content.tag_list.join(', '),
434
+ :content_area => content_area,
435
+ :position => position,
436
+ :display_title => content.display_title,
437
+ :internal_identifier => content.internal_identifier
438
+ }
439
+ end
440
+
441
+ section_hash
442
+ end
443
+
444
+
445
+ def build_menu_item_hash(menu_item)
446
+ {
447
+ :title => menu_item.title,
448
+ :url => menu_item.url,
449
+ :roles => menu_item.roles.collect{|role| role.internal_identifier},
450
+ :linked_to_item_type => menu_item.linked_to_item_type,
451
+ :linked_to_item_path => menu_item.linked_to_item.nil? ? nil : menu_item.linked_to_item.path,
452
+ :position => menu_item.position,
453
+ :items => menu_item.children.collect{|child| build_menu_item_hash(child)}
454
+ }
455
+ end
456
+
457
+ end