From 3962f6bac91e87f7849b3c0cb8cdc53c4c1a9d86 Mon Sep 17 00:00:00 2001 From: Damodharan Date: Fri, 13 Nov 2020 10:35:39 +0000 Subject: [PATCH 01/11] Added more info Documentation, related to the issue: #19. --- README.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dd1dcf8..5a77788 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ + # ncn-civi-zoom Civirules Conditions/Actions that talk with Zoom developed for NCN. @@ -39,10 +40,20 @@ The JWT App within your Zoom account will allow you to connect CiviCRM to zoom, ### Sign into CiviCRM and setup custom fields for Zoom Create custom fields against the Event entity (you can select which types of events Zoom fields applicable too or leave blank for all events). Also note we would recommend turning off the public setting on the custom field group as you probably dont want the IDs being exposed publicly on information pages. + The fields needed are -* Zoom Account ID. As the extension supports multiple zoom accounts in a single installation this field will store which zoom account the meeting or webinar is for. -* Zoom Webinar ID. Will hold Zoom Webinar IDs -* Zoom Meeting ID. Will hold the Zoom Meeting ID. +#### Zoom Account ID: +*As the extension supports multiple zoom accounts in a single installation this field will store to which zoom account the meeting or webinar belongs to. +* Please ensure that this custom field is set as **Data Type** to _Integer_ and **Field Type** to _Text_ as this field will only hold small natural numbers. +* Please ensure that this custom field is set as **View Only?** to _TRUE_ as its value is set from the 'zoom_account_list' and we don't want end users mistakenly entering invalid values. + +#### Zoom Webinar ID +* Will hold Zoom Webinar IDs. +* Please ensure that this custom field is set as **Data Type** to _Number_ and **Field Type** to _Text_ as these fields will only contain large numbers (spaces in meeting numbers are trimmed before saving). + +#### Zoom Meeting ID +* Will hold the Zoom Meeting ID. +* Please ensure that this custom field is set as **Data Type** to _Number_ and **Field Type** to _Text_ as these fields will only contain large numbers (spaces in meeting numbers are trimmed before saving). ### Sign into CiviCRM and install the CiviCRM Zoom extension From f1f8af8bc006a7a3e39f34274f3151e63a88f744 Mon Sep 17 00:00:00 2001 From: Damodharan Date: Fri, 13 Nov 2020 10:58:08 +0000 Subject: [PATCH 02/11] Calling the upgrades during the installation also. --- CRM/NcnCiviZoom/Upgrader.php | 29 +---------------------------- CRM/NcnCiviZoom/Utils.php | 31 +++++++++++++++++++++++++++++++ ncn_civi_zoom.php | 1 + 3 files changed, 33 insertions(+), 28 deletions(-) diff --git a/CRM/NcnCiviZoom/Upgrader.php b/CRM/NcnCiviZoom/Upgrader.php index 76994c0..95bd851 100644 --- a/CRM/NcnCiviZoom/Upgrader.php +++ b/CRM/NcnCiviZoom/Upgrader.php @@ -162,34 +162,7 @@ public function upgrade_1002(){ //Upgrade function to add the note type custom field to the events public function upgrade_1003(){ - $customGroupName = CRM_NcnCiviZoom_Constants::CG_Event_Zoom_Notes; - $customFieldName = CRM_NcnCiviZoom_Constants::CF_Event_Zoom_Notes; - - $customGroupDetails = civicrm_api3('CustomGroup', 'create', [ - 'sequential' => 1, - 'title' => "Event Zoom Notes", - 'extends' => "Event", - 'name' => $customGroupName, - ]); - - civicrm_api3('CustomField', 'create', [ - 'sequential' => 1, - 'custom_group_id' => $customGroupDetails['values'][0]['id'], - 'label' => "Event Zoom Notes", - 'name' => $customFieldName, - 'data_type' => "Memo", - 'html_type' => "TextArea", - 'is_view' => 1, - ]); - - $sendZoomRegistrantsEmailTemplateTitle = CRM_NcnCiviZoom_Constants::SEND_ZOOM_REGISTRANTS_EMAIL_TEMPLATE_TITLE; - $msgHtml = "
{event_title}
{registrants}
"; - $msgSubject = "Recently Joined to the zoom event: {event_title}"; - civicrm_api3('MessageTemplate', 'create', [ - 'msg_title' => $sendZoomRegistrantsEmailTemplateTitle, - 'msg_html' => $msgHtml, - 'msg_subject' => $msgSubject, - ]); + CRM_NcnCiviZoom_Utils::forUpgrade1003(); return TRUE; } diff --git a/CRM/NcnCiviZoom/Utils.php b/CRM/NcnCiviZoom/Utils.php index c248359..168c588 100644 --- a/CRM/NcnCiviZoom/Utils.php +++ b/CRM/NcnCiviZoom/Utils.php @@ -433,4 +433,35 @@ public static function sendEmail($email, $emailContent) { return $emailSent; } + + public static function forUpgrade1003(){ + $customGroupName = CRM_NcnCiviZoom_Constants::CG_Event_Zoom_Notes; + $customFieldName = CRM_NcnCiviZoom_Constants::CF_Event_Zoom_Notes; + + $customGroupDetails = civicrm_api3('CustomGroup', 'create', [ + 'sequential' => 1, + 'title' => "Event Zoom Notes", + 'extends' => "Event", + 'name' => $customGroupName, + ]); + + civicrm_api3('CustomField', 'create', [ + 'sequential' => 1, + 'custom_group_id' => $customGroupDetails['values'][0]['id'], + 'label' => "Event Zoom Notes", + 'name' => $customFieldName, + 'data_type' => "Memo", + 'html_type' => "TextArea", + 'is_view' => 1, + ]); + + $sendZoomRegistrantsEmailTemplateTitle = CRM_NcnCiviZoom_Constants::SEND_ZOOM_REGISTRANTS_EMAIL_TEMPLATE_TITLE; + $msgHtml = "
{event_title}
{registrants}
"; + $msgSubject = "Recently Joined to the zoom event: {event_title}"; + civicrm_api3('MessageTemplate', 'create', [ + 'msg_title' => $sendZoomRegistrantsEmailTemplateTitle, + 'msg_html' => $msgHtml, + 'msg_subject' => $msgSubject, + ]); + } } diff --git a/ncn_civi_zoom.php b/ncn_civi_zoom.php index 92612de..163fdfc 100644 --- a/ncn_civi_zoom.php +++ b/ncn_civi_zoom.php @@ -54,6 +54,7 @@ function ncn_civi_zoom_civicrm_install() { function ncn_civi_zoom_civicrm_postInstall() { $settings['base_url'] = "https://api.zoom.us/v2"; CRM_Core_BAO_Setting::setItem($settings, ZOOM_SETTINGS, 'zoom_settings'); + CRM_NcnCiviZoom_Utils::forUpgrade1003(); _ncn_civi_zoom_civix_civicrm_postInstall(); } From 34352aa8b16b73e20767118fc9a4d0c9ec99b422 Mon Sep 17 00:00:00 2001 From: Damodharan Date: Fri, 13 Nov 2020 11:27:31 +0000 Subject: [PATCH 03/11] Minor Change in Readme file. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5a77788..85f7793 100644 --- a/README.md +++ b/README.md @@ -89,5 +89,5 @@ Once you've decided this you can create a new CiviRule as per the screen shot. ### Scheduled Job for Zoom attendance * Once you've created a zoom event, you need to create a scheduled job for that event. The Api Entity should be **Zoom Event** and the api action should be **Generatezoomattendance**. This api has only one parameter which is 'days'. This will be used the events using the event's end date i.e the events ended 'x' number of days from current date, where 'x' is the 'days' paramter you enter. You can schedule the Job as frequent as you need it to run. -### Scheduled Job for updating Zoom registrants +### Scheduled Job for emailing new Zoom registrants * Once you've created a zoom event, you need to create a scheduled job for that event. The Api Entity should be **Zoom Event** and the api action should be **Getrecentzoomregistrants**. This api has two parameters one is 'mins' i.e registrants registered that many 'minutes' before will be filtered and will be taken for updation and for sending email. The other parameter is the Email address to which you want the regitrants list to be sent, for multiple email addresses seperate each by a comma symbol. From 1c1a7841f361129d9a733a019bc07a1fa76929aa Mon Sep 17 00:00:00 2001 From: Damodharan Date: Fri, 13 Nov 2020 14:20:16 +0000 Subject: [PATCH 04/11] Added the template id to the zoom settings and made the zoom attendance to loop through for meetings. --- CRM/NcnCiviZoom/Upgrader.php | 6 ++++++ CRM/NcnCiviZoom/Utils.php | 19 +++++++++++++++++++ README.md | 3 +++ api/v3/Zoomevent.php | 27 ++++++++++++++++++--------- images/email-zoom-registrants.PNG | Bin 0 -> 27100 bytes images/generate-zoom-attendance.PNG | Bin 0 -> 26612 bytes ncn_civi_zoom.php | 3 ++- 7 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 images/email-zoom-registrants.PNG create mode 100644 images/generate-zoom-attendance.PNG diff --git a/CRM/NcnCiviZoom/Upgrader.php b/CRM/NcnCiviZoom/Upgrader.php index 95bd851..4aa0519 100644 --- a/CRM/NcnCiviZoom/Upgrader.php +++ b/CRM/NcnCiviZoom/Upgrader.php @@ -166,4 +166,10 @@ public function upgrade_1003(){ return TRUE; } + //Upgrade function to add the email template id to zoom settings + public function upgrade_1004(){ + CRM_NcnCiviZoom_Utils::forUpgrade1004(); + return TRUE; + } + } diff --git a/CRM/NcnCiviZoom/Utils.php b/CRM/NcnCiviZoom/Utils.php index 168c588..81d9d13 100644 --- a/CRM/NcnCiviZoom/Utils.php +++ b/CRM/NcnCiviZoom/Utils.php @@ -464,4 +464,23 @@ public static function forUpgrade1003(){ 'msg_subject' => $msgSubject, ]); } + + public static function forUpgrade1004(){ + $sendZoomRegistrantsEmailTemplateTitle = CRM_NcnCiviZoom_Constants::SEND_ZOOM_REGISTRANTS_EMAIL_TEMPLATE_TITLE; + $templateDetails = civicrm_api3('MessageTemplate', 'get', [ + 'sequential' => 1, + 'msg_title' => $sendZoomRegistrantsEmailTemplateTitle, + ]); + $zoomSettings = self::getZoomSettings(); + if(!empty($templateDetails['id'])){ + $settings['registrants_email_template_id'] = $templateDetails['id']; + } + CRM_Core_BAO_Setting::setItem($zoomSettings, ZOOM_SETTINGS, 'zoom_settings'); + } + + public static function getEmailTemplateIdToSendZoomRegistrants(){ + $settings = self::getZoomSettings(); + $templateId = CRM_Utils_Array::value('registrants_email_template_id', $settings, NULL); + return $templateId; + } } diff --git a/README.md b/README.md index 85f7793..037125c 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ + # ncn-civi-zoom Civirules Conditions/Actions that talk with Zoom developed for NCN. @@ -88,6 +89,8 @@ Once you've decided this you can create a new CiviRule as per the screen shot. ## Creating Scheduled Jobs ### Scheduled Job for Zoom attendance * Once you've created a zoom event, you need to create a scheduled job for that event. The Api Entity should be **Zoom Event** and the api action should be **Generatezoomattendance**. This api has only one parameter which is 'days'. This will be used the events using the event's end date i.e the events ended 'x' number of days from current date, where 'x' is the 'days' paramter you enter. You can schedule the Job as frequent as you need it to run. +* An example has been done below![Screenshot of Scheduled Job](images/generate-zoom-attendance) ### Scheduled Job for emailing new Zoom registrants * Once you've created a zoom event, you need to create a scheduled job for that event. The Api Entity should be **Zoom Event** and the api action should be **Getrecentzoomregistrants**. This api has two parameters one is 'mins' i.e registrants registered that many 'minutes' before will be filtered and will be taken for updation and for sending email. The other parameter is the Email address to which you want the regitrants list to be sent, for multiple email addresses seperate each by a comma symbol. +* An example has been done below![Screenshot of Scheduled Job](images/email-zoom-registrants.PNG) diff --git a/api/v3/Zoomevent.php b/api/v3/Zoomevent.php index 78dcb90..8a4a39c 100644 --- a/api/v3/Zoomevent.php +++ b/api/v3/Zoomevent.php @@ -71,22 +71,20 @@ function civicrm_api3_zoomevent_generatezoomattendance($params) { $jwt = JWT::encode($payload, $key); $webinarId = getWebinarID($eventId); $meetingId = getMeetingID($eventId); + $page = 0; if(!empty($webinarId)){ $entityId = $webinarId; $url = $settings['base_url'] . "/past_webinars/$webinar/absentees?page=$page"; $entity = "Webinar"; }elseif(!empty($meetingId)){ $entityId = $meetingId; - $url = $settings['base_url'] . "/past_meetings/$meetingId/participants"; + $url = $settings['base_url'] . "/past_meetings/$meetingId/participants?page=$page"; $entity = "Meeting"; }else{ continue; } $token = $jwt; - - $page = 0; - // Get absentees from Zoom API $response = Zttp::withHeaders([ 'Content-Type' => 'application/json;charset=UTF-8', @@ -129,10 +127,21 @@ function civicrm_api3_zoomevent_generatezoomattendance($params) { } }elseif ($entity == "Meeting") { $attendeesEmails = []; - $participants = $response->json()['participants']; - foreach ($participants as $key => $value) { - $attendeesEmails[] = $value['user_email']; - } + $page = 1; + do { + $url = $settings['base_url'] . "/past_meetings/$meetingId/participants?page=$page"; + // Get absentees from Zoom API + $response = Zttp::withHeaders([ + 'Content-Type' => 'application/json;charset=UTF-8', + 'Authorization' => "Bearer $token" + ])->get($url); + $participants = $response->json()['participants']; + foreach ($participants as $key => $value) { + $attendeesEmails[] = $value['user_email']; + } + $page++; + $pageCount = $response->json()['page_count']; + } while ($page <= $pageCount); $attendees = selectAttendees($attendeesEmails, $eventId, "Meeting"); } updateAttendeesStatus($attendees, $eventId); @@ -306,7 +315,7 @@ function civicrm_api3_zoomevent_getrecentzoomregistrants($params) { $result = []; $events = CRM_NcnCiviZoom_Utils::getUpcomingEventsList(); - foreach ($events as $key => $event) { + foreach ($events as $key => $events) { $registrantsList = CRM_CivirulesActions_Participant_AddToZoom::getZoomRegistrants($event['id']); if(!empty($registrantsList)){ $recentRegistrants = CRM_NcnCiviZoom_Utils::filterZoomRegistrantsByTime($registrantsList, $params['mins']); diff --git a/images/email-zoom-registrants.PNG b/images/email-zoom-registrants.PNG new file mode 100644 index 0000000000000000000000000000000000000000..7f2107c802d3f6005daa6b96ed3b351691251bac GIT binary patch literal 27100 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

