virgo 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (309) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +37 -0
  4. data/app/assets/javascripts/tinymce/manifest.coffee +1 -0
  5. data/app/assets/javascripts/tinymce/plugins/image_manager/plugin.coffee +234 -0
  6. data/app/assets/javascripts/virgo/admin/page_modules/edit_form.coffee +4 -0
  7. data/app/assets/javascripts/virgo/admin/page_modules/edit_subject_form.coffee +29 -0
  8. data/app/assets/javascripts/virgo/admin/posts/edit_locking.coffee +35 -0
  9. data/app/assets/javascripts/virgo/admin/posts/form.coffee +183 -0
  10. data/app/assets/javascripts/virgo/admin/slides/form.coffee +27 -0
  11. data/app/assets/javascripts/virgo/admin/slideshows/form.coffee +26 -0
  12. data/app/assets/javascripts/virgo/admin_only.coffee +1 -0
  13. data/app/assets/javascripts/virgo/application.coffee +51 -0
  14. data/app/assets/javascripts/virgo/common.coffee +83 -0
  15. data/app/assets/javascripts/virgo/common/character_counter.coffee +27 -0
  16. data/app/assets/javascripts/virgo/components/header.coffee +47 -0
  17. data/app/assets/javascripts/virgo/components/media_modal.coffee +151 -0
  18. data/app/assets/javascripts/virgo/components/share_bar.coffee +53 -0
  19. data/app/assets/javascripts/virgo/components/slideshow.coffee +35 -0
  20. data/app/assets/javascripts/virgo/lib/jquery-ui-multiselect.js +342 -0
  21. data/app/assets/javascripts/virgo/lib/jquery.browser.js +184 -0
  22. data/app/assets/javascripts/virgo/lib/jquery.cookie.js +114 -0
  23. data/app/assets/javascripts/virgo/lib/jquery.pjax.js +932 -0
  24. data/app/assets/javascripts/virgo/lib/jquery_ext.coffee +0 -0
  25. data/app/assets/javascripts/virgo/lib/manifest.coffee +5 -0
  26. data/app/assets/javascripts/virgo/lib/ouibounce.js +194 -0
  27. data/app/assets/javascripts/virgo/page_modules.coffee +175 -0
  28. data/app/assets/javascripts/virgo/posts/show.coffee +32 -0
  29. data/app/assets/stylesheets/tinymce/skin.sass +2 -0
  30. data/app/assets/stylesheets/tinymce/skins/railspress/content.min.sass +82 -0
  31. data/app/assets/stylesheets/tinymce/skins/railspress/fonts/tinymce-small.eot +0 -0
  32. data/app/assets/stylesheets/tinymce/skins/railspress/fonts/tinymce-small.svg +62 -0
  33. data/app/assets/stylesheets/tinymce/skins/railspress/fonts/tinymce-small.ttf +0 -0
  34. data/app/assets/stylesheets/tinymce/skins/railspress/fonts/tinymce-small.woff +0 -0
  35. data/app/assets/stylesheets/tinymce/skins/railspress/fonts/tinymce.eot +0 -0
  36. data/app/assets/stylesheets/tinymce/skins/railspress/fonts/tinymce.svg +63 -0
  37. data/app/assets/stylesheets/tinymce/skins/railspress/fonts/tinymce.ttf +0 -0
  38. data/app/assets/stylesheets/tinymce/skins/railspress/fonts/tinymce.woff +0 -0
  39. data/app/assets/stylesheets/tinymce/skins/railspress/img/anchor.gif +0 -0
  40. data/app/assets/stylesheets/tinymce/skins/railspress/img/loader.gif +0 -0
  41. data/app/assets/stylesheets/tinymce/skins/railspress/img/object.gif +0 -0
  42. data/app/assets/stylesheets/tinymce/skins/railspress/img/trans.gif +0 -0
  43. data/app/assets/stylesheets/tinymce/skins/railspress/skin.min.sass +1703 -0
  44. data/app/assets/stylesheets/virgo/_common.sass +65 -0
  45. data/app/assets/stylesheets/virgo/admin.sass +301 -0
  46. data/app/assets/stylesheets/virgo/admin/columns.sass +3 -0
  47. data/app/assets/stylesheets/virgo/admin/images.sass +14 -0
  48. data/app/assets/stylesheets/virgo/admin/media_modal.sass +72 -0
  49. data/app/assets/stylesheets/virgo/admin/post.sass +102 -0
  50. data/app/assets/stylesheets/virgo/admin/slideshow.sass +35 -0
  51. data/app/assets/stylesheets/virgo/admin/users.sass +2 -0
  52. data/app/assets/stylesheets/virgo/admin_only.sass +1 -0
  53. data/app/assets/stylesheets/virgo/application.sass +605 -0
  54. data/app/assets/stylesheets/virgo/columns.sass +51 -0
  55. data/app/assets/stylesheets/virgo/install.sass +15 -0
  56. data/app/assets/stylesheets/virgo/lib/bootstrap/bootstrap-imports.sass +9 -0
  57. data/app/assets/stylesheets/virgo/lib/bootstrap/bootstrap-overrides.sass +51 -0
  58. data/app/assets/stylesheets/virgo/lib/bootstrap/bootstrap-variables.sass +27 -0
  59. data/app/assets/stylesheets/virgo/lib/jquery-ui-multiselect.css +30 -0
  60. data/app/assets/stylesheets/virgo/lib/manifest.sass +3 -0
  61. data/app/assets/stylesheets/virgo/lib/ouibounce.css +222 -0
  62. data/app/assets/stylesheets/virgo/lib/select2-imports.sass +8 -0
  63. data/app/assets/stylesheets/virgo/page_modules.sass +54 -0
  64. data/app/assets/stylesheets/virgo/pages/authors.sass +30 -0
  65. data/app/assets/stylesheets/virgo/posts.sass +782 -0
  66. data/app/assets/stylesheets/virgo/search.sass +36 -0
  67. data/app/assets/stylesheets/virgo/ui.sass +434 -0
  68. data/app/assets/stylesheets/virgo/users.sass +9 -0
  69. data/app/controllers/virgo/admin/base_controller.rb +22 -0
  70. data/app/controllers/virgo/admin/categories_controller.rb +90 -0
  71. data/app/controllers/virgo/admin/columns_controller.rb +65 -0
  72. data/app/controllers/virgo/admin/images_controller.rb +89 -0
  73. data/app/controllers/virgo/admin/media_modal_controller.rb +101 -0
  74. data/app/controllers/virgo/admin/newsletters_controller.rb +37 -0
  75. data/app/controllers/virgo/admin/page_modules_controller.rb +62 -0
  76. data/app/controllers/virgo/admin/pages_controller.rb +6 -0
  77. data/app/controllers/virgo/admin/posts_controller.rb +173 -0
  78. data/app/controllers/virgo/admin/sites_controller.rb +31 -0
  79. data/app/controllers/virgo/admin/slides_controller.rb +71 -0
  80. data/app/controllers/virgo/admin/slideshows_controller.rb +66 -0
  81. data/app/controllers/virgo/admin/tags_controller.rb +98 -0
  82. data/app/controllers/virgo/admin/users/sessions_controller.rb +21 -0
  83. data/app/controllers/virgo/admin/users_controller.rb +84 -0
  84. data/app/controllers/virgo/application_controller.rb +151 -0
  85. data/app/controllers/virgo/categories_controller.rb +20 -0
  86. data/app/controllers/virgo/columns_controller.rb +24 -0
  87. data/app/controllers/virgo/images_controller.rb +9 -0
  88. data/app/controllers/virgo/install_controller.rb +46 -0
  89. data/app/controllers/virgo/page_modules_controller.rb +23 -0
  90. data/app/controllers/virgo/pages_controller.rb +29 -0
  91. data/app/controllers/virgo/posts_controller.rb +101 -0
  92. data/app/controllers/virgo/search_controller.rb +35 -0
  93. data/app/controllers/virgo/subscribers_controller.rb +54 -0
  94. data/app/controllers/virgo/tags_controller.rb +15 -0
  95. data/app/controllers/virgo/users/confirmations_controller.rb +18 -0
  96. data/app/controllers/virgo/users/passwords_controller.rb +31 -0
  97. data/app/controllers/virgo/users/registrations_controller.rb +41 -0
  98. data/app/controllers/virgo/users/sessions_controller.rb +32 -0
  99. data/app/controllers/virgo/users_controller.rb +15 -0
  100. data/app/helpers/virgo/admin/post_helper.rb +54 -0
  101. data/app/helpers/virgo/admin_helper.rb +15 -0
  102. data/app/helpers/virgo/application_helper.rb +124 -0
  103. data/app/helpers/virgo/hooks_helper.rb +30 -0
  104. data/app/helpers/virgo/page_modules_helper.rb +4 -0
  105. data/app/helpers/virgo/post_helper.rb +159 -0
  106. data/app/helpers/virgo/render_helper.rb +33 -0
  107. data/app/helpers/virgo/search_helper.rb +49 -0
  108. data/app/helpers/virgo/shortcode_helper.rb +7 -0
  109. data/app/helpers/virgo/text_helper.rb +12 -0
  110. data/app/helpers/virgo/video_helper.rb +34 -0
  111. data/app/models/concerns/virgo/common/slug_history.rb +36 -0
  112. data/app/models/concerns/virgo/common/uuid.rb +22 -0
  113. data/app/models/concerns/virgo/post/recommendations.rb +15 -0
  114. data/app/models/concerns/virgo/post/search.rb +100 -0
  115. data/app/models/concerns/virgo/user/search.rb +39 -0
  116. data/app/models/virgo/ability.rb +59 -0
  117. data/app/models/virgo/category.rb +44 -0
  118. data/app/models/virgo/column.rb +25 -0
  119. data/app/models/virgo/form_model.rb +19 -0
  120. data/app/models/virgo/image.rb +63 -0
  121. data/app/models/virgo/page_module.rb +42 -0
  122. data/app/models/virgo/page_module_post.rb +8 -0
  123. data/app/models/virgo/post.rb +391 -0
  124. data/app/models/virgo/post_category.rb +8 -0
  125. data/app/models/virgo/post_search.rb +7 -0
  126. data/app/models/virgo/post_tag.rb +10 -0
  127. data/app/models/virgo/site.rb +36 -0
  128. data/app/models/virgo/slide.rb +23 -0
  129. data/app/models/virgo/slideshow.rb +37 -0
  130. data/app/models/virgo/slug_history.rb +5 -0
  131. data/app/models/virgo/subscriber.rb +5 -0
  132. data/app/models/virgo/tag.rb +34 -0
  133. data/app/models/virgo/user.rb +145 -0
  134. data/app/uploaders/virgo/application_uploader.rb +11 -0
  135. data/app/uploaders/virgo/avatar_uploader.rb +21 -0
  136. data/app/uploaders/virgo/featured_image_uploader.rb +28 -0
  137. data/app/uploaders/virgo/image_uploader.rb +40 -0
  138. data/app/uploaders/virgo/slide_uploader.rb +19 -0
  139. data/app/views/layouts/virgo/admin.haml +16 -0
  140. data/app/views/layouts/virgo/admin/site_settings.haml +14 -0
  141. data/app/views/layouts/virgo/application.haml +63 -0
  142. data/app/views/layouts/virgo/errors.haml +42 -0
  143. data/app/views/layouts/virgo/main.haml +5 -0
  144. data/app/views/layouts/virgo/posts.haml +10 -0
  145. data/app/views/virgo/admin/categories/_form.haml +19 -0
  146. data/app/views/virgo/admin/categories/edit.haml +7 -0
  147. data/app/views/virgo/admin/categories/index.haml +38 -0
  148. data/app/views/virgo/admin/categories/modal_form.haml +11 -0
  149. data/app/views/virgo/admin/categories/new.haml +7 -0
  150. data/app/views/virgo/admin/categories/success_modal.haml +14 -0
  151. data/app/views/virgo/admin/columns/_form.haml +30 -0
  152. data/app/views/virgo/admin/columns/edit.haml +1 -0
  153. data/app/views/virgo/admin/columns/index.haml +30 -0
  154. data/app/views/virgo/admin/columns/new.haml +1 -0
  155. data/app/views/virgo/admin/images/_form.haml +46 -0
  156. data/app/views/virgo/admin/images/edit.haml +1 -0
  157. data/app/views/virgo/admin/images/index.haml +32 -0
  158. data/app/views/virgo/admin/images/new.haml +1 -0
  159. data/app/views/virgo/admin/media_modal/_image_settings.haml +17 -0
  160. data/app/views/virgo/admin/media_modal/_library_panel.haml +26 -0
  161. data/app/views/virgo/admin/media_modal/_upload_panel.haml +21 -0
  162. data/app/views/virgo/admin/media_modal/_upload_success.haml +7 -0
  163. data/app/views/virgo/admin/media_modal/index.haml +19 -0
  164. data/app/views/virgo/admin/newsletters/changelog.haml +25 -0
  165. data/app/views/virgo/admin/newsletters/edit.haml +6 -0
  166. data/app/views/virgo/admin/page_modules/edit.haml +33 -0
  167. data/app/views/virgo/admin/page_modules/edit_subject.haml +43 -0
  168. data/app/views/virgo/admin/pages/help.haml +114 -0
  169. data/app/views/virgo/admin/posts/_author_dropdown.haml +1 -0
  170. data/app/views/virgo/admin/posts/_categories_form.haml +7 -0
  171. data/app/views/virgo/admin/posts/_category.haml +22 -0
  172. data/app/views/virgo/admin/posts/_form.haml +201 -0
  173. data/app/views/virgo/admin/posts/_revisions.haml +40 -0
  174. data/app/views/virgo/admin/posts/edit.haml +9 -0
  175. data/app/views/virgo/admin/posts/index.haml +59 -0
  176. data/app/views/virgo/admin/posts/new.haml +1 -0
  177. data/app/views/virgo/admin/posts/revision_detail.haml +28 -0
  178. data/app/views/virgo/admin/posts/revisions.haml +3 -0
  179. data/app/views/virgo/admin/sites/edit.haml +12 -0
  180. data/app/views/virgo/admin/slides/_form.haml +41 -0
  181. data/app/views/virgo/admin/slides/edit.haml +1 -0
  182. data/app/views/virgo/admin/slides/new.haml +1 -0
  183. data/app/views/virgo/admin/slideshows/_form.haml +49 -0
  184. data/app/views/virgo/admin/slideshows/edit.haml +1 -0
  185. data/app/views/virgo/admin/slideshows/index.haml +43 -0
  186. data/app/views/virgo/admin/slideshows/new.haml +1 -0
  187. data/app/views/virgo/admin/tags/_form.haml +16 -0
  188. data/app/views/virgo/admin/tags/edit.haml +7 -0
  189. data/app/views/virgo/admin/tags/index.haml +33 -0
  190. data/app/views/virgo/admin/tags/modal_form.haml +12 -0
  191. data/app/views/virgo/admin/tags/new.haml +7 -0
  192. data/app/views/virgo/admin/tags/success_modal.haml +14 -0
  193. data/app/views/virgo/admin/users/_form.haml +62 -0
  194. data/app/views/virgo/admin/users/edit.haml +1 -0
  195. data/app/views/virgo/admin/users/index.haml +34 -0
  196. data/app/views/virgo/admin/users/new.haml +1 -0
  197. data/app/views/virgo/application/sitemap.xml.builder +0 -0
  198. data/app/views/virgo/authors/_profile_long.haml +13 -0
  199. data/app/views/virgo/authors/_profile_short.haml +10 -0
  200. data/app/views/virgo/categories/show.haml +24 -0
  201. data/app/views/virgo/columns/index.haml +19 -0
  202. data/app/views/virgo/columns/show.haml +18 -0
  203. data/app/views/virgo/common/_admin_navbar.haml +100 -0
  204. data/app/views/virgo/common/_alerts.haml +40 -0
  205. data/app/views/virgo/common/_analytics.html.erb +1 -0
  206. data/app/views/virgo/common/_base_errors.haml +8 -0
  207. data/app/views/virgo/common/_category_heading.haml +26 -0
  208. data/app/views/virgo/common/_disqus.html.erb +24 -0
  209. data/app/views/virgo/common/_disqus_comment_count.html.erb +12 -0
  210. data/app/views/virgo/common/_fb_api.html.erb +30 -0
  211. data/app/views/virgo/common/_fonts.html.erb +1 -0
  212. data/app/views/virgo/common/_footer.haml +13 -0
  213. data/app/views/virgo/common/_footer_javascript.html.erb +0 -0
  214. data/app/views/virgo/common/_header_analytics.html.erb +9 -0
  215. data/app/views/virgo/common/_list_signup.haml +20 -0
  216. data/app/views/virgo/common/_ouibounce_modal.haml +26 -0
  217. data/app/views/virgo/common/_public_nav.haml +58 -0
  218. data/app/views/virgo/common/_search_form.haml +8 -0
  219. data/app/views/virgo/common/_share_bar.haml +45 -0
  220. data/app/views/virgo/common/_user_vars.haml +2 -0
  221. data/app/views/virgo/errors/404.haml +10 -0
  222. data/app/views/virgo/errors/500.haml +10 -0
  223. data/app/views/virgo/images/_shortcode.haml +4 -0
  224. data/app/views/virgo/install/index.haml +58 -0
  225. data/app/views/virgo/install/success.haml +12 -0
  226. data/app/views/virgo/page_modules/_latest_posts.haml +29 -0
  227. data/app/views/virgo/page_modules/_popular_posts.haml +51 -0
  228. data/app/views/virgo/pages/authors.haml +8 -0
  229. data/app/views/virgo/pages/home.haml +0 -0
  230. data/app/views/virgo/pages/show.haml +6 -0
  231. data/app/views/virgo/posts/_author_info.haml +16 -0
  232. data/app/views/virgo/posts/_post_full.haml +63 -0
  233. data/app/views/virgo/posts/_post_short.haml +30 -0
  234. data/app/views/virgo/posts/_post_thumb_box.haml +13 -0
  235. data/app/views/virgo/posts/_recommendations.haml +12 -0
  236. data/app/views/virgo/posts/_sidebar_thumb.haml +4 -0
  237. data/app/views/virgo/posts/index.haml +22 -0
  238. data/app/views/virgo/posts/latest.haml +16 -0
  239. data/app/views/virgo/posts/more.haml +10 -0
  240. data/app/views/virgo/posts/rss.xml.builder +17 -0
  241. data/app/views/virgo/posts/show.haml +17 -0
  242. data/app/views/virgo/search/_form.haml +5 -0
  243. data/app/views/virgo/search/_item.haml +22 -0
  244. data/app/views/virgo/search/index.haml +11 -0
  245. data/app/views/virgo/search/new.haml +4 -0
  246. data/app/views/virgo/shortcode_templates/blockquote.haml +9 -0
  247. data/app/views/virgo/shortcode_templates/h1.haml +1 -0
  248. data/app/views/virgo/shortcode_templates/h2.haml +1 -0
  249. data/app/views/virgo/shortcode_templates/h3.haml +1 -0
  250. data/app/views/virgo/shortcode_templates/h4.haml +1 -0
  251. data/app/views/virgo/shortcode_templates/image.haml +17 -0
  252. data/app/views/virgo/shortcode_templates/pullquote.haml +3 -0
  253. data/app/views/virgo/shortcode_templates/slideshow.haml +35 -0
  254. data/app/views/virgo/shortcode_templates/tweet.haml +3 -0
  255. data/app/views/virgo/shortcode_templates/video.haml +8 -0
  256. data/app/views/virgo/subscribers/_success_modal.haml +13 -0
  257. data/app/views/virgo/subscribers/modal.haml +15 -0
  258. data/app/views/virgo/tags/show.haml +16 -0
  259. data/app/views/virgo/users/confirmations/new.html.haml +10 -0
  260. data/app/views/virgo/users/devise_mailer/confirmation_instructions.html.erb +7 -0
  261. data/app/views/virgo/users/devise_mailer/reset_password_instructions.html.haml +6 -0
  262. data/app/views/virgo/users/devise_mailer/unlock_instructions.html.haml +5 -0
  263. data/app/views/virgo/users/passwords/edit.html.haml +14 -0
  264. data/app/views/virgo/users/passwords/new.html.haml +14 -0
  265. data/app/views/virgo/users/registrations/edit.html.haml +30 -0
  266. data/app/views/virgo/users/registrations/new.html.haml +13 -0
  267. data/app/views/virgo/users/sessions/new.html.haml +12 -0
  268. data/app/views/virgo/users/shared/_links.haml +26 -0
  269. data/app/views/virgo/users/show.haml +11 -0
  270. data/app/views/virgo/users/unlocks/new.html.haml +9 -0
  271. data/config/initializers/action_mailer.rb +7 -0
  272. data/config/initializers/active_record.rb +72 -0
  273. data/config/initializers/active_record_symbolize.rb +18 -0
  274. data/config/initializers/assets.rb +17 -0
  275. data/config/initializers/backtrace_silencers.rb +7 -0
  276. data/config/initializers/carrierwave.rb +36 -0
  277. data/config/initializers/cookies_serializer.rb +3 -0
  278. data/config/initializers/devise.rb +257 -0
  279. data/config/initializers/filter_parameter_logging.rb +4 -0
  280. data/config/initializers/hash.rb +5 -0
  281. data/config/initializers/inflections.rb +16 -0
  282. data/config/initializers/mime_types.rb +4 -0
  283. data/config/initializers/nil_class.rb +13 -0
  284. data/config/initializers/non_digest_assets.rb +3 -0
  285. data/config/initializers/paper_trail.rb +30 -0
  286. data/config/initializers/schema_plus.rb +5 -0
  287. data/config/initializers/session_store.rb +3 -0
  288. data/config/initializers/shortcode.rb +12 -0
  289. data/config/initializers/simple_form.rb +170 -0
  290. data/config/initializers/simple_form_bootstrap.rb +136 -0
  291. data/config/initializers/string.rb +5 -0
  292. data/config/initializers/time_formatting.rb +23 -0
  293. data/config/initializers/wrap_parameters.rb +14 -0
  294. data/config/locales/devise.en.yml +60 -0
  295. data/config/routes.rb +96 -0
  296. data/config/schedule.rb +3 -0
  297. data/db/migrate/20150729174510_create_virgo_schema.rb +225 -0
  298. data/lib/generators/virgo/install/install_generator.rb +16 -0
  299. data/lib/generators/virgo/views/views_generator.rb +22 -0
  300. data/lib/tasks/virgo_tasks.rake +7 -0
  301. data/lib/virgo.rb +4 -0
  302. data/lib/virgo/development_mail_interceptor.rb +6 -0
  303. data/lib/virgo/email_validator.rb +6 -0
  304. data/lib/virgo/engine.rb +73 -0
  305. data/lib/virgo/reduce_validator.rb +6 -0
  306. data/lib/virgo/upload_helpers.rb +89 -0
  307. data/lib/virgo/version.rb +3 -0
  308. data/lib/virgo/view_help.rb +3 -0
  309. metadata +1051 -0
