From 49c5ad7a584098a42839de88881cec9f5055c1d2 Mon Sep 17 00:00:00 2001 From: Brian Hogg Date: Tue, 19 Aug 2025 07:33:19 -0400 Subject: [PATCH 01/14] WIP: Adding certificate earned engagement via user_earned_certificate action. --- .../class.llms.meta.box.engagement.php | 6 +++++ includes/class.llms.engagements.php | 4 +++ includes/llms.functions.core.php | 27 ++++++++++--------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/includes/admin/post-types/meta-boxes/class.llms.meta.box.engagement.php b/includes/admin/post-types/meta-boxes/class.llms.meta.box.engagement.php index a182935e73..b1f98f06a8 100644 --- a/includes/admin/post-types/meta-boxes/class.llms.meta.box.engagement.php +++ b/includes/admin/post-types/meta-boxes/class.llms.meta.box.engagement.php @@ -112,6 +112,12 @@ public function get_fields() { 'label' => __( 'Select a Section', 'lifterlms' ), ), + 'certificate' => array( + 'controller_value' => array( 'user_earned_certificate' ), + 'id' => '_faux_engagement_trigger_post_llms_certificate', + 'label' => __( 'Select a Certificate', 'lifterlms' ), + ), + ); foreach ( $trigger_post_fields as $post_type => $data ) { diff --git a/includes/class.llms.engagements.php b/includes/class.llms.engagements.php index 9c44b485e2..04a0144df4 100644 --- a/includes/class.llms.engagements.php +++ b/includes/class.llms.engagements.php @@ -443,6 +443,10 @@ private function parse_hook_find_trigger_type( $action, $related_post_id ) { case 'lifterlms_product_purchased': $trigger_type = str_replace( 'llms_', '', get_post_type( $related_post_id ) ) . '_purchased'; break; + + case 'llms_user_earned_certificate': + $trigger_type = str_replace( 'llms_', '', $action ); + break; } return $trigger_type; diff --git a/includes/llms.functions.core.php b/includes/llms.functions.core.php index ebd4babb1e..6688b2f5d4 100644 --- a/includes/llms.functions.core.php +++ b/includes/llms.functions.core.php @@ -455,20 +455,21 @@ function llms_get_engagement_triggers() { return apply_filters( 'lifterlms_engagement_triggers', array( - 'user_registration' => __( 'Student creates a new account', 'lifterlms' ), - 'access_plan_purchased' => __( 'Student Purchases an Access Plan', 'lifterlms' ), - 'course_enrollment' => __( 'Student enrolls in a course', 'lifterlms' ), - 'course_purchased' => __( 'Student purchases a course', 'lifterlms' ), - 'course_completed' => __( 'Student completes a course', 'lifterlms' ), + 'user_registration' => __( 'Student creates a new account', 'lifterlms' ), + 'access_plan_purchased' => __( 'Student Purchases an Access Plan', 'lifterlms' ), + 'course_enrollment' => __( 'Student enrolls in a course', 'lifterlms' ), + 'course_purchased' => __( 'Student purchases a course', 'lifterlms' ), + 'course_completed' => __( 'Student completes a course', 'lifterlms' ), // 'days_since_login' => __( 'Days since user last logged in', 'lifterlms' ), // @todo. - 'lesson_completed' => __( 'Student completes a lesson', 'lifterlms' ), - 'quiz_completed' => __( 'Student completes a quiz', 'lifterlms' ), - 'quiz_passed' => __( 'Student passes a quiz', 'lifterlms' ), - 'quiz_failed' => __( 'Student fails a quiz', 'lifterlms' ), - 'section_completed' => __( 'Student completes a section', 'lifterlms' ), - 'course_track_completed' => __( 'Student completes a course track', 'lifterlms' ), - 'membership_enrollment' => __( 'Student enrolls in a membership', 'lifterlms' ), - 'membership_purchased' => __( 'Student purchases a membership', 'lifterlms' ), + 'lesson_completed' => __( 'Student completes a lesson', 'lifterlms' ), + 'quiz_completed' => __( 'Student completes a quiz', 'lifterlms' ), + 'quiz_passed' => __( 'Student passes a quiz', 'lifterlms' ), + 'quiz_failed' => __( 'Student fails a quiz', 'lifterlms' ), + 'section_completed' => __( 'Student completes a section', 'lifterlms' ), + 'course_track_completed' => __( 'Student completes a course track', 'lifterlms' ), + 'membership_enrollment' => __( 'Student enrolls in a membership', 'lifterlms' ), + 'membership_purchased' => __( 'Student purchases a membership', 'lifterlms' ), + 'user_earned_certificate' => __( 'Student earns a certificate', 'lifterlms' ), ) ); } From 375040e2dec7a5e88c02e8387e4944d09a02ee07 Mon Sep 17 00:00:00 2001 From: Brian Hogg Date: Tue, 19 Aug 2025 14:58:13 -0400 Subject: [PATCH 02/14] Fixing to show the certificate(s) available for engagement. --- .../post-types/meta-boxes/class.llms.meta.box.engagement.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/admin/post-types/meta-boxes/class.llms.meta.box.engagement.php b/includes/admin/post-types/meta-boxes/class.llms.meta.box.engagement.php index b1f98f06a8..a5b6f625fb 100644 --- a/includes/admin/post-types/meta-boxes/class.llms.meta.box.engagement.php +++ b/includes/admin/post-types/meta-boxes/class.llms.meta.box.engagement.php @@ -112,7 +112,7 @@ public function get_fields() { 'label' => __( 'Select a Section', 'lifterlms' ), ), - 'certificate' => array( + 'llms_certificate' => array( 'controller_value' => array( 'user_earned_certificate' ), 'id' => '_faux_engagement_trigger_post_llms_certificate', 'label' => __( 'Select a Certificate', 'lifterlms' ), From c94076f6326bfa68ed942bfa4c625bc14a42cfa5 Mon Sep 17 00:00:00 2001 From: Brian Hogg Date: Tue, 19 Aug 2025 15:20:34 -0400 Subject: [PATCH 03/14] Adding case to save the trigger post ID. --- .../post-types/meta-boxes/class.llms.meta.box.engagement.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/includes/admin/post-types/meta-boxes/class.llms.meta.box.engagement.php b/includes/admin/post-types/meta-boxes/class.llms.meta.box.engagement.php index a5b6f625fb..dce7c276ae 100644 --- a/includes/admin/post-types/meta-boxes/class.llms.meta.box.engagement.php +++ b/includes/admin/post-types/meta-boxes/class.llms.meta.box.engagement.php @@ -330,6 +330,10 @@ public function save( $post_id ) { $var = 'track'; break; + case 'user_earned_certificate': + $var = 'llms_certificate'; + break; + default: $var = false; From 8a9c8be01072246b4cfd49ce4f6d83fb7e697c00 Mon Sep 17 00:00:00 2001 From: Brian Hogg Date: Tue, 19 Aug 2025 16:30:15 -0400 Subject: [PATCH 04/14] Add hook for earned certificate. --- includes/class.llms.engagements.php | 1 + 1 file changed, 1 insertion(+) diff --git a/includes/class.llms.engagements.php b/includes/class.llms.engagements.php index 04a0144df4..46a62d0c4f 100644 --- a/includes/class.llms.engagements.php +++ b/includes/class.llms.engagements.php @@ -232,6 +232,7 @@ protected function get_trigger_hooks() { 'llms_rest_student_registered', 'llms_user_added_to_membership_level', 'llms_user_enrolled_in_course', + 'llms_user_earned_certificate', ); // If there are any actions registered to this deprecated hook, add it to the list. From f418d5dbb6d96ecacef04a51d154c6366bbe8512 Mon Sep 17 00:00:00 2001 From: Brian Hogg Date: Tue, 19 Aug 2025 16:35:31 -0400 Subject: [PATCH 05/14] WIp: Adding action for lifterlms PDF and others to hook into for adding attachements etc. --- includes/emails/class.llms.email.engagement.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/includes/emails/class.llms.email.engagement.php b/includes/emails/class.llms.email.engagement.php index 68b186a396..4ad94ddec8 100644 --- a/includes/emails/class.llms.email.engagement.php +++ b/includes/emails/class.llms.email.engagement.php @@ -82,7 +82,12 @@ public function init( $args ) { $this->add_recipient( $email, $type ); } } - + /** + * Action to allow modification of the email object before it is sent. + * + * @since [version] + */ + do_action( 'llms_email_engagement_init', $this, $args ); } /** @@ -109,7 +114,6 @@ private function merge_emails( $list ) { $merged = str_replace( $codes, $addresses, $list ); $array = explode( ',', $merged ); return array_map( 'trim', $array ); - } /** @@ -128,7 +132,6 @@ public function send() { remove_filter( 'llms_user_info_shortcode_user_id', array( $this, 'set_shortcode_user' ) ); return $ret; - } /** @@ -142,5 +145,4 @@ public function send() { public function set_shortcode_user( $uid ) { return $this->student->ID; } - } From c18dde65492250994a8dafac417b8e1d8a2defa8 Mon Sep 17 00:00:00 2001 From: Brian Hogg Date: Tue, 19 Aug 2025 16:36:48 -0400 Subject: [PATCH 06/14] Allow a null trigger post, so the engagement could be created once to match any. --- includes/class.llms.engagements.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class.llms.engagements.php b/includes/class.llms.engagements.php index 46a62d0c4f..842c9c1bb8 100644 --- a/includes/class.llms.engagements.php +++ b/includes/class.llms.engagements.php @@ -151,7 +151,7 @@ private function get_engagements( $trigger_type, $related_post_id = '' ) { $related_select = ', relation_meta.meta_value AS related_post_id'; $related_join = "LEFT JOIN $wpdb->postmeta AS relation_meta ON triggers.ID = relation_meta.post_id"; - $related_where = $wpdb->prepare( "AND relation_meta.meta_key = '_llms_engagement_trigger_post' AND relation_meta.meta_value = %d", $related_post_id ); + $related_where = $wpdb->prepare( "AND relation_meta.meta_key = '_llms_engagement_trigger_post' AND ( relation_meta.meta_value IS NULL OR relation_meta.meta_value = %d )", $related_post_id ); } From 5b1d7612bdf5d3fabee3533593bbf8755e4952a7 Mon Sep 17 00:00:00 2001 From: Brian Hogg Date: Thu, 21 Aug 2025 09:48:42 -0400 Subject: [PATCH 07/14] WIP: Adding email notification type for certificates. --- ...fication.controller.certificate.earned.php | 28 ++++++------------- ...s.notification.view.certificate.earned.php | 22 +++------------ 2 files changed, 13 insertions(+), 37 deletions(-) diff --git a/includes/notifications/controllers/class.llms.notification.controller.certificate.earned.php b/includes/notifications/controllers/class.llms.notification.controller.certificate.earned.php index aa6ad7883d..9295a8a76b 100644 --- a/includes/notifications/controllers/class.llms.notification.controller.certificate.earned.php +++ b/includes/notifications/controllers/class.llms.notification.controller.certificate.earned.php @@ -38,6 +38,9 @@ class LLMS_Notification_Controller_Certificate_Earned extends LLMS_Abstract_Noti */ protected $action_hooks = array( 'llms_user_earned_certificate' ); + + public $certificate; + /** * Callback function, called upon certificate post generation * @@ -53,9 +56,9 @@ public function action_callback( $user_id = null, $certificate_id = null, $relat $this->user_id = $user_id; $this->post_id = $certificate_id; $this->related_post_id = $related_post_id; + $this->certificate = llms_get_certificate( $certificate_id ); $this->send(); - } /** @@ -80,7 +83,6 @@ protected function get_subscriber( $subscriber ) { } return $uid; - } /** @@ -113,27 +115,15 @@ protected function set_subscriber_options( $type ) { $options[] = $this->get_subscriber_option_array( 'student', 'yes' ); break; + case 'email': + $options[] = $this->get_subscriber_option_array( 'student', 'no' ); + $options[] = $this->get_subscriber_option_array( 'custom', 'no' ); + break; + } return $options; - } - - /** - * Determine what types are supported - * Extending classes can override this function in order to add or remove support - * 3rd parties should add support via filter on $this->get_supported_types() - * - * @return array associative array, keys are the ID/db type, values should be translated display types - * @since 3.8.0 - * @version 3.8.0 - */ - protected function set_supported_types() { - return array( - 'basic' => __( 'Basic', 'lifterlms' ), - ); - } - } return LLMS_Notification_Controller_Certificate_Earned::instance(); diff --git a/includes/notifications/views/class.llms.notification.view.certificate.earned.php b/includes/notifications/views/class.llms.notification.view.certificate.earned.php index 3bfadaa2ab..98a8bb1477 100644 --- a/includes/notifications/views/class.llms.notification.view.certificate.earned.php +++ b/includes/notifications/views/class.llms.notification.view.certificate.earned.php @@ -82,6 +82,9 @@ private function get_mini_html( $title ) { * @return string */ protected function set_body() { + if ( 'email' === $this->notification->get( 'type' ) ) { + return sprintf( __( 'Congratulations! %1$s earned a certificate: %2$s.', 'lifterlms' ) . "\n\n{{MINI_CERTIFICATE}}", '{{STUDENT_NAME}}', '{{CERTIFICATE_TITLE}}' ); + } return '{{MINI_CERTIFICATE}}'; } @@ -213,7 +216,7 @@ private function set_merge_data_student_name( $cert ) { * @return string */ protected function set_subject() { - return ''; + return 'Certificate Earned: {{CERTIFICATE_TITLE}}'; } /** @@ -226,21 +229,4 @@ protected function set_subject() { protected function set_title() { return __( 'You\'ve earned a certificate!', 'lifterlms' ); } - - /** - * Defines field support for the view. - * - * @since 3.8.0 - * - * @return array - */ - protected function set_supported_fields() { - return array( - 'basic' => array( - 'body' => true, - 'title' => true, - 'icon' => true, - ), - ); - } } From 9d7ac8c376bfba270ce687a751d4000181be3c68 Mon Sep 17 00:00:00 2001 From: Brian Hogg Date: Thu, 21 Aug 2025 10:36:40 -0400 Subject: [PATCH 08/14] Adding action before a notification is scheduled for sending. --- .../class.llms.notification.controller.certificate.earned.php | 4 ---- .../processors/class.llms.notification.processor.email.php | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/includes/notifications/controllers/class.llms.notification.controller.certificate.earned.php b/includes/notifications/controllers/class.llms.notification.controller.certificate.earned.php index 9295a8a76b..206160bf85 100644 --- a/includes/notifications/controllers/class.llms.notification.controller.certificate.earned.php +++ b/includes/notifications/controllers/class.llms.notification.controller.certificate.earned.php @@ -38,9 +38,6 @@ class LLMS_Notification_Controller_Certificate_Earned extends LLMS_Abstract_Noti */ protected $action_hooks = array( 'llms_user_earned_certificate' ); - - public $certificate; - /** * Callback function, called upon certificate post generation * @@ -56,7 +53,6 @@ public function action_callback( $user_id = null, $certificate_id = null, $relat $this->user_id = $user_id; $this->post_id = $certificate_id; $this->related_post_id = $related_post_id; - $this->certificate = llms_get_certificate( $certificate_id ); $this->send(); } diff --git a/includes/notifications/processors/class.llms.notification.processor.email.php b/includes/notifications/processors/class.llms.notification.processor.email.php index 021fc50d9b..4bef2ba6b7 100644 --- a/includes/notifications/processors/class.llms.notification.processor.email.php +++ b/includes/notifications/processors/class.llms.notification.processor.email.php @@ -63,6 +63,8 @@ protected function task( $notification_id ) { $mailer->set_subject( $view->get_subject() )->set_heading( $view->get_title() )->set_body( $view->get_html() ); + do_action( 'llms_notification_email_before_send', $notification, $mailer ); + } catch ( Error $e ) { $this->log( sprintf( 'Error sending email notification ID #%d', $notification_id ) ); $this->log( sprintf( 'Error caught %1$s in %2$s on line %3$s', $e->getMessage(), $e->getFile(), $e->getLine() ) ); @@ -79,9 +81,7 @@ protected function task( $notification_id ) { } return false; - } - } return new LLMS_Notification_Processor_Email(); From 468b42053a857c997d93eb77a928bde312497046 Mon Sep 17 00:00:00 2001 From: Brian Hogg Date: Tue, 18 Nov 2025 07:42:50 -0500 Subject: [PATCH 09/14] Update class.llms.email.engagement.php Removing new unneeded action since we're not attaching PDFs --- includes/emails/class.llms.email.engagement.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/includes/emails/class.llms.email.engagement.php b/includes/emails/class.llms.email.engagement.php index 4ad94ddec8..8114228dd8 100644 --- a/includes/emails/class.llms.email.engagement.php +++ b/includes/emails/class.llms.email.engagement.php @@ -82,12 +82,6 @@ public function init( $args ) { $this->add_recipient( $email, $type ); } } - /** - * Action to allow modification of the email object before it is sent. - * - * @since [version] - */ - do_action( 'llms_email_engagement_init', $this, $args ); } /** @@ -114,6 +108,7 @@ private function merge_emails( $list ) { $merged = str_replace( $codes, $addresses, $list ); $array = explode( ',', $merged ); return array_map( 'trim', $array ); + } /** @@ -132,6 +127,7 @@ public function send() { remove_filter( 'llms_user_info_shortcode_user_id', array( $this, 'set_shortcode_user' ) ); return $ret; + } /** @@ -145,4 +141,5 @@ public function send() { public function set_shortcode_user( $uid ) { return $this->student->ID; } + } From ca7a0799ffbb613e55fb45c18152d4bb9cb2527a Mon Sep 17 00:00:00 2001 From: Brian Hogg Date: Tue, 18 Nov 2025 07:43:17 -0500 Subject: [PATCH 10/14] Update class.llms.email.engagement.php --- includes/emails/class.llms.email.engagement.php | 1 + 1 file changed, 1 insertion(+) diff --git a/includes/emails/class.llms.email.engagement.php b/includes/emails/class.llms.email.engagement.php index 8114228dd8..d4f8d54c46 100644 --- a/includes/emails/class.llms.email.engagement.php +++ b/includes/emails/class.llms.email.engagement.php @@ -82,6 +82,7 @@ public function init( $args ) { $this->add_recipient( $email, $type ); } } + } /** From e2a50d3994e00efafa9f082a774807a4ad7d0b5b Mon Sep 17 00:00:00 2001 From: Brian Hogg Date: Tue, 18 Nov 2025 07:43:34 -0500 Subject: [PATCH 11/14] Update class.llms.email.engagement.php --- includes/emails/class.llms.email.engagement.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/emails/class.llms.email.engagement.php b/includes/emails/class.llms.email.engagement.php index d4f8d54c46..68b186a396 100644 --- a/includes/emails/class.llms.email.engagement.php +++ b/includes/emails/class.llms.email.engagement.php @@ -82,7 +82,7 @@ public function init( $args ) { $this->add_recipient( $email, $type ); } } - + } /** From 7edea4a3d8635aee896def5b640deef86369fab8 Mon Sep 17 00:00:00 2001 From: Brian Hogg Date: Tue, 18 Nov 2025 07:46:32 -0500 Subject: [PATCH 12/14] Adding translation helper string. --- .../views/class.llms.notification.view.certificate.earned.php | 1 + 1 file changed, 1 insertion(+) diff --git a/includes/notifications/views/class.llms.notification.view.certificate.earned.php b/includes/notifications/views/class.llms.notification.view.certificate.earned.php index 98a8bb1477..127cfbca97 100644 --- a/includes/notifications/views/class.llms.notification.view.certificate.earned.php +++ b/includes/notifications/views/class.llms.notification.view.certificate.earned.php @@ -83,6 +83,7 @@ private function get_mini_html( $title ) { */ protected function set_body() { if ( 'email' === $this->notification->get( 'type' ) ) { + // Translators: %1$s - student name, %2$s - Certificate title. return sprintf( __( 'Congratulations! %1$s earned a certificate: %2$s.', 'lifterlms' ) . "\n\n{{MINI_CERTIFICATE}}", '{{STUDENT_NAME}}', '{{CERTIFICATE_TITLE}}' ); } return '{{MINI_CERTIFICATE}}'; From 96214164fa96da7c76e78b54b68bbe42e5518914 Mon Sep 17 00:00:00 2001 From: Brian Hogg Date: Tue, 18 Nov 2025 08:52:47 -0500 Subject: [PATCH 13/14] Update class.llms.notification.processor.email.php Removing new action not being used. --- .../processors/class.llms.notification.processor.email.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/notifications/processors/class.llms.notification.processor.email.php b/includes/notifications/processors/class.llms.notification.processor.email.php index 4bef2ba6b7..021fc50d9b 100644 --- a/includes/notifications/processors/class.llms.notification.processor.email.php +++ b/includes/notifications/processors/class.llms.notification.processor.email.php @@ -63,8 +63,6 @@ protected function task( $notification_id ) { $mailer->set_subject( $view->get_subject() )->set_heading( $view->get_title() )->set_body( $view->get_html() ); - do_action( 'llms_notification_email_before_send', $notification, $mailer ); - } catch ( Error $e ) { $this->log( sprintf( 'Error sending email notification ID #%d', $notification_id ) ); $this->log( sprintf( 'Error caught %1$s in %2$s on line %3$s', $e->getMessage(), $e->getFile(), $e->getLine() ) ); @@ -81,7 +79,9 @@ protected function task( $notification_id ) { } return false; + } + } return new LLMS_Notification_Processor_Email(); From 7fd628287d4eaafb548e5806666a209e80fa438f Mon Sep 17 00:00:00 2001 From: Brian Hogg Date: Tue, 18 Nov 2025 09:51:02 -0500 Subject: [PATCH 14/14] Add view link to footer. --- .../views/class.llms.notification.view.certificate.earned.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/notifications/views/class.llms.notification.view.certificate.earned.php b/includes/notifications/views/class.llms.notification.view.certificate.earned.php index 127cfbca97..f63bee208a 100644 --- a/includes/notifications/views/class.llms.notification.view.certificate.earned.php +++ b/includes/notifications/views/class.llms.notification.view.certificate.earned.php @@ -84,7 +84,7 @@ private function get_mini_html( $title ) { protected function set_body() { if ( 'email' === $this->notification->get( 'type' ) ) { // Translators: %1$s - student name, %2$s - Certificate title. - return sprintf( __( 'Congratulations! %1$s earned a certificate: %2$s.', 'lifterlms' ) . "\n\n{{MINI_CERTIFICATE}}", '{{STUDENT_NAME}}', '{{CERTIFICATE_TITLE}}' ); + return sprintf( __( 'Congratulations! %1$s earned a certificate: %2$s.', 'lifterlms' ) . "\n\n{{MINI_CERTIFICATE}}\n\n" . $this->set_footer(), '{{STUDENT_NAME}}', '{{CERTIFICATE_TITLE}}' ); } return '{{MINI_CERTIFICATE}}'; }