PluginProbe
wpForo Forum / 3.1.6
wpForo Forum v3.1.6
3.1.6 3.1.5 3.1.4 3.1.2 3.1.1 3.1.0 3.0.9 3.0.8 3.0.7 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.10 1.4.11 1.4.12 1.4.13 All 138 releases
wpforo / admin / pages / news / src / Services / CronService.php

CronService.php in wpForo Forum 3.1.6, at admin/pages/news/src/Services/CronService.php

135 lines 5.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace gVectors\News\Services;
4
5 use gVectors\News\Config;
6
7 // Exit if accessed directly
8 if( ! defined( 'ABSPATH' ) ) exit;
9
10 /**
11 * Daily sync with the proxy server:
12 * 1. bail unless the service is enabled (consent gate)
13 * 2. fetch news → cache in a transient for the admin notices
14 * 3. build the installed-versions map of licensed addons
15 * 4. fetch at-risk licenses (proxy computes phases + builds the email)
16 * 5. dispatch news digest emails
17 * 6. dispatch expiry reminder emails
18 */
19 class CronService {
20 private $config;
21 private $api;
22 private $consent;
23 private $email;
24
25 public function __construct( Config $config, ApiService $api, ConsentService $consent, EmailService $email ) {
26 $this->config = $config;
27 $this->api = $api;
28 $this->consent = $consent;
29 $this->email = $email;
30
31 add_action( $this->config->get_cron_hook(), [ $this, 'sync' ] );
32 add_action( 'init', [ $this, 'maybe_schedule' ] );
33 }
34
35 /**
36 * Self-healing scheduler: keep the daily event scheduled while the service
37 * is enabled, clear it when consent is withdrawn.
38 */
39 public function maybe_schedule(): void {
40 $scheduled = wp_next_scheduled( $this->config->get_cron_hook() );
41 if( $this->consent->is_enabled() ) {
42 if( ! $scheduled ) {
43 wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', $this->config->get_cron_hook() );
44 }
45 } elseif( $scheduled ) {
46 wp_clear_scheduled_hook( $this->config->get_cron_hook() );
47 }
48 }
49
50 /**
51 * Daily sync handler.
52 */
53 public function sync(): void {
54 if( ! $this->consent->is_enabled() ) return;
55
56 // 1. News → transient (admin notices read from here; no request per pageload)
57 $news_payload = $this->api->get_news();
58 if( $news_payload !== null ) {
59 $news_payload['fetched_at'] = time();
60 set_transient( $this->config->get_news_transient(), $news_payload, $this->config->get_news_cache_ttl() );
61 }
62
63 // 2. At-risk licenses (only when there are licensed addons to report on)
64 $at_risk = null;
65 $versions = $this->get_installed_versions_map();
66 if( ! empty( $versions ) ) {
67 $at_risk = $this->api->get_at_risk_licenses( $versions );
68 }
69
70 // 3. Emails — EmailService filters through the site + per-admin
71 // channel/category matrix (PrefsService), so no gating needed here.
72 if( $news_payload !== null ) {
73 $this->email->send_news_digest( $news_payload );
74 }
75 if( $at_risk !== null ) {
76 $this->email->send_expiry_reminders( $at_risk ); // upcoming expirations (phase > 0)
77 $this->email->send_winback_reminders( $at_risk ); // already expired (phase < 0)
78 $this->email->send_dunning_notices( $at_risk ); // failed payments (past_due)
79 $this->email->send_offer_reminders( $at_risk ); // unused personal discounts
80 }
81 }
82
83 /**
84 * Cached news payload for the notices renderer.
85 */
86 public function get_cached_news(): array {
87 $payload = get_transient( $this->config->get_news_transient() );
88
89 return is_array( $payload ) && ! empty( $payload['news'] ) && is_array( $payload['news'] ) ? $payload['news'] : [];
90 }
91
92 /**
93 * plugin_slug => installed version map for every addon ANY registered
94 * gVectors plugin has a local license for. Licenses are gVectors-wide, so
95 * the map merges the license storage of every plugin context (wpForo
96 * addon licenses + wpDiscuz addon licenses + ...), and the proxy answers
97 * with at-risk licenses for all of them in one request.
98 */
99 private function get_installed_versions_map(): array {
100 $slugs = [];
101 foreach( \gVectors\News\NewsModule::get_contexts() as $config ) {
102 $licenses = get_option( $config->get_licenses_option(), [] );
103 if( ! is_array( $licenses ) ) continue;
104 foreach( $licenses as $license ) {
105 if( is_array( $license ) && ! empty( $license['plugin_slug'] ) && is_string( $license['plugin_slug'] ) ) {
106 $slugs[ $license['plugin_slug'] ] = true;
107 }
108 }
109 }
110 if( empty( $slugs ) ) return [];
111
112 if( ! function_exists( 'get_plugins' ) ) {
113 require_once ABSPATH . 'wp-admin/includes/plugin.php';
114 }
115
116 $versions = [];
117 foreach( get_plugins() as $plugin_file => $plugin_data ) {
118 $dir = dirname( $plugin_file );
119 if( isset( $slugs[ $dir ] ) ) {
120 $versions[ $dir ] = (string) ( $plugin_data['Version'] ?? '' );
121 }
122 }
123
124 // Licensed but not installed addons are still reported (version unknown) so
125 // the proxy can include them in expiry reminders.
126 foreach( array_keys( $slugs ) as $slug ) {
127 if( ! isset( $versions[ $slug ] ) ) {
128 $versions[ $slug ] = '';
129 }
130 }
131
132 return $versions;
133 }
134 }
135