PluginProbe
Extendify / 3.1.5
Extendify v3.1.5
3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 0.7.0 All 126 releases
extendify / app / Insights.php

Insights.php in Extendify 3.1.5, at app/Insights.php

250 lines 8.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Insights setup
5 */
6
7 namespace Extendify;
8
9 defined('ABSPATH') || die('No direct access.');
10
11 use Extendify\Shared\Services\Sanitizer;
12 use Extendify\PartnerData;
13
14 /**
15 * Controller for handling various Insights related things.
16 * WP code reviewers: This is used in another plugin and not invoked here.
17 */
18
19 class Insights
20 {
21 /**
22 * Option name storing each site's A/B test assignments, keyed by the
23 * screen/feature under test (e.g. 'AutoLaunch.HideEnhanceAI').
24 *
25 * @var string
26 */
27 // phpcs:ignore PSR12.Properties.ConstantVisibility.NotFound
28 const ACTIVE_TESTS_OPTION = 'extendify_active_tests';
29
30 /**
31 * Tests the plugin knows how to run. Each is rolled independently against
32 * its own rollout percentage, which the partner config supplies.
33 *
34 * @var string[]
35 */
36 // phpcs:ignore PSR12.Properties.ConstantVisibility.NotFound
37 const AVAILABLE_TESTS = [
38 'AutoLaunch.HideEnhanceAI',
39 'AutoLaunch.SubmitOutside',
40 'AutoLaunch.SubmitCreateWebsite',
41 'AutoLaunch.DescriptionPlaceholderLaw',
42 'AutoLaunch.HeaderParagraphOld',
43 'AutoLaunch.MigrateScreen',
44 ];
45
46 /**
47 * Process the readme file to get version and name
48 *
49 * @return void
50 */
51 public function __construct()
52 {
53 // If there isn't a siteId, then create one.
54 if (!\get_option('extendify_site_id', false)) {
55 \update_option('extendify_site_id', \wp_generate_uuid4());
56 }
57
58 if (
59 defined('EXTENDIFY_INSIGHTS_URL')
60 && class_exists('ExtendifyInsights')
61 && !\get_option('extendify_insights_checkedin_once', 0)
62 ) {
63 \update_option('extendify_insights_checkedin_once', gmdate('Y-m-d H:i:s'));
64 // WP code reviewers: This job is defined in another plugin (i.e. it's opt-in).
65 \add_action('init', function () {
66 // Run this once but wait 10 minutes.
67 \wp_schedule_single_event((time() + 10 * MINUTE_IN_SECONDS), 'extendify_insights');
68 \spawn_cron();
69 });
70 }
71
72 $this->filterExternalInsights();
73 $this->setupAdminLoginInsights();
74 }
75
76 /**
77 * Assign A/B variants for the known tests based on the partner's active
78 * tests. Each active test is rolled once;
79 * inactive tests are dropped.
80 *
81 * @param string[] $activeTests Active tests in `Name:Percentage` form
82 * (e.g. 'AutoLaunch.HideEnhanceAI:20'); a bare
83 * name defaults to a 50% rollout.
84 * @return void
85 */
86 public static function setup(array $activeTests = [])
87 {
88 $assignments = \get_option(self::ACTIVE_TESTS_OPTION, []);
89
90 $percentages = [];
91 foreach ($activeTests as $entry) {
92 list($key, $percentage) = array_pad(explode(':', $entry, 2), 2, null);
93 $percentages[$key] = is_numeric($percentage) ? (float) $percentage : 50.0;
94 }
95
96 foreach (self::AVAILABLE_TESTS as $key) {
97 if (!array_key_exists($key, $percentages)) {
98 unset($assignments[$key]);
99 continue;
100 }
101
102 // Roll once so the site keeps the same variant.
103 if (!isset($assignments[$key])) {
104 $assignments[$key] = [
105 // The percentage is variant B's rollout share: 50 -> 50% A / 50% B,
106 // 20 -> 80% A / 20% B.
107 'variant' => random_int(1, 10000) <= $percentages[$key] * 100 ? 'B' : 'A',
108 'percentage' => $percentages[$key],
109 // ISO 8601 (UTC)
110 'assignedAt' => gmdate('c'),
111 ];
112 }
113 }
114
115 \update_option(self::ACTIVE_TESTS_OPTION, Sanitizer::sanitizeArray($assignments));
116 }
117
118 /**
119 * Add additional data to the opt-in insights
120 *
121 * @return void
122 */
123 public function filterExternalInsights()
124 {
125 add_filter('extendify_insights_data', function ($data) {
126 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
127 $readme = file_get_contents(EXTENDIFY_PATH . 'readme.txt');
128 preg_match('/Stable tag: ([0-9.:]+)/', $readme, $version);
129
130 $insights = array_merge($data, [
131 'launch' => Config::$showLaunch,
132 'launchRedirectedAt' => \get_option('extendify_attempted_redirect', null),
133 'launchLoadedAt' => \get_option('extendify_launch_loaded', null),
134 'partner' => defined('EXTENDIFY_PARTNER_ID') ? constant('EXTENDIFY_PARTNER_ID') : null,
135 'siteCreatedAt' => SiteSettings::getSiteCreatedAt(),
136 'assistRouterData' => \get_option('extendify_assist_router', null),
137 'libraryData' => \get_option('extendify_library_site_data', null),
138 'draftSettingsData' => \get_option('extendify_draft_settings', null),
139 'activity' => \get_option('extendify_shared_activity', null),
140 'domainsActivities' => \get_option('extendify_domains_recommendations_activities', null),
141 'extendifyVersion' => ($version[1] ?? null),
142 'siteProfile' => \get_option('extendify_site_profile', null),
143 'pluginSearchTerms' => \get_option('extendify_plugin_search_terms', []),
144 'blockSearchTerms' => \get_option('extendify_block_search_terms', []),
145 'phpVersion' => PHP_VERSION,
146 'themeSearchTerms' => \get_option('extendify_theme_search_terms', []),
147 'license' => PartnerData::setting('license'),
148 'pagesCount' => $this->getPostsCount('page'),
149 'postsCount' => $this->getPostsCount('post'),
150 'lastUpdatedPage' => $this->getLastUpdatedPost('page'),
151 'lastUpdatedPost' => $this->getLastUpdatedPost('post'),
152 'lastLoginAdmin' => $this->getLastAdminLogin(),
153 'hasImprint' => $this->hasImprint(),
154 ]);
155 return $insights;
156 });
157 }
158
159 /**
160 * Get the number of posts/pages
161 *
162 * @param string $type The type of post/page to get the count for (post or page)
163 * @return int The number of posts/pages
164 */
165 protected function getPostsCount($type = 'post')
166 {
167 $count = wp_count_posts($type);
168 return isset($count->publish) ? (int) $count->publish : 0;
169 }
170
171 /**
172 * Set up admin login insights to monitor when admin users log in
173 *
174 * @return void
175 */
176 protected function setupAdminLoginInsights()
177 {
178 add_action('wp_login', function ($user_login, $user) {
179 // Only get insights for admin users
180 if (user_can($user, 'manage_options')) {
181 update_user_meta($user->ID, 'extendify_last_login', gmdate('Y-m-d H:i:s'));
182 }
183 }, 10, 2);
184 }
185
186 /**
187 * Get the last time a post/page was updated
188 *
189 * @return string|null The last updated post timestamp or null if no posts found
190 */
191 protected function getLastUpdatedPost($type = 'post')
192 {
193 $posts = get_posts([
194 'post_type' => $type,
195 'post_status' => 'publish',
196 'orderby' => 'modified',
197 'order' => 'DESC',
198 'numberposts' => 1,
199 'fields' => 'ids'
200 ]);
201
202 if (!empty($posts)) {
203 $post = get_post($posts[0]);
204 return $post ? $post->post_modified : null;
205 }
206 return null;
207 }
208
209 /**
210 * Get the last time an admin user logged in
211 *
212 * @return string|null The most recent admin login timestamp or null if no data found
213 */
214 protected function getLastAdminLogin()
215 {
216 $admins = get_users([
217 'role' => 'administrator',
218 'meta_key' => 'extendify_last_login',
219 'orderby' => 'meta_value',
220 'order' => 'DESC',
221 'number' => 1
222 ]);
223
224 if (!empty($admins)) {
225 return get_user_meta($admins[0]->ID, 'extendify_last_login', true);
226 }
227 return null;
228 }
229
230 /**
231 * Check if the site has an imprint based on the site profile and language settings
232 *
233 * @return bool True if the site has an imprint, false otherwise
234 */
235 protected function hasImprint()
236 {
237 $siteProfile = \get_option('extendify_site_profile', []);
238 if (empty($siteProfile)) {
239 return false;
240 }
241
242 $imprintLanguages = array_filter(PartnerData::setting('showImprint') ?? [], function ($value) {
243 return $value === get_locale();
244 });
245
246 return !empty($imprintLanguages) && (strtolower($siteProfile['aiSiteCategory']) === 'business' ||
247 strtolower($siteProfile['category']) === 'business');
248 }
249 }
250