PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 2.14.2
Independent Analytics – WordPress Analytics Plugin v2.14.2
2.15.0 2.14.10 trunk 1.1 1.10 1.10.1 1.11 1.12 1.13 1.14 1.15 1.16 1.17 1.17.1 1.17.2 1.17.3 1.17.4 1.18 1.18.1 1.19.0 1.19.1 1.2 1.20.0 1.21.0 1.22.0 1.22.1 1.23.0 1.23.1 1.24.0 1.24.1 1.25.0 1.25.1 1.26.0 1.27.0 1.28.0 1.28.1 1.28.2 1.28.3 1.29.0 1.3 1.30.0 1.30.1 1.4 1.5 1.6 1.7 1.8 1.9 2.0.0 2.0.1 2.1.4 2.1.5 2.1.6 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.10 2.11.2 2.11.3 2.11.4 2.11.5 2.11.6 2.11.7 2.11.8 2.11.9 2.12.0 2.12.1 2.12.2 2.13.1 2.13.2 2.13.5 2.13.6 2.14.0 2.14.1 2.14.2 2.14.4 2.14.6 2.14.7 2.14.8 2.14.9 2.2.0 2.2.1 2.3.1 2.3.2 2.4.2 2.4.3 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.7.1 2.7.2 2.7.3 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7
independent-analytics / IAWP / Overview / Modules / Module.php
independent-analytics / IAWP / Overview / Modules Last commit date
Busiest_Day_Of_Week_Module.php 5 months ago Busiest_Time_Of_Day_Module.php 1 year ago Line_Chart_Module.php 9 months ago Map_Module.php 9 months ago Module.php 5 months ago New_Sessions_Module.php 1 year ago Pie_Chart_Module.php 9 months ago Quick_Stats_Module.php 9 months ago Recent_Conversions_Module.php 5 months ago Recent_Views_Module.php 5 months ago Top_Ten_Module.php 9 months ago
Module.php
420 lines
1 <?php
2
3 namespace IAWP\Overview\Modules;
4
5 use DateTime;
6 use IAWP\Date_Range\Relative_Date_Range;
7 use IAWP\Overview\Form_Field;
8 use IAWP\Overview\Form_Field_Option;
9 use IAWP\Overview\WP_Options_Storage;
10 use IAWP\Report;
11 use IAWP\Report_Finder;
12 use IAWP\Utils\Format;
13 use IAWP\Utils\Security;
14 use IAWP\Utils\Timezone;
15 use IAWPSCOPED\Illuminate\Support\Arr;
16 use IAWPSCOPED\Illuminate\Support\Collection;
17 /** @internal */
18 abstract class Module
19 {
20 protected $attributes;
21 private $report;
22 private $form_fields;
23 private $validation_errors = null;
24 private $options_storage;
25 public function __construct(?array $attributes = [])
26 {
27 $this->report = $this->get_report($attributes);
28 $this->form_fields = $this->create_form_fields();
29 $this->attributes = $this->prune_attributes($attributes);
30 $this->options_storage = new WP_Options_Storage('iawp_overview_modules');
31 }
32 public abstract function module_type() : string;
33 public abstract function module_name() : string;
34 protected abstract function module_fields() : array;
35 public abstract function calculate_dataset();
36 public function id() : ?string
37 {
38 return $this->attributes['id'] ?? null;
39 }
40 public function name() : ?string
41 {
42 return $this->attributes['name'] ?? null;
43 }
44 public function is_full_width() : bool
45 {
46 return $this->attributes['is_full_width'] ?? \false;
47 }
48 public function report() : ?Report
49 {
50 return $this->report;
51 }
52 public function save($skip_sync = \false) : bool
53 {
54 if (!$this->is_valid()) {
55 return \false;
56 }
57 $is_update = $this->options_storage->exists($this->id());
58 // Generate an id
59 if (!$is_update) {
60 $this->attributes['id'] = $this->options_storage->generate_id();
61 }
62 $this->options_storage->insert($this->attributes);
63 $this->report = $this->get_report($this->attributes);
64 if (!$skip_sync) {
65 $this->refresh();
66 }
67 return \true;
68 }
69 public function update(array $attributes)
70 {
71 $pruned_attributes = $this->prune_attributes($attributes, \true);
72 $this->attributes = \array_merge($this->attributes, $pruned_attributes);
73 $this->report = $this->get_report($attributes);
74 $this->form_fields = $this->create_form_fields();
75 }
76 public function delete() : bool
77 {
78 // Delete any cached datasets
79 \delete_option($this->option_name());
80 // Delete the module configuration
81 return $this->options_storage->delete($this->id());
82 }
83 public function is_valid() : bool
84 {
85 $validation_errors = [];
86 if (\is_string($this->attributes['name'] ?? null) && \strlen($this->attributes['name']) > 0) {
87 } else {
88 $validation_errors[] = ['name', 'Must be provided'];
89 }
90 // Check every form field
91 foreach ($this->form_fields as $form_field) {
92 $value = $this->attributes[$form_field->id()] ?? null;
93 if (!$form_field->is_a_supported_value($value)) {
94 $validation_errors[] = [$form_field->id(), "Invalid option provided"];
95 }
96 }
97 if (\count($validation_errors) > 0) {
98 $this->validation_errors = $validation_errors;
99 return \false;
100 } else {
101 return \true;
102 }
103 }
104 public function validation_errors() : ?array
105 {
106 return $this->validation_errors;
107 }
108 public function form_fields() : array
109 {
110 return $this->form_fields;
111 }
112 public function get_module_html() : string
113 {
114 $dataset = $this->get_dataset();
115 return \IAWPSCOPED\iawp_render("overview.modules.layout", ['module' => $this, 'dataset' => $dataset, 'is_loaded' => $dataset !== null, 'is_empty' => \is_array($dataset) && empty($dataset)]);
116 }
117 public function get_editor_html() : string
118 {
119 return \IAWPSCOPED\iawp_render('overview.module-editor', ['module' => $this]);
120 }
121 public function get_form_fields_html() : string
122 {
123 $html = '';
124 foreach ($this->form_fields as $form_field) {
125 $type = $form_field->type();
126 $html .= \IAWPSCOPED\iawp_render("overview.form-fields.{$type}", ['form_field' => $form_field, 'selected_value' => $this->attributes[$form_field->id()] ?? null]);
127 }
128 return $html;
129 }
130 public function get_report_details() : array
131 {
132 return Form_Field::get_report_details();
133 }
134 /**
135 * Has this module already been saved?
136 *
137 * @return bool
138 */
139 public function is_saved() : bool
140 {
141 return $this->id() !== null;
142 }
143 public function get_dataset() : ?array
144 {
145 $value = \get_option($this->option_name(), null);
146 return \is_array($value) ? $value : null;
147 }
148 public function has_dataset() : bool
149 {
150 return \is_array($this->get_dataset());
151 }
152 /**
153 * Subtitle to show under the module name
154 *
155 * @return string
156 */
157 public function subtitle() : string
158 {
159 // If the module has a range option, use the selected value
160 if ($this->has_dataset()) {
161 $relative_range = Relative_Date_Range::range_by_id($this->attributes['date_range'] ?? $this->attributes['busiest_date_range']);
162 if ($relative_range) {
163 return $relative_range->label();
164 }
165 }
166 $number_of_days = $this->get_dataset_number_of_days();
167 if (\is_int($number_of_days)) {
168 return \sprintf(\__("Last %s Days", 'independent-analytics'), $this->get_dataset_number_of_days());
169 }
170 return '';
171 }
172 public function refresh()
173 {
174 \delete_option($this->option_name());
175 \update_option($this->option_name(), $this->calculate_dataset(), \false);
176 }
177 protected function get_dataset_number_of_days() : ?int
178 {
179 return null;
180 }
181 protected function option_name() : string
182 {
183 return 'iawp_module_' . $this->id();
184 }
185 /**
186 * Get the name for the field option that was selcted for the given $field_id.
187 *
188 * @param string $field_id
189 *
190 * @return string|null
191 */
192 protected function get_field_option_name(string $field_id) : ?string
193 {
194 $selected_field_option_id = $this->attributes[$field_id] ?? null;
195 if ($selected_field_option_id === null) {
196 return null;
197 }
198 $options = $this->get_form_field($field_id)->supported_values();
199 $option = Collection::make($options)->first(function (Form_Field_Option $option) use($selected_field_option_id) {
200 return $option->id() === $selected_field_option_id;
201 });
202 if ($option === null) {
203 return null;
204 }
205 return $option->name();
206 }
207 /**
208 * @return ?Form_Field
209 */
210 protected function get_form_field(string $id) : ?Form_Field
211 {
212 return Collection::make($this->form_fields)->first(function (Form_Field $field) use($id) {
213 return $field->id() === $id;
214 });
215 }
216 /**
217 * Determine which report to associate with a module instance.
218 *
219 * @param array $attributes
220 *
221 * @return Report
222 */
223 private function get_report(array $attributes) : Report
224 {
225 $default_report = Report_Finder::new()->get_base_report_for_type('views');
226 $report_id = $attributes['report'] ?? $attributes['geo_report'] ?? null;
227 if ($report_id === null) {
228 return $default_report;
229 }
230 $report = Report_Finder::new()->fetch_report($report_id);
231 if ($report instanceof Report) {
232 return $report;
233 } else {
234 return $default_report;
235 }
236 }
237 /**
238 * Remove attributes that are not available for a given module
239 *
240 * @param array $attributes
241 *
242 * @return array
243 */
244 private function prune_attributes(array $attributes, $for_update = \false) : array
245 {
246 if ($for_update) {
247 $valid_attributes = Collection::make(['name', 'is_full_width']);
248 } else {
249 $valid_attributes = Collection::make(['id', 'module_type', 'name', 'is_full_width']);
250 }
251 foreach ($this->form_fields as $form_field) {
252 $valid_attributes->push($form_field->id());
253 }
254 $new_attributes = Arr::only($attributes, $valid_attributes->all());
255 // Convert is_full_width into a boolean if present
256 if (\array_key_exists('is_full_width', $new_attributes) && !\is_bool($new_attributes['is_full_width'])) {
257 $new_attributes['is_full_width'] = $new_attributes['is_full_width'] === 'true';
258 }
259 foreach ($new_attributes as $key => $value) {
260 if (\is_string($value)) {
261 $new_attributes[$key] = Security::string($value);
262 }
263 if (\is_array($value) && Arr::isList($value)) {
264 $new_attributes[$key] = Collection::make($value)->map(function ($value) {
265 if (\is_string($value)) {
266 return Security::string($value);
267 }
268 return $value;
269 })->all();
270 } elseif (\is_array($value) && Arr::isAssoc($value)) {
271 unset($new_attributes[$key]);
272 }
273 }
274 return $new_attributes;
275 }
276 /**
277 * @return Form_Field[]
278 */
279 private function create_form_fields() : array
280 {
281 return Collection::make($this->module_fields())->map(function (string $id) {
282 return new Form_Field($id, $this->report);
283 })->all();
284 }
285 public static function last_refreshed_at() : string
286 {
287 $timestamp = \get_option('iawp_modules_refreshed_at', null);
288 // The option could be a string or an int. Normalize it.
289 if (\is_int($timestamp)) {
290 $timestamp = \strval($timestamp);
291 }
292 if (!\ctype_digit($timestamp)) {
293 return '';
294 }
295 try {
296 $date = DateTime::createFromFormat('U', $timestamp, Timezone::utc_timezone());
297 $date->setTimezone(Timezone::site_timezone());
298 $text = $date->format(Format::time());
299 return \sprintf(\_x('Last updated at %s.', 'The placeholder is a time of day', 'independent-analytics'), $text);
300 } catch (\Exception $e) {
301 return '';
302 }
303 }
304 public static function new(string $module_type, array $attributes) : ?self
305 {
306 switch ($module_type) {
307 case 'top-ten':
308 return new \IAWP\Overview\Modules\Top_Ten_Module($attributes);
309 case 'quick-stats':
310 return new \IAWP\Overview\Modules\Quick_Stats_Module($attributes);
311 case 'line-chart':
312 return new \IAWP\Overview\Modules\Line_Chart_Module($attributes);
313 case 'pie-chart':
314 return new \IAWP\Overview\Modules\Pie_Chart_Module($attributes);
315 case 'map':
316 return new \IAWP\Overview\Modules\Map_Module($attributes);
317 case 'recent-views':
318 return new \IAWP\Overview\Modules\Recent_Views_Module($attributes);
319 case 'recent-conversions':
320 return new \IAWP\Overview\Modules\Recent_Conversions_Module($attributes);
321 case 'busiest-time-of-day':
322 return new \IAWP\Overview\Modules\Busiest_Time_Of_Day_Module($attributes);
323 case 'busiest-day-of-week':
324 return new \IAWP\Overview\Modules\Busiest_Day_Of_Week_Module($attributes);
325 case 'new-sessions':
326 return new \IAWP\Overview\Modules\New_Sessions_Module($attributes);
327 default:
328 return null;
329 }
330 }
331 /**
332 * Get a template version of each module for use when creating new modules
333 *
334 * @return self[]
335 */
336 public static function get_template_modules() : array
337 {
338 return [new \IAWP\Overview\Modules\Top_Ten_Module(), new \IAWP\Overview\Modules\Quick_Stats_Module(), new \IAWP\Overview\Modules\Line_Chart_Module(), new \IAWP\Overview\Modules\Pie_Chart_Module(), new \IAWP\Overview\Modules\Map_Module(), new \IAWP\Overview\Modules\Recent_Views_Module(), new \IAWP\Overview\Modules\Recent_Conversions_Module(), new \IAWP\Overview\Modules\Busiest_Time_Of_Day_Module(), new \IAWP\Overview\Modules\Busiest_Day_Of_Week_Module(), new \IAWP\Overview\Modules\New_Sessions_Module()];
339 }
340 /**
341 * Get the modules that are saved to wp_options
342 *
343 * @return self[]
344 */
345 public static function get_saved_modules() : array
346 {
347 $options_storage = new WP_Options_Storage('iawp_overview_modules');
348 $records = Collection::make($options_storage->all())->filter(function (array $module) {
349 if (\array_key_exists('report', $module) && $module['report'] === 'journeys') {
350 return \false;
351 }
352 return \true;
353 })->values()->all();
354 // No records? Add the default modules.
355 if (\count($records) === 0 && \IAWPSCOPED\iawp()->get_option('iawp_default_modules_added', \false) === \false) {
356 \update_option('iawp_modules_refreshed_at', \time(), \true);
357 \update_option('iawp_default_modules_added', \true, \true);
358 foreach (self::default_modules() as $attributes) {
359 $module = self::new($attributes['module_type'], $attributes);
360 $module->save(\true);
361 }
362 $records = $options_storage->all();
363 static::refresh_all_modules();
364 }
365 return \array_map(function ($record) {
366 return self::new($record['module_type'], $record);
367 }, $records);
368 }
369 public static function refresh_all_modules() : void
370 {
371 foreach (self::get_saved_modules() as $module) {
372 $module->refresh();
373 }
374 \update_option('iawp_modules_refreshed_at', \time(), \true);
375 }
376 public static function queue_refresh_all_modules() : void
377 {
378 foreach (self::get_saved_modules() as $module) {
379 \delete_option($module->option_name());
380 }
381 \wp_schedule_single_event(\time(), 'iawp_module_refresh_now');
382 \spawn_cron();
383 }
384 public static function get_saved_module(string $id) : ?self
385 {
386 $modules = self::get_saved_modules();
387 return Collection::make($modules)->first(function (self $module) use($id) {
388 return $module->id() === $id;
389 });
390 }
391 /**
392 * @param string[] $ids
393 *
394 * @return void
395 */
396 public static function set_module_order(array $ids) : void
397 {
398 $options_storage = new WP_Options_Storage('iawp_overview_modules');
399 $options_storage->set_order($ids);
400 }
401 /**
402 * Delete all exiting modules and restore the defaults.
403 *
404 * @return void
405 */
406 public static function reset()
407 {
408 $modules = self::get_saved_modules();
409 foreach ($modules as $module) {
410 $module->delete();
411 }
412 \delete_option('iawp_modules_refreshed_at');
413 \delete_option('iawp_default_modules_added');
414 }
415 public static function default_modules() : array
416 {
417 return [["module_type" => "line-chart", "name" => \esc_html__("Site Traffic", "independent-analytics"), "report" => "views", "primary_metric" => "visitors", "secondary_metric" => "views", "date_range" => "LAST_THIRTY"], ["module_type" => "top-ten", "name" => \esc_html__("Top 10 Pages", "independent-analytics"), "report" => "views", "sort_by" => "visitors", "sort_direction" => "desc", "date_range" => "LAST_SEVEN"], ["module_type" => "map", "name" => \esc_html__("Geographic Traffic", "independent-analytics"), "geo_report" => "geo", "date_range" => "LAST_SEVEN"], ["module_type" => "new-sessions", "name" => \esc_html__("New vs. Returning Sessions", "independent-analytics"), "date_range" => "LAST_THIRTY"], ["module_type" => "quick-stats", "name" => \esc_html__("Site Metrics", "independent-analytics"), "report" => "views", "statistics" => ["visitors", "views", "sessions", "average_session_duration", "bounce_rate", "views_per_session", "clicks"], "date_range" => "LAST_SEVEN", "is_full_width" => \true], ["module_type" => "recent-views", "name" => \esc_html__("Recent Views", "independent-analytics")], ["module_type" => "recent-conversions", "name" => \esc_html__("Recent Conversions", "independent-analytics"), "recent_conversion_types" => ["order", "form_submission", "click"]], ["module_type" => "pie-chart", "name" => \esc_html__("Devices", "independent-analytics"), "report" => "devices", "aggregatable_sort_by" => "visitors", "date_range" => "LAST_THIRTY"], ["module_type" => "pie-chart", "name" => \esc_html__("Top Traffic Sources", "independent-analytics"), "report" => "referrers", "aggregatable_sort_by" => "visitors", "date_range" => "LAST_SEVEN"], ["module_type" => "busiest-time-of-day", "name" => \esc_html__("Busiest Time of Day", "independent-analytics"), "busiest_date_range" => "LAST_NINETY"], ["module_type" => "busiest-day-of-week", "name" => \esc_html__("Busiest Day of Week", "independent-analytics"), "busiest_date_range" => "LAST_NINETY"]];
418 }
419 }
420