liquid_feedback_frontend

view model/member.lua @ 1800:b87997219042

Updated spanish translation
author bsw
date Thu Oct 21 15:22:29 2021 +0200 (2021-10-21)
parents 446d2798f424
children
line source
1 Member = mondelefant.new_class()
2 Member.table = 'member'
4 local function secret_token()
5 local parts = {}
6 for i = 1, 5 do
7 parts[#parts+1] = multirand.string(5, "23456789bcdfghjkmnpqrstvwxyz")
8 end
9 return (table.concat(parts, "-"))
10 end
12 Member:add_reference{
13 mode = "1m",
14 to = "MemberHistory",
15 this_key = 'id',
16 that_key = 'member_id',
17 ref = 'history_entries',
18 back_ref = 'member'
19 }
21 Member:add_reference{
22 mode = '1m',
23 to = "MemberImage",
24 this_key = 'id',
25 that_key = 'member_id',
26 ref = 'images',
27 back_ref = 'member'
28 }
30 Member:add_reference{
31 mode = '1m',
32 to = "Contact",
33 this_key = 'id',
34 that_key = 'member_id',
35 ref = 'contacts',
36 back_ref = 'member',
37 default_order = '"other_member_id"'
38 }
40 Member:add_reference{
41 mode = '1m',
42 to = "Contact",
43 this_key = 'id',
44 that_key = 'member_id',
45 ref = 'foreign_contacts',
46 back_ref = 'other_member',
47 default_order = '"member_id"'
48 }
50 Member:add_reference{
51 mode = '1m',
52 to = "Session",
53 this_key = 'id',
54 that_key = 'member_id',
55 ref = 'sessions',
56 back_ref = 'member',
57 default_order = '"ident"'
58 }
60 Member:add_reference{
61 mode = '1m',
62 to = "Draft",
63 this_key = 'id',
64 that_key = 'author_id',
65 ref = 'drafts',
66 back_ref = 'author',
67 default_order = '"id"'
68 }
70 Member:add_reference{
71 mode = '1m',
72 to = "Suggestion",
73 this_key = 'id',
74 that_key = 'author_id',
75 ref = 'suggestions',
76 back_ref = 'author',
77 default_order = '"id"'
78 }
80 Member:add_reference{
81 mode = '1m',
82 to = "Membership",
83 this_key = 'id',
84 that_key = 'member_id',
85 ref = 'memberships',
86 back_ref = 'member',
87 default_order = '"area_id"'
88 }
90 Member:add_reference{
91 mode = '1m',
92 to = "Interest",
93 this_key = 'id',
94 that_key = 'member_id',
95 ref = 'interests',
96 back_ref = 'member',
97 default_order = '"id"'
98 }
100 Member:add_reference{
101 mode = '1m',
102 to = "Initiator",
103 this_key = 'id',
104 that_key = 'member_id',
105 ref = 'initiators',
106 back_ref = 'member'
107 }
109 Member:add_reference{
110 mode = '1m',
111 to = "Supporter",
112 this_key = 'id',
113 that_key = 'member_id',
114 ref = 'supporters',
115 back_ref = 'member'
116 }
118 Member:add_reference{
119 mode = '1m',
120 to = "Opinion",
121 this_key = 'id',
122 that_key = 'member_id',
123 ref = 'opinions',
124 back_ref = 'member',
125 default_order = '"id"'
126 }
128 Member:add_reference{
129 mode = '1m',
130 to = "Delegation",
131 this_key = 'id',
132 that_key = 'truster_id',
133 ref = 'outgoing_delegations',
134 back_ref = 'truster',
135 -- default_order = '"id"'
136 }
138 Member:add_reference{
139 mode = '1m',
140 to = "Delegation",
141 this_key = 'id',
142 that_key = 'trustee_id',
143 ref = 'incoming_delegations',
144 back_ref = 'trustee',
145 -- default_order = '"id"'
146 }
148 Member:add_reference{
149 mode = '1m',
150 to = "DirectVoter",
151 this_key = 'id',
152 that_key = 'member_id',
153 ref = 'direct_voter',
154 back_ref = 'member',
155 default_order = '"issue_id"'
156 }
158 Member:add_reference{
159 mode = '1m',
160 to = "Vote",
161 this_key = 'id',
162 that_key = 'member_id',
163 ref = 'vote',
164 back_ref = 'member',
165 default_order = '"issue_id", "initiative_id"'
166 }
168 Member:add_reference{
169 mode = '11',
170 to = "MemberProfile",
171 this_key = 'id',
172 that_key = 'member_id',
173 ref = 'profile',
174 back_ref = 'member'
175 }
177 Member:add_reference{
178 mode = '11',
179 to = "MemberSettings",
180 this_key = 'id',
181 that_key = 'member_id',
182 ref = 'settings',
183 back_ref = 'member'
184 }
186 Member:add_reference{
187 mode = 'mm',
188 to = "Member",
189 this_key = 'id',
190 that_key = 'id',
191 connected_by_table = 'contact',
192 connected_by_this_key = 'member_id',
193 connected_by_that_key = 'other_member_id',
194 ref = 'saved_members',
195 }
197 Member:add_reference{
198 mode = 'mm',
199 to = "Member",
200 this_key = 'id',
201 that_key = 'id',
202 connected_by_table = 'contact',
203 connected_by_this_key = 'other_member_id',
204 connected_by_that_key = 'member_id',
205 ref = 'saved_by_members',
206 }
208 Member:add_reference{
209 mode = 'mm',
210 to = "Unit",
211 this_key = 'id',
212 that_key = 'id',
213 connected_by_table = 'privilege',
214 connected_by_this_key = 'member_id',
215 connected_by_that_key = 'unit_id',
216 ref = 'units'
217 }
219 Member:add_reference{
220 mode = 'mm',
221 to = "Area",
222 this_key = 'id',
223 that_key = 'id',
224 connected_by_table = 'membership',
225 connected_by_this_key = 'member_id',
226 connected_by_that_key = 'area_id',
227 ref = 'areas'
228 }
230 Member:add_reference{
231 mode = 'mm',
232 to = "Issue",
233 this_key = 'id',
234 that_key = 'id',
235 connected_by_table = 'interest',
236 connected_by_this_key = 'member_id',
237 connected_by_that_key = 'issue_id',
238 ref = 'issues'
239 }
241 Member:add_reference{
242 mode = 'mm',
243 to = "Initiative",
244 this_key = 'id',
245 that_key = 'id',
246 connected_by_table = 'initiator',
247 connected_by_this_key = 'member_id',
248 connected_by_that_key = 'initiative_id',
249 ref = 'initiated_initiatives'
250 }
252 Member:add_reference{
253 mode = 'mm',
254 to = "Initiative",
255 this_key = 'id',
256 that_key = 'id',
257 connected_by_table = 'supporter',
258 connected_by_this_key = 'member_id',
259 connected_by_that_key = 'initiative_id',
260 ref = 'supported_initiatives'
261 }
263 function Member:build_selector(args)
264 local selector = self:new_selector()
265 if args.active ~= nil then
266 selector:add_where{ "member.active = ?", args.active }
267 end
268 if args.locked ~= nil then
269 selector:add_where{ "member.locked = ?", args.locked }
270 end
271 if args.is_contact_of_member_id then
272 selector:join("contact", "__model_member__contact", "member.id = __model_member__contact.other_member_id")
273 selector:add_where{ "__model_member__contact.member_id = ?", args.is_contact_of_member_id }
274 end
275 if args.voting_right_for_unit_id then
276 selector:join("privilege", "__model_member__privilege", { "member.id = __model_member__privilege.member_id AND __model_member__privilege.voting_right AND __model_member__privilege.unit_id = ?", args.voting_right_for_unit_id })
277 end
278 if args.admin_search then
279 local search_string = "%" .. args.admin_search .. "%"
280 selector:add_where{ "member.identification ILIKE ? OR member.name ILIKE ?", search_string, search_string }
281 end
282 if args.order then
283 if args.order == "id" then
284 selector:add_order_by("id")
285 elseif args.order == "identification" then
286 selector:add_order_by("identification")
287 elseif args.order == "name" then
288 selector:add_order_by("name")
289 else
290 error("invalid order")
291 end
292 end
293 return selector
294 end
296 function Member:lockForReference()
297 self.get_db_conn().query("LOCK TABLE " .. self:get_qualified_table() .. " IN ROW SHARE MODE")
298 end
301 function Member:get_all_by_authority(authority)
303 local members = Member:new_selector()
304 :add_where{ "authority = ?", authority }
305 :add_field("authority_uid")
306 :exec()
308 return members
309 end
311 function Member.object:set_password(password)
312 trace.disable()
314 local hash_prefix
315 local salt_length
317 local function rounds()
318 return multirand.integer(
319 config.password_hash_min_rounds,
320 config.password_hash_max_rounds
321 )
322 end
324 if config.password_hash_algorithm == "crypt_md5" then
325 hash_prefix = "$1$"
326 salt_length = 8
328 elseif config.password_hash_algorithm == "crypt_sha256" then
329 hash_prefix = "$5$rounds=" .. rounds() .. "$"
330 salt_length = 16
332 elseif config.password_hash_algorithm == "crypt_sha512" then
333 hash_prefix = "$6$rounds=" .. rounds() .. "$"
334 salt_length = 16
336 else
337 error("Unknown hash algorithm selected in configuration")
339 end
341 hash_prefix = hash_prefix .. multirand.string(
342 salt_length,
343 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz./"
344 )
346 local hash = extos.crypt(password, hash_prefix)
348 if not hash or hash:sub(1, #hash_prefix) ~= hash_prefix then
349 error("Password hashing algorithm failed")
350 end
352 self.password = hash
353 self.password_reset_secret = nil
354 self.password_reset_secret_expiry = nil
355 end
357 function Member.object:check_password(password)
358 if type(password) == "string" and type(self.password) == "string" then
359 return extos.crypt(password, self.password) == self.password
360 else
361 return false
362 end
363 end
365 function Member.object_get:password_hash_needs_update()
367 if self.password == nil then
368 return nil
369 end
371 local function check_rounds(rounds)
372 if rounds then
373 rounds = tonumber(rounds)
374 if
375 rounds >= config.password_hash_min_rounds and
376 rounds <= config.password_hash_max_rounds
377 then
378 return false
379 end
380 end
381 return true
382 end
384 if config.password_hash_algorithm == "crypt_md5" then
386 return self.password:sub(1,3) ~= "$1$"
388 elseif config.password_hash_algorithm == "crypt_sha256" then
390 return check_rounds(self.password:match("^%$5%$rounds=([1-9][0-9]*)%$"))
392 elseif config.password_hash_algorithm == "crypt_sha512" then
394 return check_rounds(self.password:match("^%$6%$rounds=([1-9][0-9]*)%$"))
396 else
397 error("Unknown hash algorithm selected in configuration")
399 end
401 end
403 function Member.object_get:published_contacts()
404 return Member:new_selector()
405 :join('"contact"', nil, '"contact"."other_member_id" = "member"."id"')
406 :add_where{ '"contact"."member_id" = ?', self.id }
407 :add_where("public")
408 :exec()
409 end
411 function Member:by_login_and_password(login, password)
413 local function prepare_login_selector()
414 local selector = self:new_selector()
415 selector:add_field({ "now() > COALESCE(last_delegation_check, activated) + ?::interval", config.check_delegations_interval_hard }, "needs_delegation_check_hard")
416 selector:optional_object_mode()
417 return selector
418 end
420 local function do_local_login()
421 local selector = prepare_login_selector()
422 selector:add_where{'"login" = ?', login }
423 local member = selector:exec()
424 if member and member:check_password(password) then
425 return member
426 else
427 return nil
428 end
429 end
431 if config.ldap.member then
433 -- Let's check the users credentials against the LDAP
434 local ldap_entry, ldap_err = ldap.check_credentials(login, password)
436 -- Is the user already registered as member?
437 local uid
438 local selector = prepare_login_selector()
440 -- Get login name from LDAP entry
441 if ldap_entry then
442 uid = config.ldap.member.uid_map(ldap_entry)
443 selector:add_where{'"authority" = ? AND "authority_uid" = ?', "ldap", uid }
445 -- or build it from the login
446 else
447 login = config.ldap.member.login_normalizer(login)
448 selector:add_where{'"authority" = ? AND "authority_uid" = ?', "ldap", login }
449 end
451 local member = selector:exec()
452 -- The member is already registered
453 if member then
455 -- The credentials entered by the user are invalid
456 if ldap_err == "invalid_credentials" then
458 -- Check if the user tried a cached password (which is invalid now)
459 if config.ldap.member.cache_passwords and member:check_password(password) then
460 member.password = nil
461 member:save()
462 end
464 -- Try a regular login
465 return do_local_login()
467 end
469 -- The credentials were accepted by the LDAP server and no error occured
470 if ldap_entry and not ldap_err then
472 -- Cache the password (if feature enabled)
473 if config.ldap.member.cache_passwords and not member:check_password(password) then
474 member:set_password(password)
475 end
477 -- update the member attributes and privileges from LDAP
478 local ldap_conn, ldap_entry, err, err2 = ldap.update_member_attr(member, nil, uid)
479 if not err then
480 ldap.update_member_allowed(member, ldap_entry)
481 local err = member:try_save()
482 if err then
483 return nil, "member_save_error", err
484 end
485 if member.locked then
486 return nil, "member_locked"
487 end
488 local succes, err, err2 = ldap.update_member_privileges(member, ldap_entry)
489 if err then
490 return nil, "update_member_privileges_error", err, err2
491 end
492 return member
493 end
495 end
497 -- Some kind of LDAP error happened, if cached password are enabled,
498 -- check user credentials against the cache
499 if config.ldap.member.cache_passwords and member:check_password(password) then
501 -- return the successfully logged in member
502 return member
504 end
506 -- The member is not registered
507 elseif config.ldap.member.registration and ldap_entry and not ldap_err then
508 -- Automatic registration ("auto")
509 if config.ldap.member.registration == "auto" then
510 member = Member:new()
511 member.authority = "ldap"
512 local ldap_login
513 if config.ldap.member.cache_passwords then
514 if config.ldap.member.login_normalizer then
515 ldap_login = config.ldap.member.login_normalizer(login)
516 else
517 ldap_login = login
518 end
519 end
520 -- TODO change this when SQL layers supports hstore
521 member.authority_uid = uid
522 member.authority_login = ldap_login
523 member.activated = "now"
524 member.last_activity = "now"
525 if config.ldap.member.cache_passwords then
526 member:set_password(password)
527 end
528 local ldap_conn, ldap_entry, err, err2 = ldap.update_member_attr(member, nil, uid)
529 if not err then
530 ldap.update_member_allowed(member, ldap_entry)
531 if member.locked then
532 return nil, "member_not_allowed"
533 end
534 local err = member:try_save()
535 if err then
536 return nil, "member_save_error", err
537 end
538 local success, err, err2 = ldap.update_member_privileges(member, ldap_entry)
539 if err then
540 return nil, "update_member_privileges_error", err, err2
541 end
542 return member
543 end
545 -- No automatic registration
546 else
547 return nil, "ldap_credentials_valid_but_no_member", uid
548 end
549 end
551 end
553 return do_local_login()
555 end
557 function Member:by_ids(ids)
558 local selector = self:new_selector()
559 selector:add_where{'"id" IN ($)', { ids } }
560 return selector:exec()
561 end
563 function Member:by_login(login)
564 local selector = self:new_selector()
565 selector:add_where{'"login" = ?', login }
566 selector:optional_object_mode()
567 return selector:exec()
568 end
570 function Member:by_name(name)
571 local selector = self:new_selector()
572 selector:add_where{'"name" = ?', name }
573 selector:optional_object_mode()
574 return selector:exec()
575 end
577 function Member:get_search_selector(search_string)
578 return self:new_selector()
579 :add_field( {'"highlight"("member"."name", ?)', search_string }, "name_highlighted")
580 :add_where{ 'to_tsvector("member") @@ "plainto_tsquery"(?)', search_string }
581 :add_where("activated NOTNULL AND active")
582 end
584 function Member.object:send_password_reset_mail()
585 trace.disable()
586 if not self.notify_email then
587 return false
588 end
589 self.password_reset_secret = secret_token()
590 local expiry = db:query("SELECT now() + '1 days'::interval as expiry", "object").expiry
591 self.password_reset_secret_expiry = expiry
592 self:save()
593 local content = slot.use_temporary(function()
594 slot.put(_"Hello " .. self.name .. ",\n\n")
595 slot.put(_"to reset your password please click on the following link:\n\n")
596 slot.put(request.get_absolute_baseurl() .. "index/reset_password.html?secret=" .. self.password_reset_secret .. "\n\n")
597 slot.put(_"If this link is not working, please open following url in your web browser:\n\n")
598 slot.put(request.get_absolute_baseurl() .. "index/reset_password.html\n\n")
599 slot.put(_"On that page please enter the reset code:\n\n")
600 slot.put(self.password_reset_secret .. "\n\n")
601 end)
602 local success = net.send_mail{
603 envelope_from = config.mail_envelope_from,
604 from = config.mail_from,
605 reply_to = config.mail_reply_to,
606 to = self.notify_email,
607 subject = config.mail_subject_prefix .. _"Password reset request",
608 content_type = "text/plain; charset=UTF-8",
609 content = content
610 }
611 return success
612 end
614 function Member.object:send_invitation(template_file, subject)
615 trace.disable()
616 self.invite_code = secret_token()
617 self:save()
619 local subject = subject
620 local content
621 local baseurl = request.get_absolute_baseurl() .. "index/register.html"
622 local url = baseurl .. "?skip=1&code=" .. self.invite_code
623 if template_file then
624 local fh = io.open(template_file, "r")
625 content = fh:read("*a")
626 content = content:gsub("#{url}", url):gsub("#{baseurl}", baseurl):gsub("#{code}", self.invite_code)
627 elseif config.invitation_mail then
628 subject = config.invitation_mail.subject
629 content = config.invitation_mail.content:gsub("#{url}", url):gsub("#{baseurl}", baseurl):gsub("#{code}", self.invite_code)
630 else
631 subject = config.mail_subject_prefix .. _"Invitation to LiquidFeedback"
632 content = slot.use_temporary(function()
633 slot.put(_"Hello\n\n")
634 slot.put(_"You are invited to LiquidFeedback. To register please click the following link:\n\n")
635 slot.put(url .. "\n\n")
636 slot.put(_"If this link is not working, please open following url in your web browser:\n\n")
637 slot.put(baseurl .. "\n\n")
638 slot.put(_"On that page please enter the invite key:\n\n")
639 slot.put(self.invite_code .. "\n\n")
640 end)
641 end
643 local success = net.send_mail{
644 envelope_from = config.mail_envelope_from,
645 from = config.mail_from,
646 reply_to = config.mail_reply_to,
647 to = self.notify_email_unconfirmed or self.notify_email,
648 subject = subject,
649 content_type = "text/plain; charset=UTF-8",
650 content = content
651 }
652 return success
653 end
655 function Member.object:set_notify_email(notify_email)
656 trace.disable()
657 local expiry = db:query("SELECT now() + '7 days'::interval as expiry", "object").expiry
658 self.notify_email_unconfirmed = notify_email
659 self.notify_email_secret = secret_token()
660 self.notify_email_secret_expiry = expiry
661 local content = slot.use_temporary(function()
662 slot.put(_"Hello " .. self.name .. ",\n\n")
663 slot.put(_"Please confirm your email address by clicking the following link:\n\n")
664 slot.put(request.get_absolute_baseurl() .. "index/confirm_notify_email.html?secret=" .. self.notify_email_secret .. "\n\n")
665 slot.put(_"If this link is not working, please open following url in your web browser:\n\n")
666 slot.put(request.get_absolute_baseurl() .. "index/confirm_notify_email.html\n\n")
667 slot.put(_"On that page please enter the confirmation code:\n\n")
668 slot.put(self.notify_email_secret .. "\n\n")
669 end)
670 local success = net.send_mail{
671 envelope_from = config.mail_envelope_from,
672 from = config.mail_from,
673 reply_to = config.mail_reply_to,
674 to = self.notify_email_unconfirmed,
675 subject = config.mail_subject_prefix .. _"Email confirmation request",
676 content_type = "text/plain; charset=UTF-8",
677 content = content
678 }
679 if success then
680 local lock_expiry = db:query("SELECT now() + '1 hour'::interval AS lock_expiry", "object").lock_expiry
681 self.notify_email_lock_expiry = lock_expiry
682 end
683 self:save()
684 return success
685 end
687 function Member.object:get_setting(key)
688 return Setting:by_pk(self.id, key)
689 end
691 function Member.object:get_setting_value(key)
692 local setting = Setting:by_pk(self.id, key)
693 if setting then
694 return setting.value
695 end
696 end
698 function Member.object:set_setting(key, value)
699 local setting = self:get_setting(key)
700 if not setting then
701 setting = Setting:new()
702 setting.member_id = self.id
703 setting.key = key
704 end
705 setting.value = value
706 setting:save()
707 end
709 function Member.object:get_setting_maps_by_key(key)
710 return SettingMap:new_selector()
711 :add_where{ "member_id = ?", self.id }
712 :add_where{ "key = ?", key }
713 :add_order_by("subkey")
714 :exec()
715 end
717 function Member.object:get_setting_map_by_key_and_subkey(key, subkey)
718 return SettingMap:new_selector()
719 :add_where{ "member_id = ?", self.id }
720 :add_where{ "key = ?", key }
721 :add_where{ "subkey = ?", subkey }
722 :add_order_by("subkey")
723 :optional_object_mode()
724 :exec()
725 end
727 function Member.object:set_setting_map(key, subkey, value)
728 setting_map = self:get_setting_map_by_key_and_subkey(key, subkey)
729 if not setting_map then
730 setting_map = SettingMap:new()
731 setting_map.member_id = self.id
732 setting_map.key = key
733 setting_map.subkey = subkey
734 end
735 setting_map.value = value
736 setting_map:save()
737 end
739 function Member.object_get:notify_email_locked()
740 return(
741 Member:new_selector()
742 :add_where{ "id = ?", app.session.member.id }
743 :add_where("notify_email_lock_expiry > now()")
744 :count() == 1
745 )
746 end
748 function Member.object_get:units_with_voting_right()
749 return(Unit:new_selector()
750 :join("privilege", nil, { "privilege.unit_id = unit.id AND privilege.member_id = ? AND privilege.voting_right", self.id })
751 :exec()
752 )
753 end
755 function Member.object:ui_field_text(args)
756 args = args or {}
757 if app.session:has_access("authors_pseudonymous") then
758 -- ugly workaround for getting html into a replaced string and to the user
759 ui.container{label = args.label, label_attr={class="ui_field_label"}, content = function()
760 slot.put(string.format('<span><a href="%s">%s</a></span>',
761 encode.url{
762 module = "member",
763 view = "show",
764 id = self.id,
765 },
766 encode.html(self.name)))
767 end
768 }
769 else
770 ui.field.text{ label = args.label, value = _"[not displayed public]" }
771 end
772 end
774 local function populate_units_with_initiative_right_hash(self)
775 if not self.__units_with_initiative_right_hash then
776 local privileges = Privilege:new_selector()
777 :add_where{ "member_id = ?", self.id }
778 :add_where("initiative_right")
779 :exec()
780 self.__units_with_initiative_right_hash = {}
781 for i, privilege in ipairs(privileges) do
782 self.__units_with_initiative_right_hash[privilege.unit_id] = true
783 end
784 end
785 end
787 function Member.object:has_initiative_right_for_unit_id(unit_id)
788 populate_units_with_initiative_right_hash(self)
789 return self.__units_with_initiative_right_hash[unit_id] and true or false
790 end
792 function Member.object_get:has_initiative_right()
793 populate_units_with_initiative_right_hash(self)
794 for k, v in pairs(self.__units_with_initiative_right_hash) do
795 return true
796 end
797 return false
798 end
800 local function populate_units_with_voting_right_hash(self)
801 if not self.__units_with_voting_right_hash then
802 local privileges = Privilege:new_selector()
803 :add_where{ "member_id = ?", self.id }
804 :add_where("voting_right")
805 :exec()
806 self.__units_with_voting_right_hash = {}
807 for i, privilege in ipairs(privileges) do
808 self.__units_with_voting_right_hash[privilege.unit_id] = true
809 end
810 end
811 end
813 function Member.object_get:has_voting_right()
814 populate_units_with_voting_right_hash(self)
815 for k, v in pairs(self.__units_with_voting_right_hash) do
816 return true
817 end
818 return false
819 end
821 function Member.object:has_voting_right_for_unit_id(unit_id)
822 populate_units_with_voting_right_hash(self)
823 return self.__units_with_voting_right_hash[unit_id] and true or false
824 end
826 function Member.object:has_polling_right_for_unit_id(unit_id)
827 if not self.__units_with_polling_right_hash then
828 local privileges = Privilege:new_selector()
829 :add_where{ "member_id = ?", self.id }
830 :add_where("polling_right")
831 :exec()
832 self.__units_with_polling_right_hash = {}
833 for i, privilege in ipairs(privileges) do
834 self.__units_with_polling_right_hash[privilege.unit_id] = true
835 end
836 end
837 return self.__units_with_polling_right_hash[unit_id] and true or false
838 end
840 function Member.object:get_single_unit_id()
841 populate_units_with_voting_right_hash(self)
842 local single_unit_id
843 local count = 0
844 for unit_id in pairs(self.__units_with_voting_right_hash) do
845 single_unit_id = unit_id
846 count = count + 1
847 end
848 if count == 1 then
849 return single_unit_id
850 end
851 end
853 function Member.object:get_delegatee_member(unit_id, area_id, issue_id)
854 local selector = Member:new_selector()
855 if unit_id then
856 selector:join("delegation", nil, { "delegation.trustee_id = member.id AND delegation.scope = 'unit' AND delegation.unit_id = ? AND delegation.truster_id = ?", unit_id, self.id })
857 end
858 selector:optional_object_mode()
859 return selector:exec()
860 end
862 function Member.object:has_role(role)
863 self:load("units")
864 for i, unit in ipairs(self.units) do
865 if unit.attr.role == role then
866 return true
867 end
868 end
869 return false
870 end
872 function Member.object:delete()
873 db:query{ "SELECT delete_member(?)", self.id }
874 end
876 function Member.object_get:display_name()
877 if self.identification then
878 return self.identification
879 elseif self.name then
880 return self.name
881 else
882 return "Member #" .. self.id
883 end
884 end

Impressum / About Us