registerCron(); $this->registerTable(); add_filter(BASE . 'handle_bulk_operation', [ $this, 'processOperation' ], 10, 3); } protected function registerCron():void { add_action(BASE . 'notification_digest_daily', [ $this, 'runDailyDigests' ]); add_action(BASE . 'notification_digest_weekly', [ $this, 'runWeeklyDigests' ]); add_action(BASE . 'notification_digest_monthly', [ $this, 'runMonthlyDigests' ]); } protected function registerTable():void { $this->registerUserIndex(); $this->registerTermIndex(); } protected function registerUserIndex():void { $table = CustomTable::for('user_notification_email_digest'); // $types = implode(',',array_map(function($item) { return "'{$item}'"; }, Registrar::withFeature('favouritable'))); $table->setColumns([ 'id' => 'bigint(20) unsigned NOT NULL AUTO_INCREMENT', 'user_id' => "{$table->getUserIDType()} NOT NULL", 'frequency' => "ENUM('daily', 'weekly', 'monthly') NOT NULL", 'content_ids' => 'JSON DEFAULT NULL', 'output' => 'TEXT DEFAULT NULL', 'total_sent' => 'int unsigned DEFAULT 0', 'created_at' => 'datetime DEFAULT CURRENT_TIMESTAMP', ]); $table->setKeys([ ['key' => 'PRIMARY', 'value' => '(`id`)'], ['key' => 'UNIQUE', 'value' => '`user_frequency` (`user_id`, `frequency`)'], 'user_id (`user_id`)' ]); $base = BASE; $table->setConstraints([ "CONSTRAINT `{$base}digest_user` FOREIGN KEY (`user_id`) REFERENCES `{$table->getUserTable()}` (`ID`) ON DELETE CASCADE" ]); $table->defineTable(); $this->userIndex = $table; } protected function registerTermIndex():void { $table = CustomTable::for('user_notification_email_digest'); $types = implode(',',array_map(function($item) { return "'{$item}'"; }, Registrar::withFeature('favouritable', 'term'))); $table->setColumns([ 'id' => 'bigint(20) unsigned NOT NULL AUTO_INCREMENT', 'term_id' => "{$table->getTermIDType()} NOT NULL", 'type' => "ENUM({$types}) NOT NULL", 'frequency' => "ENUM('daily', 'weekly', 'monthly') NOT NULL", 'content_ids' => 'JSON DEFAULT NULL', 'output' => 'TEXT DEFAULT NULL', 'total_sent' => 'int unsigned DEFAULT 0', 'created_at' => 'datetime DEFAULT CURRENT_TIMESTAMP', ]); $table->setKeys([ ['key' => 'PRIMARY', 'value' => '(`id`)'], ['key' => 'UNIQUE', 'value' => '`term_frequency` (`term_id`, `type`, `frequency`)'], 'term_id (`term_id`)' ]); $base = BASE; $table->setConstraints([ "CONSTRAINT `{$base}digest_term` FOREIGN KEY (`term_id`) REFERENCES `{$table->getTermTable()}` (`term_id`) ON DELETE CASCADE" ]); $table->defineTable(); $this->termIndex = $table; } public function runDailyDigests():void { $this->campaign = 'daily_digest_' .date('Y-m-d'); $this->processDigest('daily'); } public function runWeeklyDigests():void { $this->campaign = 'weekly_digest_'.date('Y-m-d'); $this->processDigest('weekly'); } public function runMonthlyDigests():void { $this->campaign = 'monthly_digest_'.date('Y-m-d'); $this->processDigest('monthly'); } protected function processDigest(string $frequency):void { $users = $this->getUsers($frequency); if (empty($users)){ return; } JVB()->queue()->add( 'email_notification_digest', 0, [ 'frequency' => $frequency, 'users' => $users, ], [ 'chunk_key' => 'users', 'chunk_size'=> 20, 'operation_id'=> 'notification_digest_'.date('Y_m_d_His') ] ); } public function getUsers(string $frequency):array { if (!in_array($frequency, ['never', 'daily', 'weekly', 'monthly'])) { return []; } return JVB()->notification()->getUsersByFrequency($frequency); } public function processOperation(WP_Error|array $result, object $operation, array $data):WP_Error|array { if ($operation->type !== 'email_notification_digest') { return $result; } $results = []; foreach ($data['user'] as $userID) { $result = $this->generateUserDigest($userID, $data['frequency']); if ($result) { $results[$userID] = $result; } usleep(50); } return [ 'success' => true, 'result' => $results ]; } protected function getSinceDate(string $frequency):string { return match ($frequency) { 'weekly' => '-1 week', 'monthly' => '-1 month', default => '-1 day', }; } protected function generateUserDigest(int $userID, string $frequency):string|false { $subscription = JVB()->notification()->getUserSubscriptions($userID, $frequency); //TODO $notifications = JVB()->notification()->getUserNotifications($userID); if (empty($subscription) && empty($notifications)) { return false; } $content = ''; foreach ($subscription as $item) { $temp = match ($item['item_type']) { array_merge(['user'], Registrar::withFeature('favouritable', 'user')) => $this->getUserUpdates($item['item_id'], $frequency), Registrar::withFeature('favouritable', 'term') => $this->getTermUpdates($item['item_id'], $item['item_type'], $frequency), default => false, }; if ($temp) { $content .= $temp; } } if (empty($content)) { return false; } return $content; } protected function getUserUpdates(int $userID, string $frequency):string|false { $frequency = strtolower($frequency); $frequency = in_array($frequency, ['daily', 'weekly', 'monthly']) ? $frequency : 'daily'; $user = get_userdata($userID); if (!$user || is_wp_error($user)) { return false; } $since = $this->getSinceDate($frequency); $entry = $this->userIndex->get([ 'user_id' => $userID, 'frequency' => $frequency ]); if ($entry) { return $entry->output; } $new = [ 'user_id' => $userID, 'frequency' => $frequency, ]; //Didn't do it yet, create it here $IDs = Content::forUser($userID, $since); $new['content_ids'] = json_encode($IDs); $new['output'] = $this->contentForUser($userID, $IDs); $this->userIndex->insert($new); return $new['output']; } protected function contentForUser(int $userID, array $IDs):string { if (empty($IDs)) { return ''; } $user = get_userdata($userID); if (!$user || is_wp_error($user)) { return ''; } $role = jvbUserRole($userID); $registrar = Registrar::getInstance($role); if (!$registrar) { return ''; } if ($registrar->profile_link) { $meta = Meta::forPost(jvbUserProfileLink($userID)); $title = $meta->get('post_title'); $goTo = get_the_permalink(jvbUserProfileLink($userID)); }else { $meta = Meta::forUser($userID); $title = $meta->get('display_name'); $goTo = get_author_posts_url($userID); } $output = []; $count = count($IDs); $max = (min($count, 5)); $canIncrease = $count > $max; for ($i = 0; $i <=$max; $i++) { $ID = $IDs[$i]; $m = Meta::forPost($ID); $img = $m->get('post_thumbnail'); if (empty($img)) { if ($canIncrease){ $max++; } continue; } $image = wp_get_attachment_image_src($img, 'medium'); if (!$image) { if ($canIncrease){ $max++; } continue; } $url = get_the_permalink($ID); $image = sprintf( '%s', $url, JVB()->email()->image($image[0], get_post_meta($img, '_wp_attachment_image_alt', true)) ); $t = sprintf( '%s', $url, $m->get('post_title') ); $output[] = JVB()->email()->card($image, $t); } $final = JVB()->email()->button($goTo, 'Want to See More?'); return JVB()->email()->grid($output, 3, 'New from '.$title,'',$final); } protected function getTermUpdates(int $termID, string $type, string $frequency):string|false { $frequency = strtolower($frequency); $frequency = in_array($frequency, ['daily', 'weekly', 'monthly']) ? $frequency : 'daily'; $taxonomy = jvbCheckBase($type); $term = get_term($termID, $taxonomy); if (!$term || is_wp_error($term)) { return false; } $since = $this->getSinceDate($frequency); $entry = $this->termIndex->get([ 'term_id' => $termID, 'type' => $type, 'frequency' => $frequency ]); if ($entry) { return $entry->output; } $new = [ 'term_id' => $termID, 'type' => $type, 'frequency' => $frequency ]; //Didn't do it yet, create it here $IDs = Content::forTerm($termID, $taxonomy, $since); $new['content_ids'] = json_encode($IDs); $new['output'] = $this->contentForTerm($termID, $type, $IDs); $this->termIndex->insert($new); return $new['output']; } protected function contentForTerm(int $termID, string $tax, array $IDs):string { if (empty($IDs)) { return ''; } $taxonomy = jvbCheckBase($tax); $term = get_term($termID, $taxonomy); if (!$term || is_wp_error($term)) { return ''; } $registrar = Registrar::getInstance($tax); if (!$registrar) { return ''; } $meta = Meta::forTerm($termID); $title = $meta->get('name'); $goTo = get_term_link($termID,$taxonomy); $output = []; $count = count($IDs); $max = (min($count, 5)); $canIncrease = $count > $max; for ($i = 0; $i <=$max; $i++) { $ID = $IDs[$i]; $m = Meta::forPost($ID); $img = $m->get('post_thumbnail'); if (empty($img)) { if ($canIncrease){ $max++; } continue; } $image = wp_get_attachment_image_src($img, 'medium'); if (!$image) { if ($canIncrease){ $max++; } continue; } $url = get_the_permalink($ID); $image = sprintf( '%s', $url, JVB()->email()->image($image[0], get_post_meta($img, '_wp_attachment_image_alt', true)) ); $t = sprintf( '%s', $url, $m->get('post_title') ); $output[] = JVB()->email()->card($image, $t); } $final = JVB()->email()->button($goTo, 'See More'); return JVB()->email()->grid($output, 3, 'New in '.$title, '', $final); } }