initial commit
[CPE_learningsite] / CPE / CPE.App / CPE.App.Web / Controllers / RebroadcastController.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Configuration;
4 using System.Linq;
5 using System.Net;
6 using System.Web;
7 using System.Web.Mvc;
8 using CPE.App.Web.Code;
9 using CPE.App.Web.Connect;
10 using CPE.App.Web.Helpers;
11 using CPE.App.Web.Models;
12
13 namespace CPE.App.Web.Controllers {
14     public class RebroadcastController : BaseController {
15         [HttpGet]
16         public ActionResult RebroadcastSchedule(int meetingSessionKey) {
17             var meetingSession = Database.MeetingSessions.Single(ms => ms.MeetingSessionKey == meetingSessionKey);
18             return View(meetingSession);
19         }
20
21         [HttpGet]
22         public ActionResult RebroadcastSessions(int meetingSessionKey) {
23             var meetingScoId = Database.MeetingSessions.Single(ms => ms.MeetingSessionKey == meetingSessionKey)
24                                        .SCO_ID;
25             var recordingSessions =
26                 Database.RecordingSessions.Where(rs => rs.MeetingSCO_ID == meetingScoId)
27                         .OrderByDescending(rs => rs.StartTime)
28                         .ToList();
29
30             return PartialView(recordingSessions);
31         }
32
33         [HttpGet]
34         public ActionResult RebroadcastPauses(int recordingKey) {
35             var recording = Database.RecordingSessions.Single(r => r.RecordingKey == recordingKey);
36             var meeting = Database.MeetingSessions.Single(ms => ms.MeetingSessionKey == recording.MeetingSessionKey);
37             var recordingPauses = new RecordingPauses {
38                 Session = meeting,
39                 SessionPauses = meeting.MeetingSessionPauses.ToList()
40             };
41
42             return View(recordingPauses);
43         }
44
45         [HttpPost]
46         public JsonResult DeletePause(int pauseKey) {
47             var result = false;
48             try {
49                 var pause = Database.MeetingSessionPauses.Single(p => p.MesstingSessionPauseKey == pauseKey);
50                 Database.MeetingSessionPauses.DeleteOnSubmit(pause);
51                 Database.SubmitChanges();
52                 result = true;
53             } catch (Exception ex) {
54                 result = false;
55             }
56             return Json(result);
57         }
58
59         [HttpPost]
60         public JsonResult UpdatePauses(MeetingSessionPause[] pauses) {
61             var result = false;
62             try {
63                 foreach (var meetingSessionPause in pauses) {
64                     var dbPause =
65                         Database.MeetingSessionPauses.Single(
66                                                              msp => msp.MesstingSessionPauseKey == meetingSessionPause.MesstingSessionPauseKey);
67                     dbPause.StartDate = meetingSessionPause.StartDate.ToUniversalTime();
68                     dbPause.EndDate = meetingSessionPause.EndDate.Value.ToUniversalTime();
69                 }
70                 Database.SubmitChanges();
71                 result = true;
72             } catch (Exception ex) {
73                 result = false;
74             }
75             return Json(result);
76         }
77
78         [HttpGet]
79         public ActionResult ViewRecording(string meetingUrl, string d = null, string c = null) {
80             meetingUrl = meetingUrl?.Trim();
81             d = d?.Trim();
82             c = c?.Trim();
83
84             Extensions.LogServiceCall("[RebroadcastController][ViewRecording]", string.Format("meetingUrl = {0} d (PurchaseDate) = {1} c (TicketFromUrl) = {2}", meetingUrl, d, c));
85
86             if(meetingUrl.ToLower() == "test_room") {
87                 var baseUrl = ConfigurationManager.AppSettings["Connect.Url"];
88                 var testUrl = ConfigurationManager.AppSettings["Connect.AccessTestMeetingUrl"];
89                 var accessTestUrl = baseUrl + '/' + testUrl;
90                 return Redirect(accessTestUrl);
91             }
92
93             var meeting = AdobeMeetingConnection.getAdobeMeeting(meetingUrl);
94             if(meeting != null) {
95                 var sco_id = int.Parse(meeting.SelectSingleNode("//sco")
96                                               .Attributes["sco-id"].Value);
97                 DateTime? startDate = null;
98                 DateTime? endDate = null;
99                 string msg = "";
100                 bool isActive = false;
101                 var recordingSession = getActiveRecordingSession(sco_id);
102                 //this now returns a recording that starts within an hour, instead of now
103                 if(recordingSession == null) {
104                     recordingSession = getNextRecordingSession(sco_id);
105                     //this now returns the next recording that starts more than an hour from now
106                     if(recordingSession != null) {
107                         msg =
108                             "This webcast session is not currently available.  The next scheduled session is: ";
109                         startDate = recordingSession.StartTime;
110                         endDate = recordingSession.EndTime;
111                     } else {
112                         msg = "There are no webcasts scheduled for this session";
113                     }
114                 } else {
115                     isActive = true;
116                     startDate = recordingSession.StartTime;
117                     endDate = recordingSession.EndTime;
118                 }
119
120                 RestoreModelState();
121                 var now = DateTime.UtcNow;
122                 return View("recordinglogin",
123                             new RecordingView {
124                                 Name = meeting.SelectSingleNode("//sco/name")
125                                               .InnerText,
126                                 HasPassCode = recordingSession != null && recordingSession.Passcode != null,
127                                 Url = meetingUrl,
128                                 IsActive = isActive,
129                                 Start = startDate,
130                                 End = endDate,
131                                 Message = msg,
132                                 TicketFromUrl = c,
133                                 PurchaseDate = d
134                             });
135             }
136             return null;
137         }
138
139         [HttpPost]
140         public ActionResult ViewRecordingLogin(string meetingUrl, string firstname, string lastname, string email,
141                                                string ticket, string passcode, string ticketFromUrl, string purchaseDate) {
142             meetingUrl = meetingUrl?.Trim();
143             firstname = firstname?.Trim();
144             lastname = lastname?.Trim();
145             email = email?.Trim();
146             passcode = passcode?.Trim();
147             ticket = ticket?.Trim();
148             ticketFromUrl = ticketFromUrl?.Trim();
149             purchaseDate = purchaseDate?.Trim();
150
151             Extensions.LogServiceCall("[RebroadcastController][ViewRecordingLogin]", string.Format("email = {0} meetingUrl = {1} ticket = {2} ticketFromUrl = {3} purchaseDate = {4} firstname = {5} lastname = {6} passcode = {7}", email, meetingUrl, ticket, ticketFromUrl, purchaseDate, firstname, lastname, passcode));
152
153             var loginInfo = new loginInfo {
154                 meetingUrl = meetingUrl,
155                 firstname = firstname,
156                 lastname = lastname,
157                 email = email,
158                 ticket = ticket,
159                 passcode = passcode,
160                 ticketFromUrl = ticketFromUrl,
161                 purchaseDate = purchaseDate,
162                 meetingSessionKey = 0
163             };
164
165             var magicTicketLogin = string.IsNullOrEmpty(ticketFromUrl);
166
167             // I commented out the check of CPE.AdobeTicketingOn both because I didn't understand it and it complicated adding the else clause I added.
168             //if ((ConfigurationManager.AppSettings["CPE.AdobeTicketingOn"].ToLower() == "true") & !magicTicketLogin)
169             if(!magicTicketLogin) {
170                 if(!ticketFromUrl.Equals(ticket)) {
171                     ModelState.AddModelError("ticket", "You have entered an invalid pass code.");
172                     SaveModelState();
173                     return RedirectToRoute("ViewRecording", new {meetingUrl});
174                 }
175
176                 if(!VerifyAccess.VerifyTicket(ticket, meetingUrl, firstname, lastname, email, purchaseDate)) {
177                     ModelState.AddModelError("ticket",
178                                              "Invalid Login. Please re-enter your information. If you continue to have trouble, please contact Customer Service at 1-800-544-1114 or email us at sswebcast@cpeincmail.com.");
179                     SaveModelState();
180                     return RedirectToRoute("ViewRecording", new {meetingUrl});
181                 }
182             } else {
183                 if((passcode == null) || (passcode == "")) {
184                     Extensions.LogServiceCall("[RebroadcastController][ViewRecordingLogin]", string.Format("passcode found Empty!  email = {0} meetingUrl = {1} ticket = {2} ticketFromUrl = {3} purchaseDate = {4} firstname = {5} lastname = {6} passcode = {7}", email, meetingUrl, ticket, ticketFromUrl, purchaseDate, firstname, lastname, passcode));
185
186                     ModelState.AddModelError("passcode", "You have entered an invalid pass code.");
187                     SaveModelState();
188                     return RedirectToRoute("ViewRecording", new {meetingUrl});
189                 }
190
191                 if(passcode != (DateTime.UtcNow.ToString("MMM") + DateTime.UtcNow.Day.ToString("00")).ToLower()) {
192                     Extensions.LogServiceCall("[RebroadcastController][ViewRecordingLogin]", string.Format("passcode found incorrect!  email = {0} meetingUrl = {1} ticket = {2} ticketFromUrl = {3} purchaseDate = {4} firstname = {5} lastname = {6} passcode = {7}", email, meetingUrl, ticket, ticketFromUrl, purchaseDate, firstname, lastname, passcode));
193
194                     ModelState.AddModelError("passcode", "You have entered an invalid pass code.");
195                     SaveModelState();
196                     return RedirectToRoute("ViewRecording", new {meetingUrl});
197                 }
198             }
199
200             try {
201                 var principalId = getConnectUser(loginInfo);
202                 saveParticipant(loginInfo, principalId);
203                 var certificateId = 0;
204
205                 var recordingId = addAdobeConnectPermissions(loginInfo, principalId);
206                 var recordingResult = getRecording(ref loginInfo, recordingId);
207
208                 if(!magicTicketLogin) {
209                     certificateId = savePurchaseInfo(loginInfo, purchaseDate, principalId, ticketFromUrl,
210                                                      loginInfo.meetingSessionKey);
211                 }
212
213                 switch (recordingResult) {
214                     case recordingResult.Now:
215                         return RedirectToAction("GotoContent",
216                                                 new {
217                                                     loginInfo.meetingSessionKey, email, certificateId
218                                                 });
219                     case recordingResult.Lobby:
220                         placeIntoLobby(loginInfo.meetingSessionKey, email, certificateId);
221                         break;
222                 }
223             } catch (Exception ex) {
224                 Extensions.LogServiceError("[RebroadcastController][ViewRecordingLogin]", ex);
225
226                 return RedirectToRoute("ViewRecording", new {meetingUrl});
227             }
228
229             return null;
230         }
231
232
233         [HttpGet]
234         public ActionResult LobbyWait() {
235             int meetingSessionKey = Convert.ToInt32(Request.QueryString["meetingSessionKey"]);
236             string email = Request.QueryString["email"];
237             int certificateId = Convert.ToInt32(Request.QueryString["certificateId"]);
238
239             var meeting = Database.MeetingSessions.FirstOrDefault(x => x.MeetingSessionKey == meetingSessionKey);
240             var lobbyData = new LobbyModel {
241                 LobbyDuration = (DateTime.UtcNow - meeting.StartDate).TotalMilliseconds,
242                 Email = email,
243                 meetingSessionKey = meetingSessionKey,
244                 CertificateId = certificateId
245             };
246             return View("lobby", lobbyData);
247         }
248
249
250         [HttpPost]
251         public JsonResult PublishRecording(int meetingSessionKey, DateTime startTime, DateTime endTime,
252                                            string surveyLink, string passcode) {
253
254             surveyLink = surveyLink?.Trim();
255             passcode = passcode?.Trim();
256
257             var result = new JsonDataResult {Success = false, Data = new {Url = "", Message = ""}};
258             //return this.Json(result);
259             var recordingsFolderId = ConfigurationManager.AppSettings["Connect.RecordingsFolderId"];
260             string recordingUrl = null;
261             var moveToContentLibrary = false;
262             var meeting = Database.MeetingSessions.Single(ms => ms.MeetingSessionKey == meetingSessionKey);
263             var admin = AdobeMeetingConnection.GetAdobeAdminSession();
264             var request = new Request(admin, "sco-contents");
265             request.Parameters.Add("sco-id", meeting.SCO_ID.ToString());
266             request.Parameters.Add("filter-icon", "archive");
267
268
269             if(request.Execute() && request.Status == Status.OK) {
270                 var recordings = request.XmlResults.GetElementsByTagName("sco");
271
272                 if(recordings.Count == 0) {
273                     //meeting room does not have any recordings present
274                     //look up recording in content library
275                     var contentRequest = new Request(admin, "sco-contents");
276                     contentRequest.Parameters.Add("sco-id", recordingsFolderId);
277                     contentRequest.Parameters.Add("filter-description", meeting.SCO_ID.ToString());
278
279                     if(contentRequest.Execute() && contentRequest.Status == Status.OK) {
280                         recordings = contentRequest.XmlResults.GetElementsByTagName("sco");
281                         moveToContentLibrary = false;
282                     } else {
283                         Console.WriteLine("wtf");
284                     }
285                 } else {
286                     //meeting room has recordings.  Need to move to content library.
287                     moveToContentLibrary = true;
288                 }
289
290                 if(recordings.Count > 0) {
291                     var recordingId = recordings[0].Attributes["sco-id"].Value;
292                     var recordingName = recordings[0].SelectSingleNode("name")
293                                                      .InnerText;
294                     recordingUrl = recordings[0].SelectSingleNode("url-path")
295                                                 .InnerText.Replace("/", "");
296                     var durationInSeconds = int.Parse(recordings[0].Attributes["duration"].Value);
297
298                     //create RecordingSession
299                     var recordingSession = new RecordingSession {
300                         MeetingSCO_ID = meeting.SCO_ID,
301                         RecordingSCO_ID = int.Parse(recordingId),
302                         StartTime = startTime.ToUniversalTime(),
303                         EndTime = endTime.ToUniversalTime(),
304                         Name = recordingName,
305                         RecordingUrl = recordingUrl,
306                         Duration = durationInSeconds,
307                         SurveyLink = Server.UrlDecode(surveyLink),
308                         Passcode = Server.UrlDecode(passcode)
309                     };
310                     Database.RecordingSessions.InsertOnSubmit(recordingSession);
311                     Database.SubmitChanges();
312
313                     var originalDate = meeting.StartDate.Date;
314                     var diffInDays = (startTime.ToUniversalTime()
315                                                .Date - originalDate).Days;
316                     var origStartTime = meeting.StartDate.TimeOfDay;
317                     var origOnRebroadcastDate = startTime.Date + origStartTime;
318                     var diffInMs = (startTime.ToUniversalTime() - origOnRebroadcastDate).TotalMilliseconds;
319
320                     var createResult = Database.CreateRebroadcastSession(recordingSession.RecordingSCO_ID,
321                                                                          meeting.SCO_ID,
322                                                                          meeting.OwnerPrincipal_ID, startTime.ToUniversalTime(), endTime.ToUniversalTime(), diffInMs,
323                                                                          diffInDays, recordingName);
324
325                     recordingSession.MeetingSessionKey = createResult.Single()
326                                                                      .Column1;
327                     Database.SubmitChanges();
328
329                     result.Success = true;
330                     result.Data = new {Url = recordingUrl, Message = ""};
331
332                     if(moveToContentLibrary) {
333                         var moveRequest = new Request(admin, "sco-move");
334                         moveRequest.Parameters.Add("folder-id", recordingsFolderId);
335                         moveRequest.Parameters.Add("sco-id", recordingId);
336
337                         if(moveRequest.Execute() && moveRequest.Status == Status.OK) {
338                             var updateRequest = new Request(admin, "sco-update");
339                             updateRequest.Parameters.Add("sco-id", recordingId);
340                             updateRequest.Parameters.Add("description", meeting.SCO_ID.ToString());
341                             if(updateRequest.Execute() && updateRequest.Status == Status.OK) {
342                                 result.Success = true;
343                                 result.Data = new {Url = recordingUrl, Message = ""};
344                             } else {
345                                 result.Success = false;
346                                 result.Data = new {Url = "", Message = "Unable to update recording description."};
347                             }
348                         } else {
349                             result.Success = false;
350                             result.Data =
351                                 new {
352                                     Url = "",
353                                     Message = "Could not move recording to content library.",
354                                     Error = moveRequest.Status.ToString()
355                                 };
356                         }
357                     }
358                 } else {
359                     result.Success = false;
360                     result.Data = new {Url = "", Message = "Meeting does not have any recordings."};
361                 }
362             } else {
363                 result.Success = false;
364                 result.Data = new {Url = "", Message = "Problem listing recordings.", Error = request.Status.ToString()};
365             }
366
367             return Json(result, JsonRequestBehavior.AllowGet);
368         }
369
370         [HttpGet]
371         public JsonResult DeleteRecording(int recordingSessionKey) {
372             var success = false;
373             var now = DateTime.UtcNow;
374             var recordingSession =
375                 Database.RecordingSessions.SingleOrDefault(rs => rs.RecordingKey == recordingSessionKey);
376
377             if(recordingSession != null) {
378                 if(recordingSession.MeetingSessionKey.HasValue) {
379                     var meeting =
380                         Database.MeetingSessions.Single(
381                                                         ms => ms.MeetingSessionKey == recordingSession.MeetingSessionKey.Value);
382                     if(meeting.StartDate > now) {
383                         Database.DeleteSession(meeting.MeetingSessionKey);
384                     }
385                 }
386
387                 if(recordingSession.StartTime > now) {
388                     Database.RecordingSessions.DeleteOnSubmit(recordingSession);
389                     Database.SubmitChanges();
390                     success = true;
391                 }
392             }
393             return Json(success, JsonRequestBehavior.AllowGet);
394         }
395
396         [HttpGet]
397         public JsonResult RebroadcastActive(string recordingUrl) {
398
399             recordingUrl = recordingUrl?.Trim();
400
401             var active = false;
402             var recording = AdobeMeetingConnection.getAdobeMeeting(recordingUrl);
403             if(recording != null) {
404                 var sco_id = int.Parse(recording.SelectSingleNode("//sco")
405                                                 .Attributes["sco-id"].Value);
406                 var recordingSession = getActiveRecordingSession(sco_id);
407                 active = recordingSession != null;
408             }
409
410             return Json(active, JsonRequestBehavior.AllowGet);
411         }
412
413         [HttpGet]
414         public ActionResult Wufoo() {
415             var client = new WebClient();
416             var response = client.DownloadString(
417                                                  "http://intesolvtest.wufoo.com/forms/submit-a-question/def/field1=Course+Title&field2=Course+Code&field5=today,field3=Jason&field4=McIntosh&field2=jason.wigginton@gmail.com");
418
419             return Content(response);
420         }
421
422
423         /// <summary>
424         /// </summary>
425         /// <param name="recordingSession"></param>
426         /// <param name="email"></param>
427         /// <param name="firstname"></param>
428         /// <param name="lastname"></param>
429         /// <returns></returns>
430         private string getWufooQAlink(RecordingSession recordingSession, string email, string firstname, string lastname) {
431             firstname = firstname?.Trim();
432             lastname = lastname?.Trim();
433             email = email?.Trim();
434
435             var meetingDetails =
436                 Database.MeetingDetails.SingleOrDefault(md => md.MeetingKey == recordingSession.MeetingSCO_ID);
437
438             var code = meetingDetails != null ? meetingDetails.CourseCode : "";
439             var instructor = meetingDetails != null ? meetingDetails.Instructor : "";
440             var url = recordingSession.SurveyLink;
441             if(url.EndsWith("/")) {
442                 url = url.Remove(url.LastIndexOf("/"), 1);
443             }
444             //1=Course Title,2=Course Code,3=Date,4=Firstname,5=Lastname,8=email
445             //return url + String.Format("/def/field1={0}&field2={1}&field3={2}&field4={3}&field5={4}&field8={5}", recordingSession.Name, code, "today", firstname, lastname,email);
446             //1 = first name, 2 = lastname, 3 = email, 5 = Course
447             return url +
448                    string.Format("/def/field1={0}&field2={1}&field3={2}&field5={3}&field7={4}&field8={5}&field9={6}",
449                                  firstname, lastname, email, recordingSession.Name, code, instructor, "today");
450         }
451
452
453         private void TrackUserEntrance(MeetingSession session, int principalId, string firstName, string lastName,
454                                        string email) {
455             firstName = firstName?.Trim();
456             lastName = lastName?.Trim();
457             email = email?.Trim();
458
459             //participant isnt getting tracked for the test rebroadcast -> is this a real problem or a test-data problem?
460             var now = DateTime.UtcNow;
461             var context = Database;
462             Extensions.LogServiceCall("TrackUser",
463                                       string.Format(
464                                                     "Parameters: meetingSessionKey = {0} principal_id= {1} firstname = {2}, lastname = {3} email = {4}, type = 0",
465                                                     session.MeetingSessionKey, principalId, firstName, lastName, email));
466
467             try {
468                 if(principalId > 0) {
469                     if(session != null) {
470                         var user = context.Participants.SingleOrDefault(p => p.Principal_ID == principalId);
471
472                         if(user == null) {
473                             //create new user
474                             user = new Participant {
475                                 Principal_ID = principalId,
476                                 FirstName = firstName,
477                                 LastName = lastName,
478                                 Email = email
479                             };
480                             context.Participants.InsertOnSubmit(user);
481                         }
482
483                         //JM 9/27/2012 - changed to grab first participant session found.  Weird issue where some users were getting multiple sesions  
484                         var existingMeetingParticipantSession =
485                             session.MeetingParticipantSessions.OrderBy(mps => mps.MeetingParticipantSessionKey)
486                                    .
487                                     FirstOrDefault(mps => mps.ParticipantKey == principalId);
488
489                         if(existingMeetingParticipantSession == null) {
490                             //create new session for user
491                             existingMeetingParticipantSession = new MeetingParticipantSession {
492                                 MeetingSessionKey = session.MeetingSessionKey,
493                                 ParticipantKey = principalId,
494                                 PlaySound = true
495                             };
496                             context.MeetingParticipantSessions.InsertOnSubmit(existingMeetingParticipantSession);
497                             context.SubmitChanges();
498                             //JM-12/27/2012 Change to check for multiple meeting participant sessions
499                             CheckParticipantSessions(session, principalId);
500                             //make sure tracking gets assigned right key 
501                             existingMeetingParticipantSession = context.MeetingParticipantSessions.OrderBy(
502                                                                                                            p => p.MeetingParticipantSessionKey)
503                                                                        .FirstOrDefault(
504                                                                                        p =>
505                                                                                        p.MeetingSessionKey == session.MeetingSessionKey &
506                                                                                        p.ParticipantKey == principalId);
507                         }
508
509                         var existingParticipantTracking =
510                             context.ParticipantTrackings.FirstOrDefault(
511                                                                         pt =>
512                                                                         pt.MeetingParticipantSessionKey ==
513                                                                         existingMeetingParticipantSession.MeetingParticipantSessionKey &&
514                                                                         pt.Principal_ID == principalId && !pt.EndDate.HasValue);
515
516                         if(existingParticipantTracking == null) {
517                             //add entrance time
518
519                             var pTrack = new ParticipantTracking {
520                                 MeetingParticipantSessionKey =
521                                     existingMeetingParticipantSession.MeetingParticipantSessionKey,
522                                 Principal_ID = principalId,
523                                 StartDate = now
524                             };
525                             context.ParticipantTrackings.InsertOnSubmit(pTrack);
526                         } else {
527                             //track enter received when there is already an entrance with no exit
528                             //set exit on existing
529                             ParticipantEngagement lastEngagement =
530                                 existingParticipantTracking.MeetingParticipantSession.ParticipantEngagements.OrderBy(
531                                                                                                                      e => e.DisplayTime)
532                                                            .LastOrDefault();
533                             if(lastEngagement == null)
534                                 existingParticipantTracking.EndDate = existingParticipantTracking.StartDate;
535                             else
536                                 existingParticipantTracking.EndDate = lastEngagement.ResponseTime.HasValue
537                                                                           ? lastEngagement.ResponseTime.Value
538                                                                           : lastEngagement.DisplayTime;
539                             //existingParticipantTracking.EndDate = now;
540                             //add new entrance time
541
542                             var pTrack = new ParticipantTracking {
543                                 MeetingParticipantSessionKey =
544                                     existingMeetingParticipantSession.MeetingParticipantSessionKey,
545                                 Principal_ID = principalId,
546                                 StartDate = now
547                             };
548                             context.ParticipantTrackings.InsertOnSubmit(pTrack);
549                         }
550                         context.SubmitChanges();
551                     }
552                 } else {
553                     throw new ArgumentException("Principal ID: " + principalId + " is not valid");
554                 }
555             } catch (Exception ex) {
556                 Extensions.LogServiceError("TrackUser", ex);
557             }
558         }
559
560         private void CheckParticipantSessions(MeetingSession session, int principal_id) {
561             try {
562                 var context = Database;
563                 var participantSessionCount =
564                     session.MeetingParticipantSessions.Count(mps => mps.ParticipantKey == principal_id);
565                 if(participantSessionCount > 1) {
566                     var participantSessions =
567                         session.MeetingParticipantSessions.Where(mps => mps.ParticipantKey == principal_id)
568                                .OrderBy(
569                                         mps => mps.MeetingParticipantSessionKey);
570                     var psToKeep = participantSessions.First();
571                     var psessionsToDelete =
572                         participantSessions.Where(
573                                                   ps => ps.MeetingParticipantSessionKey != psToKeep.MeetingParticipantSessionKey);
574
575                     foreach (var meetingParticipantSession in psessionsToDelete) {
576                         context.MeetingParticipantSessions.DeleteOnSubmit(meetingParticipantSession);
577                     }
578                     context.SubmitChanges();
579                 }
580             } catch (Exception ex) {
581                 Extensions.LogServiceError("CheckParticipanSessions", ex);
582             }
583         }
584
585         private RecordingSession getActiveRecordingSession(int recordingScoId) {
586             //for SP3 if  rs.StartTime < now + 1hr., recording considered "active", user will be placed into lobby
587             var now = DateTime.UtcNow;
588             var recording = Database.RecordingSessions.SingleOrDefault(rs => rs.RecordingSCO_ID == recordingScoId
589                                                                              && rs.StartTime <= now.AddHours(1) &&
590                                                                              rs.EndTime > now);
591
592             return recording;
593         }
594
595         private RecordingSession getNextRecordingSession(int recordingScoId) {
596             //for SP3 a user can wait in the lobby for an hour, so the next (not currently active) recording session will have start > than now+1hr 
597             var now = DateTime.UtcNow;
598             var recording = Database.RecordingSessions.Where(rs => rs.RecordingSCO_ID == recordingScoId
599                                                                    && rs.StartTime > now.AddHours(1))
600                                     .OrderBy(rs => rs.StartTime)
601                                     .FirstOrDefault();
602
603             return recording;
604         }
605
606
607         private void SaveModelState() {
608             TempData["ModelState"] = ModelState;
609         }
610
611         private void RestoreModelState() {
612             var previousModelState = TempData["ModelState"] as ModelStateDictionary;
613             if(previousModelState != null) {
614                 foreach (KeyValuePair<string, ModelState> kvp in previousModelState)
615                     if(!ModelState.ContainsKey(kvp.Key))
616                         ModelState.Add(kvp.Key, kvp.Value);
617
618
619                 TempData["ModelState"] = ModelState; //Add back into TempData
620             }
621         }
622
623
624         /// <summary>
625         /// </summary>
626         private void placeIntoLobby(int meetingSessionKey, string email, int certificateId) {
627             email = email?.Trim();
628             //rebroadcast/LobbyWait
629             Response.Redirect("/rebroadcast/LobbyWait?meetingSessionKey=" + meetingSessionKey + "&email=" + email + "&certificateId=" + certificateId, true);
630         }
631
632
633         [HttpGet]
634         public ActionResult GotoContent(int meetingSessionKey, string email, int certificateId) {
635             email = email?.Trim();
636
637             var now = DateTime.UtcNow;
638             var connectUrl = ConfigurationManager.AppSettings["Connect.Url"];
639             var accountId = int.Parse(ConfigurationManager.AppSettings["Connect.AccountId"]);
640             var password = Guid.NewGuid()
641                                .ToString()
642                                .Replace("{", "")
643                                .Replace("}", "")
644                                .Substring(0, 8);
645
646             var participant = Database.Participants.FirstOrDefault(p => p.Email == email);
647             if(participant == null) {
648                 Extensions.LogServiceCall("[RebroadcastController][GotoContent]", string.Format("participant found null; meetingSessionKey = {0} email = {1} certificateId = {2}", meetingSessionKey, email, certificateId));
649                 throw new Exception("Login failed! " + email);
650             }
651
652             var recording =
653                 Database.RecordingSessions.FirstOrDefault(
654                                                           rs => rs.MeetingSessionKey == meetingSessionKey && rs.StartTime <= now && rs.EndTime > now);
655             if(recording == null) {
656                 Extensions.LogServiceCall("[RebroadcastController][GotoContent]", string.Format("recording found null; meetingSessionKey = {0} email = {1} certificateId = {2}", meetingSessionKey, email, certificateId));
657                 throw new Exception("Login failed! " + email);
658             }
659
660             var meeting = Database.MeetingSessions.FirstOrDefault(y => y.MeetingSessionKey == meetingSessionKey);
661             if(meeting == null) {
662                 Extensions.LogServiceCall("[RebroadcastController][GotoContent]", string.Format("meeting found null; meetingSessionKey = {0} email = {1} certificateId = {2}", meetingSessionKey, email, certificateId));
663                 throw new Exception("Login failed! " + email);
664             }
665
666             var admin = AdobeMeetingConnection.GetAdobeAdminSession();
667             Login.ResetPassword(admin, participant.Principal_ID, password);
668
669             var session = Login.UserLogin(participant.Email, password, connectUrl, accountId);
670
671             var differenceInMilliseconds = (now - meeting.StartDate).TotalMilliseconds;
672
673             var recordingView = new RecordingView {
674                 Name = AdobeMeetingConnection.getAdobeMeetingName(recording.RecordingUrl),
675                 PrincipalId = participant.Principal_ID,
676                 MeetingSessionKey = meeting.MeetingSessionKey,
677                 FirstName = participant.FirstName,
678                 LastName = participant.LastName,
679                 Email = participant.Email,
680                 QAlink = getWufooQAlink(recording, participant.Email, participant.FirstName, participant.LastName),
681                 Url =
682                     string.Format(
683                                   "{0}/{1}?session={2}&launcher=false&pbEIOpen=false&archiveOffset={3}",
684                                   connectUrl,
685                                   recording.RecordingUrl,
686                                   session.SessionKey, differenceInMilliseconds), //+
687                 //((passcode == null) ? "" : "&meeting-passcode=" + passcode)
688                 CertificateId = certificateId
689             };
690
691             admin = AdobeMeetingConnection.GetAdobeAdminSession();
692             var request = new Request(admin, "principal-update");
693             request.Parameters.Add("first-name", participant.FirstName);
694             request.Parameters.Add("last-name", participant.LastName);
695             request.Parameters.Add("principal-id", participant.Principal_ID.ToString());
696             request.Execute();
697
698             var idCookie = new HttpCookie("id") {
699                 Value = participant.Principal_ID.ToString(),
700                 Expires = DateTime.UtcNow.AddDays(1)
701             };
702             Response.Cookies.Add(idCookie);
703
704             TrackUserEntrance(meeting, participant.Principal_ID, participant.FirstName, participant.LastName,
705                               participant.Email);
706             return View("recording", recordingView);
707         }
708
709         //public string SendCert(int meetingSessionKey, int certificate_id, int principal_id)
710         //{
711         //    //TODO make outcome dictionary to return pass, fail, ineligible, or wonky:contact cust serv
712         //    string _fail = "http://localhost:11038/Certificate?a=1179512338&b=20160128&c=1d633b";
713         //    //string _fail = "FAIL";
714
715         //    var meeting = Database.MeetingSessions.FirstOrDefault(x => x.MeetingSessionKey == meetingSessionKey);
716
717
718         //    if (certificate_id > 0)
719         //    {
720         //        ParticipantPurchase purchase =
721         //            Database.ParticipantPurchases.FirstOrDefault(p => p.Purchase_ID == certificate_id && p.PrincipalID == principal_id);
722
723         //        if (purchase == null)
724         //        {
725         //            //if the certificate_id is >0 then purchase would only be null because of record being deleted while user was in the meeting, or the principalID got mis-matched
726         //            return _fail; // TODO probably return wonky, contact cust service message
727         //        }
728
729         //        if (!purchase.EarnedCertificate)
730         //        {
731         //            MeetingParticipantSession meetingParticipantSession =
732         //                Database.MeetingParticipantSessions.FirstOrDefault(
733         //                    m => m.MeetingSessionKey == meetingSessionKey && m.ParticipantKey == principal_id);
734         //            if (meetingParticipantSession == null)
735         //            {
736         //                return _fail;//TODO return wonky
737         //            }
738
739         //            List<ParticipantSessionsDataResult> participantSessionsDataResult =
740         //                Database.ParticipantSessionsData(meetingSessionKey)
741         //                    .ToList();
742
743         //            var participantSessionsData =
744         //                participantSessionsDataResult.OrderBy(p => p.MeetingParticipantSessionKey)
745         //                    .FirstOrDefault(
746         //                        p =>
747         //                            p.MeetingParticipantSessionKey ==
748         //                            meetingParticipantSession.MeetingParticipantSessionKey);
749
750         //            if (participantSessionsData == null)
751         //            {
752         //                return _fail;//TODO is this a return wonky? it means they weren't tracked
753         //            }
754
755
756         //            MeetingSessionsDataResult meetingSession = Database.MeetingSessionsData().FirstOrDefault(m => m.MeetingSessionKey == meetingSessionKey);
757         //            if (meetingSession == null)
758         //            {
759         //                return _fail;//TODO return wonky
760         //            }
761
762         //            var courseLength = meetingSession.ActualSessionTime.GetValueOrDefault(); //in minutes
763         //            double courseCredits = courseLength / 50.0; //1 credit per 50 minutes
764         //            List<double> maxCourseCredits = ConfigurationManager.AppSettings["MaxCourseCreditList"].Split(';').Select(s => double.Parse(s)).ToList(); //max credits cut list
765         //            int mx = maxCourseCredits.BinarySearch(courseCredits); //find appropriate max for this course
766         //            double maxCredits = maxCourseCredits[mx >= 0 ? mx : ~mx - 1];//...by rounding down
767         //            double realCredits = participantSessionsData.SessionCredit == null ? 0 : (double)participantSessionsData.SessionCredit.Value; //users credits per adobe
768         //            purchase.Credits = Math.Min(maxCredits, realCredits); //user awarded no more than max possible credits for the course
769
770         //            MeetingDetail meetingDetail =
771         //                Database.MeetingDetails.FirstOrDefault(m => m.MeetingKey == purchase.MeetingSco); 
772
773         //            purchase.Presenter = meetingDetail == null ? "" : meetingDetail.Instructor;
774
775         //            double percentComplete = (participantSessionsData.SessionEngagementCount.GetValueOrDefault() == 0 ?
776         //                1 : (participantSessionsData.SessionHeartbeatCount.GetValueOrDefault() / participantSessionsData.SessionEngagementCount.Value));
777
778         //            if (percentComplete >= .75) //threshold for earning a certificate is 75% response rate
779         //            {
780         //                purchase.EarnedCertificate = true;
781         //            }
782
783         //            Database.SubmitChanges();
784
785         //            if (purchase.EarnedCertificate)
786         //            {
787         //                //TODO with return Pass dictionary value as string.format with replace of certUrl
788         //                //string certUrl = AdobeCertificateHelper.SendWebcastCert(purchase);
789         //                //format the outcome.pass.value message with certUrl replace
790         //                //return outcome.pass with its value
791         //                return AdobeCertificateHelper.SendWebcastCert(purchase);
792         //            }
793         //            //TODO return outcome.fail
794         //            return AdobeCertificateHelper.SendFailNotice(purchase);
795         //        }
796         //    }
797
798         //    return _fail;// TODO return ineligible. need to make AdobeCertificateHelper.SendIneligibleNotice(principalId)
799         //}
800
801         /// <summary>
802         /// </summary>
803         /// <param name="loginInfo"></param>
804         /// <returns></returns>
805         private int getConnectUser(loginInfo loginInfo) {
806             var password = Guid.NewGuid()
807                                .ToString()
808                                .Replace("{", "")
809                                .Replace("}", "")
810                                .Substring(0, 8);
811             var admin = AdobeMeetingConnection.GetAdobeAdminSession();
812             var request = new Request(admin, "principal-list");
813             request.Parameters.Add("filter-type", "guest");
814             request.Parameters.Add("filter-type", "user");
815             request.Parameters.Add("filter-login", loginInfo.email);
816             if(request.Execute() && request.Status == Status.OK) {
817                 int principalId;
818                 if(request.XmlResults.SelectSingleNode("//principal") == null) {
819                     // Not found
820                     principalId = Login.CreateGuest(admin, loginInfo.email, password, loginInfo.email, loginInfo.firstname, loginInfo.lastname);
821                 } else {
822                     // Found
823                     principalId =
824                         int.Parse(request.XmlResults.SelectSingleNode("//principal")
825                                          .Attributes["principal-id"].Value);
826                     loginInfo.email = request.XmlResults.SelectSingleNode("//principal/login")
827                                              .InnerText;
828                     if(request.XmlResults.SelectSingleNode("//principal")
829                               .Attributes["type"].Value == "user") {
830                         ModelState.AddModelError("email", "Invalid email.");
831                         SaveModelState();
832
833                         throw new Exception("Login failed!");
834                     }
835                 }
836                 if(principalId < 1) {
837                     throw new Exception("Login failed!");
838                 }
839
840                 Login.ResetPassword(admin, principalId, password);
841                 return principalId;
842             }
843
844             throw new Exception("Login failed!");
845         }
846
847         /// <summary>
848         /// </summary>
849         /// <param name="loginInfo"></param>
850         /// <param name="principalId"></param>
851         /// <returns></returns>
852         private int addAdobeConnectPermissions(loginInfo loginInfo, int principalId) {
853             var admin = AdobeMeetingConnection.GetAdobeAdminSession();
854             var meetingXml = AdobeMeetingConnection.getAdobeMeeting(loginInfo.meetingUrl);
855             var recordingId = int.Parse(meetingXml.SelectSingleNode("//sco")
856                                                   .Attributes["sco-id"].Value);
857             var request = new Request(admin, "permissions-update");
858             request.Parameters.Add("acl-id", recordingId.ToString());
859             request.Parameters.Add("principal-id", principalId.ToString());
860             request.Parameters.Add("permission-id", "view");
861             if(request.Execute() && request.Status == Status.OK) {
862                 return recordingId;
863             }
864             throw new Exception("Login failed!");
865         }
866
867         /// <summary>
868         /// </summary>
869         /// <param name="loginInfo"></param>
870         /// <param name="recordingId"></param>
871         /// <returns></returns>
872         private recordingResult getRecording(ref loginInfo loginInfo, int recordingId) {
873             var now = DateTime.UtcNow;
874             var recording =
875                 Database.RecordingSessions.SingleOrDefault(
876                                                            rs => rs.RecordingSCO_ID == recordingId && rs.StartTime <= now && rs.EndTime > now);
877
878             if(recording == null) {
879                 recording =
880                     Database.RecordingSessions.SingleOrDefault(
881                                                                rs =>
882                                                                rs.RecordingSCO_ID == recordingId && rs.StartTime <= now.AddHours(1) &&
883                                                                rs.StartTime > now);
884
885                 if(recording == null) {
886                     loginInfo.meetingSessionKey = 0;
887                     return recordingResult.None;
888                 }
889                 loginInfo.meetingSessionKey = recording.MeetingSessionKey.Value;
890                 return recordingResult.Lobby;
891             }
892             loginInfo.meetingSessionKey = recording.MeetingSessionKey.Value;
893             return recordingResult.Now;
894         }
895
896         /// <summary>
897         /// </summary>
898         /// <param name="loginInfo"></param>
899         /// <param name="principalId"></param>
900         private void saveParticipant(loginInfo loginInfo, int principalId) {
901             var participant =
902                 Database.Participants.SingleOrDefault(p => p.Principal_ID == principalId);
903             if(participant == null) {
904                 participant = new Participant {
905                     Principal_ID = principalId,
906                     FirstName = loginInfo.firstname,
907                     LastName = loginInfo.lastname,
908                     Email = loginInfo.email
909                 };
910                 Database.Participants.InsertOnSubmit(participant);
911             } else {
912                 participant.FirstName = loginInfo.firstname;
913                 participant.LastName = loginInfo.lastname;
914             }
915             Database.SubmitChanges();
916         }
917
918         private int savePurchaseInfo(loginInfo loginInfo, string purchaseDate, int principalId, string ticketFromUrl, int meetingSessionKey) {
919             purchaseDate = purchaseDate?.Trim();
920             ticketFromUrl = ticketFromUrl?.Trim();
921
922             //purchase indicates hashed-ticket login that could earn a certificate. storing this info separate since participant name info gets updated based on adobe info
923             //and per cpe, a cert can only be earned in the exact name of purchaser.
924             var meeting =
925                 Database.MeetingSessions.SingleOrDefault(
926                                                          ms => ms.MeetingSessionKey == meetingSessionKey);
927             string formattedPdate = purchaseDate.Substring(0, 4) + '-' +
928                                     purchaseDate.Substring(4, 2) +
929                                     '-' + purchaseDate.Substring(6, 2);
930             DateTime pDate = Convert.ToDateTime(formattedPdate);
931
932             ParticipantPurchase purchase =
933                 Database.ParticipantPurchases.SingleOrDefault(
934                                                               p =>
935                                                               p.PrincipalID == principalId & p.Ticket == ticketFromUrl &
936                                                               p.MeetingSco == meeting.SCO_ID);
937             if(purchase == null) {
938                 purchase = new ParticipantPurchase {
939                     FirstName = loginInfo.firstname,
940                     LastName = loginInfo.lastname,
941                     Email = loginInfo.email,
942                     PrincipalID = principalId,
943                     MeetingName = meeting.Name,
944                     MeetingDate = meeting.StartDate,
945                     MeetingSco = meeting.SCO_ID,
946                     Ticket = ticketFromUrl,
947                     PurchaseDate = pDate,
948                     EarnedCertificate = false,
949                     Credits = 0
950                 };
951                 Database.ParticipantPurchases.InsertOnSubmit(purchase);
952             }
953             Database.SubmitChanges();
954
955             return purchase.Purchase_ID;
956         }
957     }
958
959     /// <summary>
960     /// </summary>
961     public class loginInfo {
962         //string meetingUrl, string firstname, string lastname, string email,
963         //    string ticket, string passcode, string ticketFromUrl, string purchaseDate
964
965         public string meetingUrl { get; set; }
966         public string firstname { get; set; }
967         public string lastname { get; set; }
968         public string email { get; set; }
969         public string ticket { get; set; }
970         public string passcode { get; set; }
971         public string ticketFromUrl { get; set; }
972         public string purchaseDate { get; set; }
973         public int meetingSessionKey { get; set; }
974     }
975
976     /// <summary>
977     /// </summary>
978     public enum recordingResult {
979         Now,
980         Lobby,
981         None
982     }
983 }