File Coverage

blib/lib/Elive/Entity/ParticipantList.pm
Criterion Covered Total %
statement 28 30 93.3
branch n/a
condition n/a
subroutine 10 10 100.0
pod n/a
total 38 40 95.0


line stmt bran cond sub pod time code
1             package Elive::Entity::ParticipantList;
2 3     3   1703 use warnings; use strict;
  3     3   5  
  3         113  
  3         12  
  3         5  
  3         81  
3              
4 3     3   11 use Mouse;
  3         3  
  3         19  
5 3     3   859 use Mouse::Util::TypeConstraints;
  3         5  
  3         17  
6              
7             extends 'Elive::Entity';
8              
9 3     3   1239 use Elive::Entity::Participant;
  3         9  
  3         131  
10 3     3   1099 use Elive::Entity::Participants;
  3         6  
  3         82  
11 3     3   211 use Elive::Entity::User;
  3         4  
  3         61  
12 3     3   12 use Elive::Entity::Group;
  3         4  
  3         72  
13 3     3   10 use Elive::Entity::Role;
  3         3  
  3         38  
14 3     3   1133 use Elive::Entity::Meeting;
  0            
  0            
15             use Elive::Entity::InvitedGuest;
16             use Elive::Util;
17              
18             use Carp;
19              
20             __PACKAGE__->entity_name('ParticipantList');
21              
22             coerce 'Elive::Entity::ParticipantList' => from 'HashRef'
23             => via {Elive::Entity::ParticipantList->new($_) };
24              
25             has 'meetingId' => (is => 'rw', isa => 'Int', required => 1);
26             __PACKAGE__->primary_key('meetingId');
27             __PACKAGE__->params(users => 'Str');
28              
29             has 'participants' => (is => 'rw', isa => 'Elive::Entity::Participants', coerce => 1);
30             __PACKAGE__->_alias(users => 'participants', freeze => 1);
31              
32             =head1 NAME
33              
34             Elive::Entity::ParticipantList - Meeting Participants entity class
35              
36             =head1 DESCRIPTION
37              
38             This is the entity class for meeting participants.
39              
40             The participants property is an array of type Elive::Entity::Participant.
41              
42             Note: the C and C methods are depreciated. For alternatives,
43             please see L.
44              
45             =head2 User Participants
46              
47             The I property may be specified in the format: userId[=roleId],
48             where the role is 3 for a normal participant or 2 for a meeting moderator.
49              
50             Participants may be specified as a ';' separated string:
51              
52             my $participant_list = $meeting->participant_list;
53              
54             $participant_list->participants('111111=2;222222');
55             $participant_list->update;
56              
57             Participants may also be specified as an array of scalars:
58              
59             $participant_list->participants(['111111=2', 222222]);
60             $participant_list->update;
61              
62             Or an array of hashrefs:
63              
64             $participant_list->participants([{user => 111111, role => 2},
65             {user => 222222}]);
66             $participant_list->update;
67              
68             =head2 Groups of Participants
69              
70             Groups of users may also be assigned to a meeting. All users that are member of
71             that group are then able to participate in the meeting, and are assigned the
72             given role.
73              
74             By convention, a leading '*' indicates a group:
75              
76             #
77             # Set alice and bob as moderators. Then add all students in the
78             # cookery class:
79             #
80             $participant_list->participants('alice=2;bob=2;*cookery_class=3');
81             $participant_list->update;
82              
83             Similar to the above:
84              
85             $participant_list->participants(['alice=2', 'bob=2', '*cookery_class=3']);
86             $participant_list->update;
87              
88             As a list of hashrefs:
89              
90             $participant_list->participants([
91             {user => 'alice', role => 2},
92             {user => 'bob', role => 2},
93             {group => 'cookery_class', role => 3},
94             ]);
95             $participant_list->update;
96              
97             =head2 Command Selection
98              
99             By default this command uses the C SOAP command, which
100             doesn't handle groups. If any groups are specified, it will switch to using
101             C, which does handle groups.
102              
103             =cut
104              
105             =head1 METHODS
106              
107             =cut
108              
109             sub _retrieve_all {
110             my ($class, $vals, %opt) = @_;
111              
112             #
113             # No getXxxx command use listXxxx
114             #
115             return $class->SUPER::_retrieve_all($vals,
116             command => 'listParticipants',
117             %opt);
118             }
119              
120             =head2 retrieve
121              
122             my $meeting_participants = Elive::Entity::ParticipantList->retrieve($meeting_id);
123              
124             Retrieves the participant list for a meeting.
125              
126             =head2 update
127              
128             This method updates meeting participants.
129              
130             my $participant_list
131             = Elive::Entity::ParticipantList->retrieve($meeting_id);
132             $participant_list->participants->add($alice->userId, $bob->userId);
133             $participant_list->update;
134              
135             Note:
136              
137             =over 4
138              
139             =item if you specify an empty list, C method will be called. The
140             resultant list wont be empty, but will have the moderator as the sole
141             participant.
142              
143             =back
144              
145             =cut
146              
147             sub update {
148             my ($self, $update_data, %opt) = @_;
149              
150             if (defined $update_data) {
151              
152             die 'usage: $obj->update( \%data, %opt )'
153             unless (Elive::Util::_reftype($update_data) eq 'HASH');
154              
155             $self->set( %$update_data )
156             if (keys %$update_data);
157             }
158              
159             my $meeting_id = $self->meetingId
160             or die "unable to get meetingId";
161              
162             my $meeting = Elive::Entity::Meeting
163             ->retrieve($meeting_id,
164             reuse => 1,
165             connection => $self->connection,
166             ) or die "meeting not found: ".$meeting_id;
167              
168             my ($users, $groups, $guests) = $self->participants->_group_by_type;
169              
170             $self->_build_elm2x_participants ($users, $groups, $guests);
171             #
172             # make sure that the facilitator is included with a moderator role
173             #
174             $users->{ $meeting->facilitatorId } = ${Elive::Entity::Role::MODERATOR};
175              
176             my $participants = $self->_set_participant_list( $users, $groups, $guests );
177             #
178             # do our own readback and reread from the database (The setParticipantList
179             # command doesn't return a response object.)
180             #
181             $self->revert;
182             my $class = ref($self);
183             $self = $class->retrieve($self->id, connection => $self->connection);
184              
185             $class->_readback_check({meetingId => $self->meetingId,
186             participants => $participants},
187             [$self]);
188              
189             return $self;
190             }
191              
192             #
193             # _readback_check reimplementation of our validation rule
194             # -- warn when input participants are omitted
195             # -- error when output contains participants that weren't in the input
196             #
197              
198             sub _readback_check {
199             my $class = shift;
200             my $_data = shift;
201             my $rows = shift;
202             my %opt = @_;
203              
204             my %data = %{ $_data };
205              
206             if (my $participants = delete $data{participants}) {
207              
208             my $participants_in = Elive::Entity::Participants->new( $participants );
209             my %requested = map {lc Elive::Entity::Participant->stringify($_) => 1} @$participants_in;
210             my $requested_count = scalar keys %requested;
211              
212             foreach my $row (@$rows) {
213              
214             if ($participants = $row->{participants}) {
215             my $participants_out = Elive::Entity::Participants->new( $participants );
216             my %actual = map {lc Elive::Entity::Participant->stringify($_) => 1} @$participants_out;
217             my %rejected = %requested;
218             delete @rejected{ keys %actual };
219              
220             my @rejected_participants = sort keys %rejected;
221             my $rejected_count = scalar @rejected_participants;
222              
223             Carp::croak "unable to add $rejected_count of $requested_count participants; rejected participants: @rejected_participants"
224             if $rejected_count;
225              
226             my %extra = %actual;
227             delete @extra{ keys %requested };
228             my @extra_participants = sort keys %extra;
229             my $extra_count = scalar @extra_participants;
230              
231             Carp::croak "found $extra_count extra participants: @extra_participants"
232             if $extra_count;
233              
234             }
235             }
236             }
237              
238             $class->SUPER::_readback_check( \%data, $rows, %opt);
239             }
240              
241             sub _build_elm2x_participants {
242             my ($self, $users, $groups, $guests) = @_;
243             #
244             # Take our best shot at passing participants via the elm 2.x
245             # setParticipantList and updateParticipantList commands. These have
246             # some restrictions on the handling of groups and invited guests.
247             #
248             #
249             if (keys %$guests) {
250             # no can do invited guests
251             carp join(' ', "ignoring invited guests:", sort keys %$guests);
252             %$guests = ();
253             }
254              
255             foreach my $group_spec (keys %$groups) {
256             #
257             # Best we can do with groups is to expand members
258             #
259             carp "client side expansion of group: $group_spec";
260             my $role = delete $groups->{ $group_spec };
261             (my $group_id = $group_spec) =~ s{^\*}{};
262              
263             my $group = Elive::Entity::Group->retrieve($group_id,
264             connection => $self->connection,
265             reuse => 1,
266             );
267              
268             my @members = $group->expand_members;
269              
270             foreach (@members) {
271             #
272             # member names may be in the format :userId
273             #
274             my $member = $_;
275             $member =~ s{^ [^:]* :}{}x;
276             $users->{ $member } ||= $role;
277             }
278             }
279             }
280              
281             sub _set_participant_list {
282             my $self = shift;
283             my $users = shift;
284             my $groups = shift;
285             my $guests = shift;
286              
287             my $som;
288              
289             my @participants;
290              
291             foreach (keys %$users) {
292             push(@participants, Elive::Entity::Participant->new({user => $_, role => $users->{$_}, type => ${Elive::Entity::Participant::TYPE_USER}}) )
293             }
294              
295             foreach (keys %$groups) {
296             push(@participants, Elive::Entity::Participant->new({group => $_, role => $groups->{$_}, type => ${Elive::Entity::Participant::TYPE_GROUP}}) )
297             }
298              
299             foreach (keys %$guests) {
300             push(@participants, Elive::Entity::Participant->new({guest => $_, role => $guests->{$_}, type => ${Elive::Entity::Participant::TYPE_GUEST}}) )
301             }
302              
303             my %params;
304             $params{meetingId} = $self;
305             $params{participants} = \@participants;
306             $som = $self->connection->call('setParticipantList' => %{$self->_freeze(\%params)});
307             $self->connection->_check_for_errors( $som );
308              
309             return \@participants;
310             }
311              
312             =head2 reset
313              
314             $participant_list->reset
315              
316             Reset the participant list. This will set the meeting facilitator as
317             the only participant, with a role of 2 (moderator).
318              
319             =cut
320              
321             sub reset {
322             my ($self, %opt) = @_;
323             return $self->update({participants => []}, %opt);
324             }
325              
326             =head2 insert
327            
328             my $participant_list = Elive::Entity::ParticipantList->insert({
329             meetingId => $meeting_id,
330             participants => '111111=2;33333'
331             });
332              
333             Note that if you empty the participant list, C will be called.
334              
335             =cut
336              
337             sub insert {
338             my ($class, $data, %opt) = @_;
339              
340             my $meeting_id = delete $data->{meetingId}
341             or die "can't insert participant list without meetingId";
342             my $self = $class->retrieve($meeting_id, reuse => 1)
343             or die "No such meeting: $meeting_id";
344              
345             $self->update($data, %opt);
346              
347             return $self;
348             }
349              
350             =head2 list
351              
352             The list method is not available for participant lists.
353              
354             =cut
355              
356             sub list {return shift->_not_available}
357              
358             sub _thaw {
359             my ($class, $db_data, @args) = @_;
360              
361             $db_data = Elive::Util::_clone( $db_data ); # take a copy
362             #
363             # the SOAP record has a Participant property that can either
364             # be of type user or group. However, Elive has separate 'user'
365             # and 'group' properties. Resolve here.
366             #
367             if ($db_data->{ParticipantListAdapter}) {
368             if (my $participants = $db_data->{ParticipantListAdapter}{Participants}) {
369             $participants = [$participants]
370             unless Elive::Util::_reftype($participants) eq 'ARRAY';
371              
372             foreach (@$participants) {
373             my $p = $_->{ParticipantAdapter};
374             if ($p && $p->{Participant}) {
375             #
376             # peek at the the type property. 0 => user, 1 => group
377             # a group record, rename to Group, otherwise treat
378             #
379             if (!defined $p->{Type} || $p->{Type} == ${Elive::Entity::Participant::TYPE_USER}) {
380             $p->{User} = delete $p->{Participant}
381             }
382             elsif ($p->{Type} == ${Elive::Entity::Participant::TYPE_GROUP}) {
383             $p->{Group} = delete $p->{Participant}
384             }
385             elsif ($p->{Type} == ${Elive::Entity::Participant::TYPE_GUEST}) {
386             $p->{Guest} = delete $p->{Participant}
387             }
388             else {
389             warn "unknown participant type: $p->{Type}";
390             }
391             }
392             }
393             }
394             }
395              
396             return $class->SUPER::_thaw($db_data, @args);
397             }
398              
399             =head1 SEE ALSO
400              
401             L
402              
403             L
404              
405             L
406              
407             L
408              
409             =cut
410              
411             1;