@@ -0,0 +1,39 @@
1
+ module Virgo
2
+ class User < ActiveRecord::Base
3
+ module Search
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ scope :search, ->(filters=nil) {
8
+ filters ||= {}
9
+
10
+ items = all
11
+
12
+ if filters[:term].present?
13
+ parts = filters[:term].split(" ")
14
+ queries = []
15
+ vals = {}
16
+
17
+ parts.each_with_index do |part, i|
18
+ vals[:"term_#{i}"] = "%#{part}%"
19
+
20
+ queries << \
21
+ "(users.email ILIKE :term_#{i} OR " +
22
+ "users.first_name ILIKE :term_#{i} OR " +
23
+ "users.last_name ILIKE :term_#{i} OR " +
24
+ "users.username ILIKE :term_#{i} OR " +
25
+ "users.byline ILIKE :term_#{i})"
26
+ end
27
+
28
+ query = queries.join(" OR ")
29
+
30
+ items = items.where(query, vals)
31
+ end
32
+
33
+
34
+ items
35
+ }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,59 @@
1
+ # note: role inheritence implemented via Ryan Bates' method
2
+ # outlined in https://github.com/ryanb/cancan/wiki/Role-Based-Authorization
3
+ module Virgo
4
+ class Ability
5
+ include CanCan::Ability
6
+
7
+ def initialize(user)
8
+ @user = user || User.new # for guest
9
+
10
+ send(@user.role) if @user.role.present?
11
+ end
12
+
13
+ def anonymous
14
+ can :read, Post do |post|
15
+ post.published? && post.live
16
+ end
17
+ end
18
+
19
+ def normal
20
+ anonymous
21
+ end
22
+
23
+ def editor
24
+ can :manage, :all
25
+ can :manage, Virgo::User do |u|
26
+ u == @user
27
+ end
28
+ cannot :index, Virgo::User
29
+ end
30
+
31
+ def contributor
32
+ can :manage, Virgo::Post do |post|
33
+ post.author == @user
34
+ end
35
+
36
+ can :manage, Virgo::Slideshow do |slideshow|
37
+ slideshow.author == @user
38
+ end
39
+
40
+ can :manage, Virgo::Slide do |slide|
41
+ can?(:manage, slide.slideshow)
42
+ end
43
+
44
+ can :read, Virgo::Post
45
+
46
+ cannot :manage, Virgo::User
47
+ cannot :manage, Virgo::Column
48
+ cannot :manage, Virgo::Site
49
+ end
50
+
51
+ def admin
52
+ can :manage, :all
53
+ end
54
+
55
+ def superuser
56
+ can :manage, :all
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,44 @@
1
+ module Virgo
2
+ class Category < ActiveRecord::Base
3
+ enable_redis_tracking
4
+
5
+ extend FriendlyId
6
+
7
+ friendly_id :name, use: :slugged
8
+
9
+ has_many :post_categories, dependent: :destroy
10
+ has_many :posts, ->{ where(post_type: :post) }, through: :post_categories
11
+
12
+ belongs_to :parent, class_name: 'Virgo::Category'
13
+
14
+ belongs_to :created_by, class_name: 'Virgo::User'
15
+
16
+ has_many :children, class_name: 'Virgo::Category', foreign_key: :parent_id
17
+
18
+ validates :name, presence: true, uniqueness: true
19
+
20
+ scope :visible, ->{ where(visible: true) }
21
+
22
+ scope :by_weight, ->{ order(navbar_weight: :asc) }
23
+
24
+ scope :navbar, ->{ where(display_in_navbar: true).by_weight }
25
+
26
+ scope :bottom_level, ->{
27
+ where("virgo_categories.id NOT IN (SELECT DISTINCT virgo_categories.parent_id FROM virgo_categories WHERE virgo_categories.parent_id IS NOT NULL)")
28
+ }
29
+
30
+ scope :top_level, ->{
31
+ where(parent_id: nil)
32
+ }
33
+
34
+ scope :with_post_count, ->{
35
+ select("virgo_categories.*, " +
36
+ "(SELECT COUNT(*) FROM virgo_post_categories WHERE virgo_post_categories.category_id = virgo_categories.id) AS post_count"
37
+ )
38
+ }
39
+
40
+ def self.COLUMNS
41
+ find_by(name: "Columns")
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,25 @@
1
+ module Virgo
2
+ class Column < ActiveRecord::Base
3
+ enable_redis_tracking
4
+
5
+ extend FriendlyId
6
+
7
+ friendly_id :name, use: :slugged
8
+
9
+ mount_uploader :image, Virgo::FeaturedImageUploader
10
+
11
+ validates :name, presence: true
12
+
13
+ has_many :posts
14
+
15
+ after_save :expire_site_key
16
+
17
+ scope :with_post_count, ->{
18
+ select("virgo_columns.*, " +
19
+ "(SELECT COUNT(*) FROM virgo_posts WHERE virgo_posts.column_id = virgo_columns.id) AS post_count"
20
+ )
21
+ }
22
+
23
+ scope :by_weight, ->{ order(weight: :asc) }
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ module Virgo
2
+ class FormModel
3
+ include ActiveModel::Validations
4
+ include ActiveModel::Conversion
5
+ extend ActiveModel::Naming
6
+
7
+ def initialize(attributes = {})
8
+ if attributes
9
+ attributes.each do |name, value|
10
+ send("#{name}=", value)
11
+ end
12
+ end
13
+ end
14
+
15
+ def persisted?
16
+ false
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,63 @@
1
+ module Virgo
2
+ class Image < ActiveRecord::Base
3
+ extend FriendlyId
4
+
5
+ friendly_id :name, use: :slugged
6
+
7
+ mount_uploader :image, Virgo::ImageUploader
8
+
9
+ validates :name, presence: true
10
+ validates :image, presence: true
11
+
12
+ belongs_to :user
13
+
14
+ before_validation :derive_name
15
+
16
+ scope :search, ->(filters={}){
17
+ items = all
18
+
19
+ if filters[:term].present?
20
+ items = items.where("images.name ILIKE :term", term: "%#{filters[:term]}%")
21
+ end
22
+
23
+ items
24
+ }
25
+
26
+ def url(arg)
27
+ image.url
28
+ end
29
+
30
+ def redactor_json
31
+ {
32
+ thumb: image.url(:thumb),
33
+ image: image.url
34
+ }
35
+ end
36
+
37
+ def as_json(opts={})
38
+ {
39
+ id: id,
40
+ name: name,
41
+ alt_text: alt_text,
42
+ credit: credit,
43
+ description: description,
44
+ slug: slug,
45
+ image_url: image.try(:url),
46
+ small_thumb_image: image.try(:url, :small_thumb),
47
+ thumb_image: image.try(:url, :thumb),
48
+ med_thumb_image: image.try(:url, :med_thumb),
49
+ content_image: image.try(:url, :content),
50
+ featured_image: image.try(:url, :featured),
51
+ wide_image: image.try(:url, :wide)
52
+ }
53
+ end
54
+
55
+ private
56
+
57
+ def derive_name
58
+ if defined?(image_changed?) && image_changed? && !name_changed? && name.blank?
59
+ self.name = image.file.try(:filename)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,42 @@
1
+ module Virgo
2
+ class PageModule < ActiveRecord::Base
3
+ mount_uploader :image, Virgo::ImageUploader
4
+
5
+ attr_accessor :subject_headline # form hook
6
+
7
+ before_save :derive_template_path
8
+
9
+ validates :name, presence: true
10
+
11
+ belongs_to :subject, polymorphic: true
12
+
13
+ has_many :page_module_posts
14
+ has_many :posts, through: :page_module_posts
15
+
16
+ scope :enabled, ->{ where(enabled: true) }
17
+
18
+ scope :visible_to_admins, ->{ where(hidden_from_admins: false) }
19
+
20
+ after_save :expire_site_key
21
+
22
+ def self.POPULAR
23
+ @_popular ||= find_by(name: 'popular posts')
24
+ end
25
+
26
+ def admin_image_path
27
+ "virgo/admin/page_modules/#{name.downcase.gsub(' ', '_').gsub('-', '_')}.png"
28
+ end
29
+
30
+ def subject_headline
31
+ subject.try :headline
32
+ end
33
+
34
+ private
35
+
36
+ def derive_template_path(opts={})
37
+ if name_changed? || opts[:force]
38
+ self.template_path = "/virgo/page_modules/#{name.downcase.gsub(' ', '_').gsub('-', '_')}"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,8 @@
1
+ module Virgo
2
+ class PageModulePost < ActiveRecord::Base
3
+ belongs_to :page_module, touch: true
4
+ belongs_to :post
5
+
6
+ scope :by_position, ->{ order(position: :asc) }
7
+ end
8
+ end
@@ -0,0 +1,391 @@
1
+ module Virgo
2
+ class Post < ActiveRecord::Base
3
+ paginates_per 10
4
+
5
+ enable_redis_tracking
6
+
7
+ has_paper_trail ignore: [:updated_at, :editing_user_id, :editing_timestamp, :live, :view_count, :meta, :popularity]
8
+
9
+ include Post::Search, Post::Recommendations, ActionView::Helpers::SanitizeHelper, Common::Uuid, Common::SlugHistory
10
+
11
+ extend FriendlyId
12
+
13
+ attr_accessor :ordered_tag_ids
14
+
15
+ friendly_id :slug_candidates, use: :slugged
16
+
17
+ belongs_to :author, class_name: 'Virgo::User'
18
+
19
+ belongs_to :featured_image, class_name: 'Virgo::Image'
20
+ belongs_to :thumbnail_image, class_name: 'Virgo::Image'
21
+
22
+ belongs_to :created_by, class_name: 'Virgo::User'
23
+
24
+ belongs_to :editing_user, class_name: 'Virgo::User'
25
+
26
+ validates :author_id, presence: true
27
+
28
+ has_many :post_tags, dependent: :destroy
29
+ has_many :tags, through: :post_tags
30
+
31
+ has_many :post_categories, dependent: :destroy
32
+ has_many :categories, through: :post_categories
33
+
34
+ belongs_to :column, touch: true
35
+
36
+ validates :headline, presence: true, unless: :page?
37
+ validates :body, presence: true
38
+
39
+ validates :slug, presence: true, uniqueness: true
40
+
41
+ symbolize :status
42
+ symbolize :post_type
43
+
44
+ scope :with_relations, ->{ joins(:author).preload(:author, :tags, :categories) }
45
+
46
+ scope :publicly_viewable, ->{ where(status: :published, live: true).where.not(publish_at: nil) }
47
+ scope :published, ->{ where(status: :published) }
48
+ scope :draft, ->{ where(status: :draft) }
49
+ scope :pages, ->{ where(post_type: :page) }
50
+ scope :posts, ->{ where(post_type: :post) }
51
+ scope :for_month, ->(month){ where("posts.publish_at >= :month_start AND posts.publish_at <= :month_end",
52
+ month_start: month.beginning_of_month,
53
+ month_end: month.end_of_month) }
54
+
55
+ scope :for_weekly_email, ->{
56
+ publicly_viewable.where(include_in_weekly_email: true)
57
+ }
58
+
59
+ scope :exclude_front_page_feature, ->{
60
+ items = posts.publicly_viewable
61
+
62
+ if Post.front_page_feature
63
+ items = items.where.not(id: Post.front_page_feature.id)
64
+ end
65
+
66
+ items
67
+ }
68
+
69
+ scope :exclude_category_feature, ->(category){
70
+ items = posts.publicly_viewable
71
+
72
+ if Post.category_feature(category)
73
+ items = items.where.not(id: Post.category_feature(category).id)
74
+ end
75
+
76
+ items
77
+ }
78
+
79
+ scope :exclude, ->(item) {
80
+ if item.nil?
81
+ all
82
+ else
83
+ where.not(id: item.id)
84
+ end
85
+ }
86
+
87
+ scope :most_popular, ->{
88
+ posts.publicly_viewable.order(popularity: :desc)
89
+ }
90
+
91
+ scope :latest, ->{ posts.publicly_viewable.order(publish_at: :desc) }
92
+
93
+ scope :containing_slideshow, ->(slideshow){
94
+ where("virgo_posts.meta -> 'contains_slideshow' = 'true'").where("virgo_posts.meta -> 'slideshow_id' = ?", slideshow.id.to_s)
95
+ }
96
+
97
+ before_save :set_default_publish_at
98
+ before_save :set_live_status
99
+ before_save :generate_search_document
100
+ before_save :set_meta
101
+
102
+ after_save :expire_site_key
103
+ after_save :touch_dependent
104
+ after_save :set_columns_category
105
+ after_save :apply_tag_ordering!
106
+
107
+ if Rails.application.config.require_posts_to_have_category
108
+ validate :has_category
109
+ end
110
+
111
+ after_initialize :init
112
+
113
+ def self.category_feature(category)
114
+ category.posts.publicly_viewable.where(feature_on_category_page: true).order(publish_at: :asc).last
115
+ end
116
+
117
+ def self.front_page_feature
118
+ posts.publicly_viewable.where(feature_on_front_page: true).order(publish_at: :asc).last
119
+ end
120
+
121
+ def permalink
122
+ urls.post_detail_url(self, protocol: 'https')
123
+ end
124
+
125
+ def rendered_body
126
+ @rendered_body ||= Shortcode.process(body || '').html_safe
127
+ end
128
+
129
+ def draft?
130
+ status == :draft
131
+ end
132
+
133
+ def assigned?
134
+ status == :assigned
135
+ end
136
+
137
+ def published?
138
+ status == :published
139
+ end
140
+
141
+ def killed?
142
+ status == :killed
143
+ end
144
+
145
+ def hidden?
146
+ status == :hidden
147
+ end
148
+
149
+ def page?
150
+ post_type == :page
151
+ end
152
+
153
+ def post?
154
+ post_type == :post
155
+ end
156
+
157
+ def self.status_names
158
+ {
159
+ 'Draft' => :draft,
160
+ 'Assigned' => :assigned,
161
+ 'Published' => :published,
162
+ 'Killed' => :killed,
163
+ 'Hidden' => :hidden
164
+ }
165
+ end
166
+
167
+ def disqus_identifier
168
+ "post_#{id}"
169
+ end
170
+
171
+ def self.publish_scheduled!
172
+ candidates = Post.where.not(live: true).where("publish_at <= ?", Time.now.to_datetime)
173
+
174
+ candidates.each do |post|
175
+ post.update(live: true)
176
+ end
177
+ end
178
+
179
+ def has_citation?
180
+ citation_name.present? && citation_url.present?
181
+ end
182
+
183
+ # need to explicitly unscope this because papertrail
184
+ # imposes a forward chronological order with a default scope
185
+ def unscoped_versions
186
+ PaperTrail::Version.unscoped.where(item_id: id)
187
+ end
188
+
189
+ # i.e. if the lock timeout is "15 seconds" but the post's
190
+ # "editing_timestamp" has been bumped less than 15 seconds ago,
191
+ # we say the post is "mid-edit"
192
+ def is_mid_edit?
193
+ editing_timestamp && editing_timestamp > edit_lock_timeout.ago
194
+ end
195
+
196
+ def lock_for_editing!(user)
197
+ update!(editing_timestamp: Time.now.to_datetime, editing_user: user)
198
+ end
199
+
200
+ def attempt_edit_lock(user)
201
+ if is_mid_edit? && editing_user != user
202
+ false
203
+ else
204
+ lock_for_editing!(user) if persisted?
205
+ end
206
+ end
207
+
208
+ def edit_lock_timeout
209
+ Rails.application.config.edit_lock_timeout
210
+ end
211
+
212
+ def self.cache_key
213
+ "posts-#{maximum(:updated_at).try(:to_i)}-#{all.size}"
214
+ end
215
+
216
+ def track_view!
217
+
218
+ update_columns({
219
+ view_count: (view_count + 1),
220
+ popularity: calc_popularity(:increment)
221
+ })
222
+ end
223
+
224
+ def calc_popularity(opt=nil)
225
+ unless publish_at.nil?
226
+ # the number of days ago the very oldest post was made
227
+ days_ago_max = (Time.now - Post.minimum(:publish_at))/86400
228
+ days_ago = (Time.now - publish_at)/86400
229
+
230
+ # ex oldest post gets age_percentage 0.0, newest post gets 1.
231
+ age_percentage = (1 - (days_ago/(days_ago_max || 1.0)))
232
+
233
+ # boost from today to 10x...
234
+ age_percentage = 10 if publish_at.to_date == Date.today
235
+
236
+ # age factor is a number in the range of 0.1 - 1.00
237
+ # that is used to moderate the weight of votes for this post
238
+ age_factor = 0.1 + (0.9*age_percentage)
239
+
240
+ basis = opt == :increment ? (view_count + 1) : view_count
241
+
242
+ basis * age_factor
243
+ else
244
+ 0
245
+ end
246
+ end
247
+
248
+ def apply_tag_ordering!
249
+ if ordered_tag_ids.present?
250
+ if ordered_tag_ids.first.is_a?(String)
251
+ cleaned = ordered_tag_ids.first.gsub("[", "").gsub("]", "").split(",").map(&:to_i)
252
+ ordered_tag_ids = cleaned
253
+ end
254
+
255
+ ordered_tag_ids.each_with_index do |tag_id, index|
256
+ existing = post_tags.find_by(tag_id: tag_id)
257
+
258
+ if existing
259
+ existing.update!(position: index)
260
+ else
261
+ tag = Tag.find_by(id: tag_id)
262
+
263
+ if tag
264
+ post_tags.create!(tag_id: tag_id, position: index)
265
+ end
266
+ end
267
+ end
268
+
269
+ post_tags.where.not(tag_id: ordered_tag_ids).map &:destroy
270
+ end
271
+
272
+ true
273
+ end
274
+
275
+ def primary_category
276
+ categories.first
277
+ end
278
+
279
+ def description
280
+ if meta_description_tag_value.present?
281
+ meta_description_tag_value
282
+ elsif excerpt.present?
283
+ view_help.truncate(excerpt, length: 160)
284
+ else
285
+ view_help.truncate(view_help.strip_tags(rendered_body || ''), length: 160)
286
+ end
287
+ end
288
+
289
+ def thumb_image
290
+ if thumbnail_image.present?
291
+ thumbnail_image
292
+ else
293
+ featured_image
294
+ end
295
+ end
296
+
297
+
298
+ private
299
+
300
+ def set_default_publish_at
301
+ if status_changed? && status == :published && publish_at.blank?
302
+ self.publish_at = Time.now.to_datetime
303
+ end
304
+
305
+ true
306
+ end
307
+
308
+ def init
309
+ self.post_type = :post if post_type.nil?
310
+ end
311
+
312
+ def generate_search_document(opts={})
313
+ if headline_changed? || body_changed? || author_id_changed? || excerpt_changed? || opts[:force]
314
+ self.search_document = "#{headline} by #{author.byline}#{column ? (' ' + column.name) : ''}#{excerpt.present? ? (' ' + excerpt) : ''} #{strip_tags(rendered_body)}"
315
+ end
316
+
317
+ true
318
+ end
319
+
320
+ def set_meta
321
+ self.meta = {} if (meta.nil? || meta.is_a?(String))
322
+
323
+ # note: dirty tracking does not workin with hstore and meta_will_change! must be called
324
+ # for changes to an existing hash to persist: https://github.com/rails/rails/issues/6127
325
+
326
+ if has_slideshow?
327
+ slideshow_id = body.match(/\[slideshow id=\"(?<slideshow_id>\d+)\"\]/)['slideshow_id']
328
+ _will_change = (meta['contains_slideshow'] != 'true' || meta['slideshow_id'] != slideshow_id)
329
+ self.meta['contains_slideshow'] = 'true'
330
+ self.meta['slideshow_id'] = slideshow_id
331
+ meta_will_change! if _will_change
332
+ else
333
+ _will_change = meta['contains_slideshow'] != 'false'
334
+ self.meta['contains_slideshow'] = 'false'
335
+ meta_will_change! if _will_change
336
+ end
337
+
338
+ true
339
+ end
340
+
341
+ def set_columns_category
342
+ if column_id_changed?
343
+ if column_id.present? && !categories.include?(Category.COLUMNS)
344
+ self.categories << Category.COLUMNS
345
+ elsif column_id.blank? && categories.include?(Category.COLUMNS)
346
+ self.post_categories.where(category_id: Category.COLUMNS.id).map &:destroy
347
+ end
348
+ end
349
+
350
+ true
351
+ end
352
+
353
+ def has_slideshow?
354
+ body.present? && body.match(/\[slideshow id=\"\d+\"\]/).present?
355
+ end
356
+
357
+ def has_category
358
+ if categories.size < 1 && post?
359
+ errors[:base] << "Post must have at least one category"
360
+ end
361
+ end
362
+
363
+ # if a post has a publish_at value set and it is
364
+ # in the future, set the post to not be live
365
+ def set_live_status
366
+ if publish_at.present? && publish_at >= Time.now
367
+ self.live = false
368
+ else
369
+ self.live = true
370
+ end
371
+
372
+ true
373
+ end
374
+
375
+ def touch_dependent
376
+ categories.map &:touch
377
+ end
378
+
379
+ def should_generate_new_friendly_id?
380
+ slug.blank?
381
+ end
382
+
383
+ def slug_candidates
384
+ [
385
+ :headline,
386
+ [:headline, :uuid],
387
+ :uuid
388
+ ]
389
+ end
390
+ end
391
+ end