qU}1?*!tz&5T8#5s0uj>(W21i{_gN=@Wb%sYy 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 literal 0 HcmV?d00001 diff --git a/images/generate-zoom-attendance.PNG b/images/generate-zoom-attendance.PNG new file mode 100644 index 0000000000000000000000000000000000000000..7206920230bb153973539ced870709de9723e4f6 GIT binary patch literal 26612 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 HyWRN*vJ}RT literal 0 HcmV?d00001 diff --git a/ncn_civi_zoom.php b/ncn_civi_zoom.php index 163fdfc..372e210 100644 --- a/ncn_civi_zoom.php +++ b/ncn_civi_zoom.php @@ -197,7 +197,8 @@ function ncn_civi_zoom_civicrm_validateForm($formName, &$fields, &$files, &$form if($submitValues['_qf_MessageTemplates_upload'] == 'Delete'){ $FormValues = $form->getVar('_values'); $msgTitle = CRM_NcnCiviZoom_Constants::SEND_ZOOM_REGISTRANTS_EMAIL_TEMPLATE_TITLE; - if($FormValues['msg_title'] == $msgTitle){ + $msgId = CRM_NcnCiviZoom_Utils::getEmailTemplateIdToSendZoomRegistrants(); + if(($FormValues['msg_title'] == $msgTitle) || ($FormValues['id'] == $msgId)){ $errors['_qf_default'] = ts("Sorry this template Can't be deleted. This was created by ncn_civi_zoom."); } } From 12c868c43d599024828b9be3511eccd178678471 Mon Sep 17 00:00:00 2001 From: Damodharan Date: Fri, 13 Nov 2020 14:28:22 +0000 Subject: [PATCH 05/11] Added upgrades to run on installation also. --- CRM/NcnCiviZoom/Utils.php | 1 + ncn_civi_zoom.php | 1 + 2 files changed, 2 insertions(+) diff --git a/CRM/NcnCiviZoom/Utils.php b/CRM/NcnCiviZoom/Utils.php index 81d9d13..d88256c 100644 --- a/CRM/NcnCiviZoom/Utils.php +++ b/CRM/NcnCiviZoom/Utils.php @@ -463,6 +463,7 @@ public static function forUpgrade1003(){ 'msg_html' => $msgHtml, 'msg_subject' => $msgSubject, ]); + CRM_Utils_System::url('civicrm/menu/rebuild'); } public static function forUpgrade1004(){ diff --git a/ncn_civi_zoom.php b/ncn_civi_zoom.php index 372e210..cfe19d8 100644 --- a/ncn_civi_zoom.php +++ b/ncn_civi_zoom.php @@ -55,6 +55,7 @@ function ncn_civi_zoom_civicrm_postInstall() { $settings['base_url'] = "https://api.zoom.us/v2"; CRM_Core_BAO_Setting::setItem($settings, ZOOM_SETTINGS, 'zoom_settings'); CRM_NcnCiviZoom_Utils::forUpgrade1003(); + CRM_NcnCiviZoom_Utils::forUpgrade1004(); _ncn_civi_zoom_civix_civicrm_postInstall(); } From 4842be755cffaa8802088fdc32a02327844146c8 Mon Sep 17 00:00:00 2001 From: Damodharan Date: Fri, 13 Nov 2020 14:49:16 +0000 Subject: [PATCH 06/11] Added the message template id to send the email also. --- CRM/NcnCiviZoom/Utils.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/CRM/NcnCiviZoom/Utils.php b/CRM/NcnCiviZoom/Utils.php index d88256c..0ebfd0b 100644 --- a/CRM/NcnCiviZoom/Utils.php +++ b/CRM/NcnCiviZoom/Utils.php @@ -372,6 +372,15 @@ public static function getMessageTemplateDetails($title = null, $id = null) { )); return $result ['values'][0]; + }elseif(!empty($id)){ + $result = civicrm_api3('MessageTemplate', 'get', array( + 'sequential' => 1, + 'id' => $id, + )); + + return $result ['values'][0]; + }else{ + return []; } } @@ -387,8 +396,9 @@ public static function sendZoomRegistrantsToEmail($toEmails, $registrantsList = return; } - $msgTitle = CRM_NcnCiviZoom_Constants::SEND_ZOOM_REGISTRANTS_EMAIL_TEMPLATE_TITLE; - $emailContent = self::getMessageTemplateDetails($msgTitle); + // $msgTitle = CRM_NcnCiviZoom_Constants::SEND_ZOOM_REGISTRANTS_EMAIL_TEMPLATE_TITLE; + $msgId = CRM_NcnCiviZoom_Utils::getEmailTemplateIdToSendZoomRegistrants(); + $emailContent = self::getMessageTemplateDetails(null, $msgId); if(empty($emailContent)){ return 'Email Template Not found.'; } @@ -463,7 +473,6 @@ public static function forUpgrade1003(){ 'msg_html' => $msgHtml, 'msg_subject' => $msgSubject, ]); - CRM_Utils_System::url('civicrm/menu/rebuild'); } public static function forUpgrade1004(){ From 741773741979d30dd286039b074c538f09744b50 Mon Sep 17 00:00:00 2001 From: Damodharan Date: Fri, 13 Nov 2020 15:25:31 +0000 Subject: [PATCH 07/11] Added the message template id to send the email also. --- api/v3/Zoomevent.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/v3/Zoomevent.php b/api/v3/Zoomevent.php index 8a4a39c..29d9a95 100644 --- a/api/v3/Zoomevent.php +++ b/api/v3/Zoomevent.php @@ -315,7 +315,7 @@ function civicrm_api3_zoomevent_getrecentzoomregistrants($params) { $result = []; $events = CRM_NcnCiviZoom_Utils::getUpcomingEventsList(); - foreach ($events as $key => $events) { + foreach ($events as $key => $event) { $registrantsList = CRM_CivirulesActions_Participant_AddToZoom::getZoomRegistrants($event['id']); if(!empty($registrantsList)){ $recentRegistrants = CRM_NcnCiviZoom_Utils::filterZoomRegistrantsByTime($registrantsList, $params['mins']); From b03db64ec6be5e394d7d38ed179498e63e8141ac Mon Sep 17 00:00:00 2001 From: Damodharan Date: Fri, 13 Nov 2020 15:34:17 +0000 Subject: [PATCH 08/11] Minor fix to pick the template id. --- CRM/NcnCiviZoom/Utils.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CRM/NcnCiviZoom/Utils.php b/CRM/NcnCiviZoom/Utils.php index 0ebfd0b..8b08e80 100644 --- a/CRM/NcnCiviZoom/Utils.php +++ b/CRM/NcnCiviZoom/Utils.php @@ -483,7 +483,7 @@ public static function forUpgrade1004(){ ]); $zoomSettings = self::getZoomSettings(); if(!empty($templateDetails['id'])){ - $settings['registrants_email_template_id'] = $templateDetails['id']; + $zoomSettings['registrants_email_template_id'] = $templateDetails['id']; } CRM_Core_BAO_Setting::setItem($zoomSettings, ZOOM_SETTINGS, 'zoom_settings'); } From 26ab3a1c195cb9af513b871a383b3cc94dd558a1 Mon Sep 17 00:00:00 2001 From: Damodharan Date: Fri, 13 Nov 2020 17:56:20 +0000 Subject: [PATCH 09/11] Added fixes for the bugs mentioned in the ticket: #17011. --- .../Participant/AddToZoom.php | 61 +++++- README.md | 3 +- api/v3/Zoomevent.php | 174 ++++++++++-------- info.xml | 4 +- ncn_civi_zoom.php | 2 +- 5 files changed, 155 insertions(+), 89 deletions(-) diff --git a/CRM/CivirulesActions/Participant/AddToZoom.php b/CRM/CivirulesActions/Participant/AddToZoom.php index e83dc00..47220a1 100644 --- a/CRM/CivirulesActions/Participant/AddToZoom.php +++ b/CRM/CivirulesActions/Participant/AddToZoom.php @@ -239,31 +239,78 @@ public static function getZoomRegistrants($eventId){ $settings = CRM_NcnCiviZoom_Utils::getZoomSettings(); if(!empty($meetingId)){ - $url = $settings['base_url'] . "/meetings/".$meetingId.'/registrants?page='; + $url = $settings['base_url'] . "/meetings/".$meetingId.'/registrants?'; } elseif (!empty($webinarId)) { - $url = $settings['base_url'] . "/webinars/".$webinarId.'/registrants?page='; + $url = $settings['base_url'] . "/webinars/".$webinarId.'/registrants?'; } $page = 1; $token = $object->createJWTToken($accountId); $result = []; + $next_page_token = null; do { - $fetchUrl = $url.$page; + $fetchUrl = $url.$next_page_token; $token = $object->createJWTToken($accountId); $response = Zttp::withHeaders([ 'Content-Type' => 'application/json;charset=UTF-8', 'Authorization' => "Bearer $token" ])->get($fetchUrl); - CRM_Core_Error::debug_var('zoom response', $response); $result = $response->json(); + CRM_Core_Error::debug_var('zoom result', $result); if(!empty($result['registrants'])){ - $zoomRegistrantsList = array_merge($result['registrants'], $zoomRegistrantsList); + $zoomRegistrantsList = array_merge($zoomRegistrantsList, $result['registrants']); } - $page++; - } while ($page <= $result['page_count']); + $next_page_token = 'next_page_token='.$result['next_page_token']; + } while ($result['next_page_token']); return $zoomRegistrantsList; } + public static function getZoomAttendeeOrAbsenteesList($eventId){ + if(empty($eventId)){ + return []; + } + $object = new CRM_CivirulesActions_Participant_AddToZoom; + $webinarId = $object->getWebinarID($eventId); + $meetingId = $object->getMeetingID($eventId); + $returnZoomList = []; + if(empty($webinarId) && empty($meetingId)){ + return $returnZoomList; + } + $url = $array_name = $key_name = ''; + $accountId = CRM_NcnCiviZoom_Utils::getZoomAccountIdByEventId($eventId); + $settings = CRM_NcnCiviZoom_Utils::getZoomSettings(); + if(!empty($meetingId)){ + $url = $settings['base_url'] . "/past_meetings/$meetingId/participants?"; + $array_name = 'participants'; + $key_name = 'user_email'; + } elseif (!empty($webinarId)) { + $url = $settings['base_url'] . "/past_webinars/$webinarId/absentees?"; + $array_name = 'absentees'; + $key_name = 'email'; + } + $token = $object->createJWTToken($accountId); + $result = []; + $next_page_token = null; + do { + $fetchUrl = $url.$next_page_token; + $token = $object->createJWTToken($accountId); + $response = Zttp::withHeaders([ + 'Content-Type' => 'application/json;charset=UTF-8', + 'Authorization' => "Bearer $token" + ])->get($fetchUrl); + $result = $response->json(); + CRM_Core_Error::debug_var('zoom result', $result); + if(!empty($result[$array_name])){ + $list = $result[$array_name]; + foreach ($list as $item) { + $returnZoomList[] = $item[$key_name]; + } + } + $next_page_token = 'next_page_token='.$result['next_page_token']; + } while ($result['next_page_token']); + return $returnZoomList; + } + /** * Method to return the url for additional form processing for action * and return false if none is needed diff --git a/README.md b/README.md index 037125c..41ef44d 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ + # ncn-civi-zoom Civirules Conditions/Actions that talk with Zoom developed for NCN. @@ -92,5 +93,5 @@ Once you've decided this you can create a new CiviRule as per the screen shot. * An example has been done below![Screenshot of Scheduled Job](images/generate-zoom-attendance) ### Scheduled Job for emailing new Zoom registrants -* Once you've created a zoom event, you need to create a scheduled job for that event. The Api Entity should be **Zoom Event** and the api action should be **Getrecentzoomregistrants**. This api has two parameters one is 'mins' i.e registrants registered that many 'minutes' before will be filtered and will be taken for updation and for sending email. The other parameter is the Email address to which you want the regitrants list to be sent, for multiple email addresses seperate each by a comma symbol. +* Once you've created a zoom event, you need to create a scheduled job for that event. The Api Entity should be **Zoom Event** and the api action should be **Getrecentzoomregistrants**. This api has two parameters one is ***mins*** i.e registrants registered that many 'minutes' before will be filtered and will be taken for updation and for sending email. The other parameter is the ***to_emails*** , which is the Email address to which you want the regitrants list to be sent, for multiple email addresses seperate each by a comma(,) symbol. * An example has been done below![Screenshot of Scheduled Job](images/email-zoom-registrants.PNG) diff --git a/api/v3/Zoomevent.php b/api/v3/Zoomevent.php index 29d9a95..624ddfd 100644 --- a/api/v3/Zoomevent.php +++ b/api/v3/Zoomevent.php @@ -62,90 +62,108 @@ function civicrm_api3_zoomevent_generatezoomattendance($params) { $eventIds[] = $value['id']; } foreach ($eventIds as $eventId) { - $settings = CRM_NcnCiviZoom_Utils::getZoomSettingsByEventId($eventId); - $key = $settings['secret_key']; - $payload = array( - "iss" => $settings['api_key'], - "exp" => strtotime('+1 hour') - ); - $jwt = JWT::encode($payload, $key); + CRM_Core_Error::debug_var('eventId', $eventId); + $list = CRM_CivirulesActions_Participant_AddToZoom::getZoomAttendeeOrAbsenteesList($eventId); + if(empty($list)){ + continue; + } $webinarId = getWebinarID($eventId); $meetingId = getMeetingID($eventId); - $page = 0; if(!empty($webinarId)){ - $entityId = $webinarId; - $url = $settings['base_url'] . "/past_webinars/$webinar/absentees?page=$page"; - $entity = "Webinar"; + $attendees = selectAttendees($list, $eventId, "Webinar"); }elseif(!empty($meetingId)){ - $entityId = $meetingId; - $url = $settings['base_url'] . "/past_meetings/$meetingId/participants?page=$page"; - $entity = "Meeting"; - }else{ - continue; - } - - $token = $jwt; - // Get absentees from Zoom API - $response = Zttp::withHeaders([ - 'Content-Type' => 'application/json;charset=UTF-8', - 'Authorization' => "Bearer $token" - ])->get($url); - - $attendees = []; - if($entity == "Webinar"){ - $pages = $response->json()['page_count']; - - // Store registrants who did not attend the webinar - $absentees = $response->json()['registrants']; - - $absenteesEmails = []; - - while($page < $pages) { - foreach($absentees as $absentee) { - $email = $absentee['email']; - - array_push($absenteesEmails, "'$email'"); - } - - $attendees = array_merge($attendees, selectAttendees($absenteesEmails, $eventId)); - - $page++; - - // Get and loop through all of webinar registrants - $url = $settings['base_url'] . "/past_webinars/$webinar/absentees?page=$page"; - - // Get absentees from Zoom API - $response = Zttp::withHeaders([ - 'Content-Type' => 'application/json;charset=UTF-8', - 'Authorization' => "Bearer $token" - ])->get($url); - - // Store registrants who did not attend the webinar - $absentees = $response->json()['registrants']; - - $absenteesEmails = []; - } - }elseif ($entity == "Meeting") { - $attendeesEmails = []; - $page = 1; - do { - $url = $settings['base_url'] . "/past_meetings/$meetingId/participants?page=$page"; - // Get absentees from Zoom API - $response = Zttp::withHeaders([ - 'Content-Type' => 'application/json;charset=UTF-8', - 'Authorization' => "Bearer $token" - ])->get($url); - $participants = $response->json()['participants']; - foreach ($participants as $key => $value) { - $attendeesEmails[] = $value['user_email']; - } - $page++; - $pageCount = $response->json()['page_count']; - } while ($page <= $pageCount); - $attendees = selectAttendees($attendeesEmails, $eventId, "Meeting"); + $attendees = selectAttendees($list, $eventId, "Meeting"); } updateAttendeesStatus($attendees, $eventId); - $allAttendees[] = $attendees; + $allAttendees[$eventId] = $attendees; + // $zoomAccountId = CRM_NcnCiviZoom_Utils::getZoomAccountIdByEventId($eventId); + // if(empty($zoomAccountId)){ + // continue; + // } + // $settings = CRM_NcnCiviZoom_Utils::getZoomSettings($zoomAccountId); + // $key = $settings['secret_key']; + // $payload = array( + // "iss" => $settings['api_key'], + // "exp" => strtotime('+1 hour') + // ); + // $jwt = JWT::encode($payload, $key); + // $webinarId = getWebinarID($eventId); + // $meetingId = getMeetingID($eventId); + // $page = 0; + // if(!empty($webinarId)){ + // $entityId = $webinarId; + // $url = $settings['base_url'] . "/past_webinars/$webinar/absentees?page=$page"; + // $entity = "Webinar"; + // }elseif(!empty($meetingId)){ + // $entityId = $meetingId; + // $url = $settings['base_url'] . "/past_meetings/$meetingId/participants?page=$page"; + // $entity = "Meeting"; + // }else{ + // continue; + // } + + // $token = $jwt; + // // Get absentees from Zoom API + // $response = Zttp::withHeaders([ + // 'Content-Type' => 'application/json;charset=UTF-8', + // 'Authorization' => "Bearer $token" + // ])->get($url); + + // $attendees = []; + // if($entity == "Webinar"){ + // $pages = $response->json()['page_count']; + + // // Store registrants who did not attend the webinar + // $absentees = $response->json()['registrants']; + + // $absenteesEmails = []; + + // while($page < $pages) { + // foreach($absentees as $absentee) { + // $email = $absentee['email']; + + // array_push($absenteesEmails, "'$email'"); + // } + + // $attendees = array_merge($attendees, selectAttendees($absenteesEmails, $eventId)); + + // $page++; + + // // Get and loop through all of webinar registrants + // $url = $settings['base_url'] . "/past_webinars/$webinar/absentees?page=$page"; + + // // Get absentees from Zoom API + // $response = Zttp::withHeaders([ + // 'Content-Type' => 'application/json;charset=UTF-8', + // 'Authorization' => "Bearer $token" + // ])->get($url); + + // // Store registrants who did not attend the webinar + // $absentees = $response->json()['registrants']; + + // $absenteesEmails = []; + // } + // }elseif ($entity == "Meeting") { + // $attendeesEmails = []; + // $page = 1; + // do { + // $url = $settings['base_url'] . "/past_meetings/$meetingId/participants?page=$page"; + // // Get absentees from Zoom API + // $response = Zttp::withHeaders([ + // 'Content-Type' => 'application/json;charset=UTF-8', + // 'Authorization' => "Bearer $token" + // ])->get($url); + // $participants = $response->json()['participants']; + // foreach ($participants as $key => $value) { + // $attendeesEmails[] = $value['user_email']; + // } + // $page++; + // $pageCount = $response->json()['page_count']; + // } while ($page <= $pageCount); + // $attendees = selectAttendees($attendeesEmails, $eventId, "Meeting"); + // } + // updateAttendeesStatus($attendees, $eventId); + // $allAttendees[] = $attendees; } $return['allAttendees'] = $allAttendees; diff --git a/info.xml b/info.xml index 19f511d..c087e34 100644 --- a/info.xml +++ b/info.xml @@ -7,8 +7,8 @@ Rupert Amodia hello@vedaconsulting.co.uk - 2020-11-12 - 2.1 + 2020-11-16 + 2.2 org.civicoop.civirules diff --git a/ncn_civi_zoom.php b/ncn_civi_zoom.php index cfe19d8..bca9b96 100644 --- a/ncn_civi_zoom.php +++ b/ncn_civi_zoom.php @@ -195,7 +195,7 @@ function ncn_civi_zoom_civicrm_validateForm($formName, &$fields, &$files, &$form // Throw error if tried to delete the created message template if($formName == 'CRM_Admin_Form_MessageTemplates'){ $submitValues = $form->getVar('_submitValues'); - if($submitValues['_qf_MessageTemplates_upload'] == 'Delete'){ + if( !empty($submitValues['_qf_MessageTemplates_upload']) && ($submitValues['_qf_MessageTemplates_upload'] == 'Delete')){ $FormValues = $form->getVar('_values'); $msgTitle = CRM_NcnCiviZoom_Constants::SEND_ZOOM_REGISTRANTS_EMAIL_TEMPLATE_TITLE; $msgId = CRM_NcnCiviZoom_Utils::getEmailTemplateIdToSendZoomRegistrants(); From c5722e58ab81c18ccbca2529e2b629b744263dd7 Mon Sep 17 00:00:00 2001 From: Damodharan Date: Fri, 13 Nov 2020 18:15:09 +0000 Subject: [PATCH 10/11] Added the documentation for the scheduled jobs. --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 41ef44d..2fb4730 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ + # ncn-civi-zoom Civirules Conditions/Actions that talk with Zoom developed for NCN. @@ -89,9 +90,9 @@ Once you've decided this you can create a new CiviRule as per the screen shot. ## Creating Scheduled Jobs ### Scheduled Job for Zoom attendance -* Once you've created a zoom event, you need to create a scheduled job for that event. The Api Entity should be **Zoom Event** and the api action should be **Generatezoomattendance**. This api has only one parameter which is 'days'. This will be used the events using the event's end date i.e the events ended 'x' number of days from current date, where 'x' is the 'days' paramter you enter. You can schedule the Job as frequent as you need it to run. -* An example has been done below![Screenshot of Scheduled Job](images/generate-zoom-attendance) +* Once you've created a zoom event, you need to create a scheduled job for that event. The Api Entity should be **Zoomevent** and the api action should be **Generatezoomattendance**. This api has only one parameter which is ***days*** this will be used to pickup the events which ended up within that number of days given(using the event's end date). For example if the days=10, then events(in your civi) ended within past 10 days from current date will be picked up and only for these events the participants' status will be picked up from the zoom and updated in the civicrm. You can schedule the Job as frequent as you need it to run. +* An example has been done below![Screenshot of Scheduled Job](images/generate-zoom-attendance.PNG) ### Scheduled Job for emailing new Zoom registrants -* Once you've created a zoom event, you need to create a scheduled job for that event. The Api Entity should be **Zoom Event** and the api action should be **Getrecentzoomregistrants**. This api has two parameters one is ***mins*** i.e registrants registered that many 'minutes' before will be filtered and will be taken for updation and for sending email. The other parameter is the ***to_emails*** , which is the Email address to which you want the regitrants list to be sent, for multiple email addresses seperate each by a comma(,) symbol. +* Once you've created a zoom event, you need to create a scheduled job for that event. The Api Entity should be **Zoomevent** and the api action should be **Getrecentzoomregistrants**. This api has two parameters one is ***mins*** i.e registrants who registered that many 'minutes' before will be filtered and their details(such as First name, Last name and Email) will be updated to the 'Event Zoom Notes' custom field under that civi event. The other parameter is the ***to_emails*** , which is the Email address to which you want the filtered regitrants list could be sent, for multiple email addresses seperate each by a comma(,) symbol. * An example has been done below![Screenshot of Scheduled Job](images/email-zoom-registrants.PNG) From 30b90cc31068a8db9353c8fb2ce86737c08d0eb9 Mon Sep 17 00:00:00 2001 From: Damodharan Date: Tue, 17 Nov 2020 18:00:17 +0000 Subject: [PATCH 11/11] A minor fix for the settings. --- CRM/NcnCiviZoom/Form/Settings.php | 1 + 1 file changed, 1 insertion(+) diff --git a/CRM/NcnCiviZoom/Form/Settings.php b/CRM/NcnCiviZoom/Form/Settings.php index 763efc3..b315e05 100644 --- a/CRM/NcnCiviZoom/Form/Settings.php +++ b/CRM/NcnCiviZoom/Form/Settings.php @@ -258,6 +258,7 @@ public function postProcess() { } if(empty($this->_act) && empty($this->_id)){ + $zoomSettings = CRM_NcnCiviZoom_Utils::getZoomSettings(); $zoomSettings['base_url'] = $values['base_url']; $zoomSettings['custom_field_id_webinar'] = $values['custom_field_id_webinar']; $zoomSettings['custom_field_id_meeting'] = $values['custom_field_id_meeting'];