droom 0.0.1

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 (207) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +23 -0
  3. data/Rakefile +27 -0
  4. data/app/assets/images/droom/arrows.png +0 -0
  5. data/app/assets/images/droom/big_icons.png +0 -0
  6. data/app/assets/images/droom/blueblob.png +0 -0
  7. data/app/assets/images/droom/close.png +0 -0
  8. data/app/assets/images/droom/cross.png +0 -0
  9. data/app/assets/images/droom/download.png +0 -0
  10. data/app/assets/images/droom/greyblob.png +0 -0
  11. data/app/assets/images/droom/help/goodreader.jpg +0 -0
  12. data/app/assets/images/droom/ical.png +0 -0
  13. data/app/assets/images/droom/icons.png +0 -0
  14. data/app/assets/images/droom/medium_icons.png +0 -0
  15. data/app/assets/images/droom/medium_object_icons.png +0 -0
  16. data/app/assets/images/droom/minimonth.png +0 -0
  17. data/app/assets/images/droom/minisymbols.png +0 -0
  18. data/app/assets/images/droom/object_icons.png +0 -0
  19. data/app/assets/images/droom/pinkblob.png +0 -0
  20. data/app/assets/images/droom/place_busy.png +0 -0
  21. data/app/assets/images/droom/place_quiet.png +0 -0
  22. data/app/assets/images/droom/plus_bullet.png +0 -0
  23. data/app/assets/images/droom/search.png +0 -0
  24. data/app/assets/images/droom/setup.png +0 -0
  25. data/app/assets/images/droom/small_go.png +0 -0
  26. data/app/assets/images/droom/smallbullet.png +0 -0
  27. data/app/assets/images/droom/smallspinner.gif +0 -0
  28. data/app/assets/images/droom/spinner.gif +0 -0
  29. data/app/assets/images/droom/spr_toolbar_icons_r2.png +0 -0
  30. data/app/assets/images/droom/tablesort.png +0 -0
  31. data/app/assets/images/droom/tick.png +0 -0
  32. data/app/assets/images/droom/tinysymbols.png +0 -0
  33. data/app/assets/images/droom/twister.png +0 -0
  34. data/app/assets/images/droom/vcard.png +0 -0
  35. data/app/assets/images/droom/venue_bullet.png +0 -0
  36. data/app/assets/javascripts/droom.js.coffee +88 -0
  37. data/app/assets/javascripts/droom/calendar.js.coffee +121 -0
  38. data/app/assets/javascripts/droom/drag_sort.js.coffee +22 -0
  39. data/app/assets/javascripts/droom/forms.js.coffee +746 -0
  40. data/app/assets/javascripts/droom/lib/extensions.js.coffee +60 -0
  41. data/app/assets/javascripts/droom/lib/jquery.animate-colors.js +109 -0
  42. data/app/assets/javascripts/droom/lib/jquery.cookie.js +71 -0
  43. data/app/assets/javascripts/droom/lib/jquery.sortable.js +97 -0
  44. data/app/assets/javascripts/droom/lib/kalendae.js +1692 -0
  45. data/app/assets/javascripts/droom/lib/modernizr.js +4 -0
  46. data/app/assets/javascripts/droom/lib/parser_rules/advanced.js +553 -0
  47. data/app/assets/javascripts/droom/lib/parser_rules/simple.js +32 -0
  48. data/app/assets/javascripts/droom/lib/wysihtml5.js +9550 -0
  49. data/app/assets/javascripts/droom/map.js.coffee +123 -0
  50. data/app/assets/javascripts/droom/sort.js.coffee +126 -0
  51. data/app/assets/javascripts/droom/suggester.js.coffee +230 -0
  52. data/app/assets/stylesheets/droom.css.sass +1151 -0
  53. data/app/assets/stylesheets/lib/_kalendae.css.sass +142 -0
  54. data/app/assets/stylesheets/lib/_toolbar.css.sass +192 -0
  55. data/app/controllers/droom/dashboard_controller.rb +27 -0
  56. data/app/controllers/droom/document_attachments_controller.rb +19 -0
  57. data/app/controllers/droom/documents_controller.rb +116 -0
  58. data/app/controllers/droom/engine_controller.rb +43 -0
  59. data/app/controllers/droom/events_controller.rb +120 -0
  60. data/app/controllers/droom/group_invitations_controller.rb +47 -0
  61. data/app/controllers/droom/groups_controller.rb +67 -0
  62. data/app/controllers/droom/invitations_controller.rb +43 -0
  63. data/app/controllers/droom/memberships_controller.rb +47 -0
  64. data/app/controllers/droom/pages_controller.rb +61 -0
  65. data/app/controllers/droom/people_controller.rb +92 -0
  66. data/app/controllers/droom/suggestions_controller.rb +58 -0
  67. data/app/controllers/droom/venues_controller.rb +39 -0
  68. data/app/helpers/droom/droom_helper.rb +74 -0
  69. data/app/models/droom/agenda_category.rb +8 -0
  70. data/app/models/droom/category.rb +27 -0
  71. data/app/models/droom/document.rb +110 -0
  72. data/app/models/droom/document_attachment.rb +71 -0
  73. data/app/models/droom/document_link.rb +31 -0
  74. data/app/models/droom/event.rb +409 -0
  75. data/app/models/droom/event_set.rb +6 -0
  76. data/app/models/droom/group.rb +66 -0
  77. data/app/models/droom/group_invitation.rb +23 -0
  78. data/app/models/droom/invitation.rb +30 -0
  79. data/app/models/droom/membership.rb +27 -0
  80. data/app/models/droom/page.rb +26 -0
  81. data/app/models/droom/person.rb +302 -0
  82. data/app/models/droom/personal_document.rb +98 -0
  83. data/app/models/droom/recurrence_rule.rb +82 -0
  84. data/app/models/droom/venue.rb +125 -0
  85. data/app/views/droom/dashboard/_marginalia.html.haml +3 -0
  86. data/app/views/droom/dashboard/_my_future_events.html.haml +9 -0
  87. data/app/views/droom/dashboard/_my_group_documents.html.haml +6 -0
  88. data/app/views/droom/dashboard/_my_past_events.haml +6 -0
  89. data/app/views/droom/dashboard/index.html.haml +5 -0
  90. data/app/views/droom/documents/_created.html.haml +2 -0
  91. data/app/views/droom/documents/_document.html.haml +14 -0
  92. data/app/views/droom/documents/_document_line.html.haml +2 -0
  93. data/app/views/droom/documents/_documents_list.html.haml +31 -0
  94. data/app/views/droom/documents/_documents_table.html.haml +13 -0
  95. data/app/views/droom/documents/_event_document_form.html.haml +15 -0
  96. data/app/views/droom/documents/_form.html.haml +22 -0
  97. data/app/views/droom/documents/_listing.html.haml +8 -0
  98. data/app/views/droom/documents/_suggested.html.haml +9 -0
  99. data/app/views/droom/documents/_table_document.html.haml +31 -0
  100. data/app/views/droom/documents/edit.html.haml +1 -0
  101. data/app/views/droom/documents/index.html.haml +29 -0
  102. data/app/views/droom/documents/new.html.haml +1 -0
  103. data/app/views/droom/errors/bang.html.haml +12 -0
  104. data/app/views/droom/errors/not_allowed.html.haml +12 -0
  105. data/app/views/droom/errors/not_found.html.haml +12 -0
  106. data/app/views/droom/events/_attachment.html.haml +1 -0
  107. data/app/views/droom/events/_attachment_list.html.haml +4 -0
  108. data/app/views/droom/events/_calendar.html.haml +54 -0
  109. data/app/views/droom/events/_created.html.haml +2 -0
  110. data/app/views/droom/events/_event.html.haml +77 -0
  111. data/app/views/droom/events/_event_line.html.haml +13 -0
  112. data/app/views/droom/events/_events.html.haml +2 -0
  113. data/app/views/droom/events/_form.html.haml +35 -0
  114. data/app/views/droom/events/_invitations.html.haml +20 -0
  115. data/app/views/droom/events/_other_page_parts.html.haml +0 -0
  116. data/app/views/droom/events/_popup_event.html.haml +6 -0
  117. data/app/views/droom/events/_suggested.html.haml +12 -0
  118. data/app/views/droom/events/_views.html.haml +7 -0
  119. data/app/views/droom/events/edit.html.haml +1 -0
  120. data/app/views/droom/events/index.html.haml +12 -0
  121. data/app/views/droom/events/index.rss.builder +20 -0
  122. data/app/views/droom/events/new.html.haml +1 -0
  123. data/app/views/droom/events/show.html.haml +4 -0
  124. data/app/views/droom/group_invitations/_attending_groups.html.haml +8 -0
  125. data/app/views/droom/group_invitations/_created.html.haml +3 -0
  126. data/app/views/droom/group_invitations/_form.html.haml +4 -0
  127. data/app/views/droom/group_invitations/new.html.haml +1 -0
  128. data/app/views/droom/groups/_created.html.haml +3 -0
  129. data/app/views/droom/groups/_form.html.haml +12 -0
  130. data/app/views/droom/groups/_group.html.haml +11 -0
  131. data/app/views/droom/groups/_groups.html.haml +3 -0
  132. data/app/views/droom/groups/edit.html.haml +1 -0
  133. data/app/views/droom/groups/index.html.haml +7 -0
  134. data/app/views/droom/groups/show.html.haml +13 -0
  135. data/app/views/droom/invitations/_attending_people.html.haml +8 -0
  136. data/app/views/droom/invitations/_created.html.haml +3 -0
  137. data/app/views/droom/invitations/_form.html.haml +5 -0
  138. data/app/views/droom/invitations/new.html.haml +1 -0
  139. data/app/views/droom/memberships/_button.html.haml +11 -0
  140. data/app/views/droom/memberships/_created.html.haml +4 -0
  141. data/app/views/droom/memberships/_form.html.haml +7 -0
  142. data/app/views/droom/memberships/_member.html.haml +33 -0
  143. data/app/views/droom/memberships/_memberships.html.haml +4 -0
  144. data/app/views/droom/pages/_contents.html.haml +10 -0
  145. data/app/views/droom/pages/_form.html.haml +36 -0
  146. data/app/views/droom/pages/_full_page.html.haml +17 -0
  147. data/app/views/droom/pages/_page.html.haml +5 -0
  148. data/app/views/droom/pages/_pages.html.haml +2 -0
  149. data/app/views/droom/pages/admin.html.haml +24 -0
  150. data/app/views/droom/pages/edit.html.haml +1 -0
  151. data/app/views/droom/pages/index.html.haml +10 -0
  152. data/app/views/droom/pages/new.html.haml +4 -0
  153. data/app/views/droom/pages/show.html.haml +5 -0
  154. data/app/views/droom/people/_created.html.haml +6 -0
  155. data/app/views/droom/people/_form.html.haml +40 -0
  156. data/app/views/droom/people/_people.html.haml +29 -0
  157. data/app/views/droom/people/_person.html.haml +34 -0
  158. data/app/views/droom/people/_suggested.html.haml +9 -0
  159. data/app/views/droom/people/edit.html.haml +1 -0
  160. data/app/views/droom/people/index.html.haml +11 -0
  161. data/app/views/droom/people/new.html.haml +1 -0
  162. data/app/views/droom/people/show.html.haml +1 -0
  163. data/app/views/droom/shared/_calendar_and_search.html.haml +2 -0
  164. data/app/views/droom/shared/_calendar_holder.haml +3 -0
  165. data/app/views/droom/shared/_controls.html.haml +8 -0
  166. data/app/views/droom/shared/_navigation.html.haml +8 -0
  167. data/app/views/droom/shared/_search_form.html.haml +4 -0
  168. data/app/views/droom/shared/_suggestions.html.haml +11 -0
  169. data/app/views/droom/shared/_toolbar.html.haml +17 -0
  170. data/app/views/droom/venues/_suggested.html.haml +6 -0
  171. data/app/views/droom/venues/index.html.haml +6 -0
  172. data/app/views/droom/venues/show.html.haml +21 -0
  173. data/app/views/layouts/droom/application.html.haml +36 -0
  174. data/config/initializers/dav.rb +2 -0
  175. data/config/initializers/paperclip.rb +26 -0
  176. data/config/initializers/snail.rb +2 -0
  177. data/config/locales/en.yml +191 -0
  178. data/config/routes.rb +51 -0
  179. data/db/migrate/20120910075016_create_droom_data.rb +134 -0
  180. data/db/migrate/20120917095804_agenda_sections.rb +13 -0
  181. data/db/migrate/20120918121352_add_postal_address_to_people.rb +10 -0
  182. data/db/migrate/20121009075049_give_groups_descriptions.rb +5 -0
  183. data/db/migrate/20121009105244_more_names.rb +5 -0
  184. data/db/migrate/20121009145944_event_agenda_sections.rb +13 -0
  185. data/db/migrate/20121011091230_create_group_invitations.rb +10 -0
  186. data/db/migrate/20121012144720_give_people_positions.rb +5 -0
  187. data/db/migrate/20121012154558_help_pages.rb +13 -0
  188. data/db/migrate/20121012163201_category_slugs.rb +5 -0
  189. data/db/migrate/20121101160102_document_links.rb +14 -0
  190. data/db/migrate/20121101181247_people_visibility.rb +7 -0
  191. data/db/migrate/20121102094738_shy_people.rb +6 -0
  192. data/db/migrate/20121102095856_visibility_defaults.rb +8 -0
  193. data/lib/droom.rb +73 -0
  194. data/lib/droom/dav_resource.rb +36 -0
  195. data/lib/droom/engine.rb +8 -0
  196. data/lib/droom/helpers.rb +25 -0
  197. data/lib/droom/monkeys.rb +15 -0
  198. data/lib/droom/renderers.rb +13 -0
  199. data/lib/droom/validators.rb +5 -0
  200. data/lib/droom/version.rb +3 -0
  201. data/lib/generators/droom/dashboard/dashboard_generator.rb +16 -0
  202. data/lib/generators/droom/install/USAGE +9 -0
  203. data/lib/generators/droom/install/install_generator.rb +15 -0
  204. data/lib/generators/droom/install/templates/droom_initializer.rb +6 -0
  205. data/lib/generators/droom/views/views_generator.rb +9 -0
  206. data/lib/tasks/droom_tasks.rake +4 -0
  207. metadata +635 -0
