PluginProbe
Extendify / 3.1.3
Extendify v3.1.3
3.2.1 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 All 127 releases
extendify / app / Insights.php

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

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