@@ -0,0 +1,6 @@
1
+ module Droom
2
+ class EventSet < ActiveRecord::Base
3
+ belongs_to :created_by, :class_name => "User"
4
+ has_many :events, :dependent => :nullify
5
+ end
6
+ end
@@ -0,0 +1,66 @@
1
+ module Droom
2
+ class Group < ActiveRecord::Base
3
+ attr_accessible :name, :leader_id, :description
4
+
5
+ belongs_to :created_by, :class_name => 'User'
6
+ belongs_to :leader, :class_name => 'Person'
7
+
8
+ has_many :group_invitations, :dependent => :destroy, :uniq => true
9
+ has_many :events, :through => :group_invitations
10
+
11
+ has_many :memberships, :dependent => :destroy
12
+ has_many :people, :through => :memberships, :uniq => true
13
+
14
+ has_many :document_attachments, :as => :attachee, :dependent => :destroy
15
+ has_many :documents, :through => :document_attachments
16
+
17
+ before_save :check_slug
18
+
19
+ scope :with_documents,
20
+ select("droom_groups.*")
21
+ .joins("INNER JOIN droom_document_attachments ON droom_groups.id = droom_document_attachments.attachee_id AND droom_document_attachments.attachee_type = 'Droom::Group'")
22
+ .group("droom_groups.id")
23
+
24
+
25
+ scope :name_matching, lambda { |fragment|
26
+ fragment = "%#{fragment}%"
27
+ where('droom_groups.name like ?', fragment)
28
+ }
29
+
30
+ default_scope order("droom_groups.created_at ASC")
31
+
32
+ def identifier
33
+ 'group'
34
+ end
35
+
36
+ def admit(person)
37
+ self.people << person
38
+ end
39
+
40
+ def attach(doc)
41
+ self.documents << doc
42
+ end
43
+
44
+ def membership_for(person)
45
+ self.memberships.for(person).first
46
+ end
47
+
48
+ def invite_to(event)
49
+ group_invitations.find_or_create_by_event_id(event.id)
50
+ end
51
+
52
+ def uninvite_from(event)
53
+ group_invitation = group_invitations.find_by_event_id(event.id)
54
+ group_invitation.invitations.to_event(event).each do |invitation|
55
+ invitation.destroy!
56
+ end
57
+ group_invitation.destroy!
58
+ end
59
+
60
+ protected
61
+
62
+ def check_slug
63
+ ensure_presence_and_uniqueness_of(:slug, name.parameterize)
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,23 @@
1
+ module Droom
2
+ class GroupInvitation < ActiveRecord::Base
3
+ attr_accessible :event_id, :group_id
4
+
5
+ belongs_to :created_by, :class_name => "User"
6
+ belongs_to :group
7
+ belongs_to :event
8
+ has_many :invitations, :dependent => :destroy
9
+ after_create :create_personal_invitations
10
+ validates_uniqueness_of :group_id, :scope => :event_id
11
+
12
+ scope :to_event, lambda { |event|
13
+ where("group_invitations.event_id = ?", event.id)
14
+ }
15
+
16
+ def create_personal_invitations
17
+ group.people.each do |person|
18
+ invitations.find_or_create_by_person_id_and_event_id(person.id, event.id)
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ module Droom
2
+ class Invitation < ActiveRecord::Base
3
+ attr_accessible :event_id, :person_id
4
+
5
+ belongs_to :person
6
+ belongs_to :event
7
+ belongs_to :group_invitation
8
+ belongs_to :created_by, :class_name => "User"
9
+
10
+ after_create :link_documents
11
+ after_destroy :unlink_documents
12
+
13
+ validates_uniqueness_of :person_id, :scope => [:event_id, :group_invitation_id]
14
+
15
+ scope :to_event, lambda { |event|
16
+ where(["event_id = ?", event.id])
17
+ }
18
+
19
+ protected
20
+
21
+ def link_documents
22
+ person.document_attachments << event.document_attachments
23
+ end
24
+
25
+ def unlink_documents
26
+ person.document_attachments -= event.document_attachments
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ module Droom
2
+ class Membership < ActiveRecord::Base
3
+ attr_accessible :group_id, :person_id
4
+
5
+ belongs_to :person
6
+ belongs_to :group
7
+ belongs_to :created_by, :class_name => "User"
8
+
9
+ after_create :link_documents
10
+ after_destroy :unlink_documents
11
+
12
+ scope :of_group, lambda { |group|
13
+ where(["group_id = ?", group.id])
14
+ }
15
+
16
+ protected
17
+
18
+ def link_documents
19
+ person.document_attachments << group.document_attachments
20
+ end
21
+
22
+ def unlink_documents
23
+ person.document_attachments -= group.document_attachments
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ # Pages are currently just minimal text-holders. The text is entered in markdown,
2
+ # rendered on save and placed into the usual layout.
3
+ #
4
+ module Droom
5
+ class Page < ActiveRecord::Base
6
+ attr_accessible :title, :slug, :summary, :body, :video_id
7
+
8
+ before_validation :check_slug
9
+ before_save :render_body
10
+ validates :slug, :uniqueness => true, :presence => true
11
+ validates :title, :presence => true
12
+ default_scope order('title ASC')
13
+
14
+ protected
15
+
16
+ def check_slug
17
+ ensure_presence_and_uniqueness_of(:slug, title.parameterize)
18
+ end
19
+
20
+ def render_body
21
+ Rails.logger.warn ">>> rendering body: #{self.body.inspect}"
22
+ markdown = RDiscount.new(self.body)
23
+ self.rendered_body = markdown.to_html
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,302 @@
1
+ # This has been pulled from the yearbook and simplified. Docs need updating to match.
2
+ require 'vcard'
3
+ module Droom
4
+ class Person < ActiveRecord::Base
5
+ attr_accessible :name, :forename, :email, :phone, :description, :user, :title, :invite_on_creation, :admin_user, :position
6
+ attr_accessor :invite_on_creation, :admin_user
7
+ acts_as_list
8
+ ### Associations
9
+ #
10
+ has_many :memberships, :dependent => :destroy
11
+ has_many :groups, :through => :memberships
12
+
13
+ has_many :invitations, :dependent => :destroy
14
+ has_many :events, :through => :invitations
15
+
16
+ # document_links is an automatically maintained index that we use to make easier the task of retrieving
17
+ # the documents this person is allowed to see.
18
+ has_many :document_links, :dependent => :destroy
19
+ has_many :document_attachments, :through => :document_links
20
+ # is this association really needed? We always retrieve the document list using the visible_to scope, so as to get public docs too.
21
+ has_many :documents, :through => :document_attachments, :uniq => true
22
+
23
+ # personal documents are the document clones created when a user logs to her DAV folder.
24
+ # they are spun off the document links
25
+ has_many :personal_documents, :through => :document_links
26
+
27
+ # The `user` is this person's administrative account for logging in and out and forgetting her password.
28
+ # A person can be listed without ever having a user, and a user account can exist (for an administrator)
29
+ # without having a person.
30
+ belongs_to :user, :class_name => Droom.user_class.to_s, :dependent => :destroy
31
+
32
+ before_save :update_user
33
+ after_save :invite_if_instructed
34
+
35
+ # The data requirements are minimal, with the idea that the directory will be populated gradually.
36
+ validates :name, :presence => true
37
+
38
+ default_scope order("droom_people.#{Droom.people_sort}")
39
+
40
+ scope :all_private, where("shy = 1")
41
+ scope :not_private, where("shy <> 1")
42
+ scope :all_public, where("public = 1 AND shy <> 1")
43
+ scope :not_public, where("public <> 1 OR shy = 1)")
44
+
45
+ scope :name_matching, lambda { |fragment|
46
+ fragment = "%#{fragment}%"
47
+ where('droom_people.name LIKE :f OR droom_people.forename LIKE :f', :f => fragment)
48
+ }
49
+
50
+ # warning! won't work in SQLite.
51
+ scope :visible_to, lambda { |person|
52
+ if person
53
+ select('droom_people.*')
54
+ .joins('LEFT OUTER JOIN droom_memberships as dm1 on droom_people.id = dm1.person_id')
55
+ .joins('LEFT OUTER JOIN droom_memberships as dm2 on dm1.group_id = dm2.group_id')
56
+ .where(['(droom_people.public = 1 OR dm2.person_id = ?) AND (droom_people.shy IS NULL OR droom_people.shy <> 1)', person.id])
57
+ .group('droom_people.id')
58
+ else
59
+ all_public
60
+ end
61
+ }
62
+
63
+ scope :personally_invited_to_event, lambda { |event|
64
+ joins('LEFT OUTER JOIN droom_invitations on droom_people.id = droom_invitations.person_id').where('droom_invitations.group_invitation_id is null AND droom_invitations.event_id = ?', event.id)
65
+ }
66
+
67
+ ### Images
68
+ #
69
+ # The treatment here is very basic compared to the yearbook's uploader, but we might bring that across
70
+ # if this starts to look like a useful directory resource.
71
+ #
72
+ has_attached_file :image, {
73
+ :styles => {:standard => "400x300#", :thumb => "100x100#"},
74
+ :default_url => "/assets/person/nopicture_:style.png"
75
+ }
76
+
77
+ def image_url(style=:standard)
78
+ image.url(style)
79
+ end
80
+
81
+ def identifier
82
+ 'person'
83
+ end
84
+
85
+ def formal_name
86
+ [title, forename, name].compact.join(' ').strip
87
+ end
88
+
89
+ def informal_name
90
+ if Droom.use_forenames
91
+ forename
92
+ else
93
+ name
94
+ end
95
+ end
96
+
97
+ # I don't think we're using this anywhere at the moment, but a JSON API will grow here. Other classes already make more use of
98
+ # JSON representation, eg institutions for mapping or tags for tagging.
99
+ #
100
+ def as_json(options={})
101
+ {
102
+ :id => id,
103
+ :name => name,
104
+ :title => title
105
+ }
106
+ end
107
+
108
+ # *for_selection* returns a list of people in options_for_select format with which to populate a select box.
109
+ #
110
+ def self.for_selection
111
+ self.published.map{|p| [p.name, p.id] }
112
+ end
113
+
114
+ # Document links function as a lookup table to speed up the process of working out what this person can see.
115
+ # They are created when an attachment, invitation or membership confers access, and destroyed when a link in
116
+ # that chain is removed.
117
+ #
118
+ # This will rebuild the document_link index for this person.
119
+ #
120
+ def repair_document_links
121
+ # NB this will also destroy all our personal documents. Very much a last resort.
122
+ self.document_links.destroy_all
123
+ group_and_event_attachments.each do |da|
124
+ document_links.create(:document_attachment => da)
125
+ end
126
+ end
127
+
128
+ def group_and_event_attachments
129
+ Droom::DocumentAttachment.to_groups(groups) + Droom::DocumentAttachment.to_events(events)
130
+ end
131
+
132
+ # We defer the creation and updating of personal documents until the person actually logs in over DAV. At that stage a call
133
+ # goes to person.create_personal_documents and the copying begins. It's not really ideal from a responsiveness point of view
134
+ # but it saves a great deal of update hassle (and storage space).
135
+ #
136
+ # NB this will not recreate deleted files: we only look as far as the PersonalDocument object, not the file
137
+ # it would usually have. This is to allow people to delete files they don't want, without having them
138
+ # constantly recreated.
139
+ #
140
+ def create_personal_documents
141
+ create_and_update_dav_directories
142
+ document_links.each { |dl| dl.ensure_personal_document }
143
+ end
144
+
145
+ def create_and_update_dav_directories
146
+ document_links.each do |dl|
147
+ p "-> creating DAV directory #{dl.slug}"
148
+ create_dav_directory(dl.slug)
149
+ end
150
+ end
151
+
152
+ def create_dav_directory(name)
153
+ FileUtils.mkdir_p(Rails.root + "#{Droom.dav_root}/#{self.id}/#{name}")
154
+ end
155
+
156
+ # If a personal version exists, we will return that since it may contain annotations or amendments.
157
+ #
158
+ def personal_or_generic_version_of(document)
159
+ personal_version_of(document) || document
160
+ end
161
+
162
+ # But if there has been no DAV login there can be no personal version.
163
+ #
164
+ def personal_version_of(document)
165
+ personal_documents.derived_from(document).first
166
+ end
167
+
168
+ # group_documents returns all those documents that have been attached to a group of which this person is a member.
169
+ # These documents will not show up in the calendar (since they are not attached to an event) so it's often a useful list.
170
+ #
171
+ def group_documents
172
+ groups.any? ? Droom::Document.attached_to_these_groups(groups).by_date : []
173
+ end
174
+
175
+ # It is possible to give us a document by creating an attachment that has no attachee
176
+ # but which links to this person.
177
+ #
178
+ def attach(doc)
179
+ document_attachments.create(:document => doc)
180
+ end
181
+
182
+
183
+ def invite_to(event)
184
+ invitations.find_or_create_by_event_id(event.id) if event
185
+ end
186
+
187
+ def uninvite_from(event)
188
+ p "-> destroying invitations: #{invitations.to_event(event).inspect}"
189
+ invitations.to_event(event).destroy_all
190
+ end
191
+
192
+ def admit_to(group)
193
+ memberships.find_or_create_by_group_id(group.id) if group
194
+ end
195
+
196
+ def expel_from(group)
197
+ memberships.of_group(group).destroy_all
198
+ end
199
+
200
+ def member_of?(group)
201
+ group && memberships.of_group(group).any?
202
+ end
203
+
204
+ def invited_to?(event)
205
+ group && invitations.to_event(event).any?
206
+ end
207
+
208
+ # some magic glue to allow slightly indiscriminate use of user and person objects.
209
+
210
+ def person
211
+ self
212
+ end
213
+
214
+ def admin?
215
+ user && user.admin?
216
+ end
217
+
218
+ def has_active_user?
219
+ user && user.activated?
220
+ end
221
+
222
+ def has_invited_user?
223
+ user && user.invited?
224
+ end
225
+
226
+ def has_admin_user?
227
+ user && user.admin?
228
+ end
229
+
230
+
231
+ # Snail is a library that abstracts away - as far as possible - the vagaries of international address formats. Here we map our data columns onto Snail's abstract representations so that they can be rendered into the correct format for their country.
232
+ def address
233
+ Snail.new(
234
+ :line_1 => post_line1,
235
+ :line_2 => post_line2,
236
+ :city => post_city,
237
+ :region => post_region,
238
+ :postal_code => post_code,
239
+ :country => post_country
240
+ )
241
+ end
242
+
243
+ def address?
244
+ post_line1? && post_city
245
+ end
246
+
247
+ def to_vcf
248
+ @vcard ||= Vpim::Vcard::Maker.make2 do |maker|
249
+ maker.add_name do |n|
250
+ n.given = name || ""
251
+ end
252
+ maker.add_addr {|a|
253
+ a.location = 'home' # until we do this properly with multiple contact sets
254
+ a.country = post_country || ""
255
+ a.region = post_region || ""
256
+ a.locality = post_city || ""
257
+ a.street = "#{post_line1}, #{post_line2}"
258
+ a.postalcode = post_code || ""
259
+ }
260
+ maker.add_tel phone { |t| t.location = 'home' } unless phone.blank?
261
+ # maker.add_tel mobile { |t| t.location = 'cell' } unless mobile.blank?
262
+ maker.add_email email { |e| t.location = 'home' }
263
+ end
264
+ @vcard.to_s
265
+ end
266
+
267
+ def self.vcards_for(people=[])
268
+ people.map(&:vcf).join("\n")
269
+ end
270
+
271
+ protected
272
+
273
+ def invite_if_instructed
274
+ invite_user if invite_on_creation
275
+ end
276
+
277
+ # ### Administration & callbacks
278
+ #
279
+ # At some point we may want to create a user to log in and look after this person.
280
+ # This usually has the side effect of sending out a login invitation.
281
+ #
282
+ def invite_user
283
+ unless self.user
284
+ if self.name? && self.email?
285
+ self.create_user(:forename => forename, :name => name, :email => email, :phone => phone, :admin => admin_user)
286
+ self.save
287
+ end
288
+ end
289
+ end
290
+
291
+ def update_user
292
+ if self.user && self.user.persisted?
293
+ self.user.update_column(:email, self.email) if email_changed?
294
+ self.user.update_column(:phone, self.phone) if phone_changed?
295
+ self.user.update_column(:name, self.name) if name_changed?
296
+ self.user.update_column(:forename, self.forename) if forename_changed?
297
+ end
298
+ end
299
+
300
+ end
301
+ end
302
+