PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.12.1
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.12.1
5.12.1 5.12.0 5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / classes / WpMatomo / Admin / PluginSuggestions / PluginSuggestions.php
matomo / classes / WpMatomo / Admin / PluginSuggestions Last commit date
Suggestions 3 months ago views 3 months ago PluginSuggestions.php 3 weeks ago Suggestion.php 1 week ago
PluginSuggestions.php
202 lines
1 <?php
2 /**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 * @package matomo
8 */
9
10 namespace WpMatomo\Admin\PluginSuggestions;
11
12 use WpMatomo\Admin\PluginSuggestions\Suggestions\AdvertisingConversionExport;
13 use WpMatomo\Admin\PluginSuggestions\Suggestions\Funnels;
14 use WpMatomo\Admin\PluginSuggestions\Suggestions\HeatmapSessionRecording;
15 use WpMatomo\Admin\PluginSuggestions\Suggestions\SearchEngineKeywordsPerformance;
16 use WpMatomo\Admin\PluginSuggestions\Suggestions\UsersFlow;
17 use WpMatomo\Admin\PluginSuggestions\Suggestions\WpPremiumBundle;
18 use WpMatomo\Bootstrap;
19 use WpMatomo\Feature;
20
21 if ( ! defined( 'ABSPATH' ) ) {
22 exit; // if accessed directly
23 }
24
25 class PluginSuggestions extends Feature {
26
27 const SUGGESTION_TRIGGERED_OPTION_NAME = 'matomo_plugin_suggestion_to_show';
28 const DISMISSED_SUGGESTIONS_OPTION_NAME = 'matomo_dismissed_suggestions';
29 const FORCE_SUGGESTION_QUERY_PARAM_NAME = 'mtm_force_suggestion';
30 const DISMISS_SUGGESTION_NONCE = 'matomo_dismiss_suggestion';
31
32 public function register_hooks() {
33 add_action( 'matomo_scheduled_check_plugin_suggestions', [ $this, 'check' ], 10 );
34
35 if ( wp_next_scheduled( 'matomo_scheduled_check_plugin_suggestions' ) === false ) {
36 wp_schedule_event( time(), 'daily', 'matomo_scheduled_check_plugin_suggestions', [], true );
37 }
38
39 add_action( 'matomo_page_content_before', [ $this, 'show' ] );
40
41 add_action( 'admin_enqueue_scripts', [ $this, 'load_scripts' ] );
42
43 add_action( 'init', [ $this, 'add_suggestion_hooks' ] );
44 }
45
46 public function add_suggestion_hooks() {
47 foreach ( $this->get_suggestions() as $suggestion ) {
48 $suggestion->register_hooks();
49 }
50 }
51 public function register_ajax() {
52 add_action( 'wp_ajax_matomo_dismiss_suggestion', [ $this, 'dismiss_suggestion_ajax' ] );
53 }
54
55 public function load_scripts() {
56 wp_localize_script(
57 'matomo-admin-js',
58 'mtmDismissSuggestionAjax',
59 [
60 'ajax_url' => admin_url( 'admin-ajax.php' ),
61 'nonce' => wp_create_nonce( self::DISMISS_SUGGESTION_NONCE ),
62 ]
63 );
64 }
65
66 public function show() {
67 if ( ! empty( $_REQUEST[ self::FORCE_SUGGESTION_QUERY_PARAM_NAME ] ) ) {
68 $matomo_suggestion_to_show = sanitize_text_field( wp_unslash( $_REQUEST[ self::FORCE_SUGGESTION_QUERY_PARAM_NAME ] ) );
69 } else {
70 $matomo_suggestion_to_show = get_user_option( self::SUGGESTION_TRIGGERED_OPTION_NAME );
71 }
72
73 $matomo_suggestion_to_show = $this->find_suggestion_by_class( $matomo_suggestion_to_show );
74
75 if ( empty( $matomo_suggestion_to_show ) ) {
76 return;
77 }
78
79 if ( $this->is_suggestion_dismissed( $matomo_suggestion_to_show ) ) {
80 return;
81 }
82
83 require __DIR__ . '/views/suggestion.php';
84 }
85
86 public function check() {
87 Bootstrap::do_bootstrap();
88
89 $triggered_suggestions = [];
90
91 foreach ( $this->get_suggestions() as $suggestion ) {
92 if ( ! $suggestion->is_suggestion_applicable() ) {
93 continue;
94 }
95
96 $should_trigger = \Piwik\Access::doAsSuperUser(
97 function () use ( $suggestion ) {
98 return $suggestion->should_trigger();
99 }
100 );
101 if ( ! $should_trigger ) {
102 continue;
103 }
104
105 $triggered_suggestions[] = $suggestion;
106 }
107
108 $users = get_users( [ 'fields' => 'ID' ] );
109 foreach ( $users as $user_id ) {
110 delete_user_option( $user_id, self::SUGGESTION_TRIGGERED_OPTION_NAME );
111
112 foreach ( $triggered_suggestions as $suggestion ) {
113 if ( $this->is_suggestion_dismissed( $suggestion, $user_id ) ) {
114 continue;
115 }
116
117 update_user_option( $user_id, self::SUGGESTION_TRIGGERED_OPTION_NAME, wp_slash( get_class( $suggestion ) ) );
118 break;
119 }
120 }
121 }
122
123 /**
124 * @param string $suggestion_id simple or full suggestion class name
125 * @return void
126 */
127 public function dismiss_suggestion( $suggestion_id ) {
128 $suggestion = $this->find_suggestion_by_class( $suggestion_id );
129 if ( empty( $suggestion ) ) {
130 return;
131 }
132
133 $dismissed_suggestions = get_user_option( self::DISMISSED_SUGGESTIONS_OPTION_NAME );
134 if ( ! is_array( $dismissed_suggestions ) ) {
135 $dismissed_suggestions = [];
136 }
137
138 // user metadata is unslashed when saving, but is not re-slashed when getting
139 $dismissed_suggestions[] = get_class( $suggestion );
140 $dismissed_suggestions = array_map( 'wp_slash', $dismissed_suggestions );
141 $dismissed_suggestions = array_values( array_unique( $dismissed_suggestions ) );
142
143 update_user_option( get_current_user_id(), self::DISMISSED_SUGGESTIONS_OPTION_NAME, $dismissed_suggestions );
144 }
145
146 public function dismiss_suggestion_ajax() {
147 check_ajax_referer( self::DISMISS_SUGGESTION_NONCE );
148
149 if ( ! empty( $_REQUEST['suggestion'] ) ) {
150 $suggestion_id = sanitize_text_field( wp_unslash( $_REQUEST['suggestion'] ) );
151 $this->dismiss_suggestion( $suggestion_id );
152 }
153
154 wp_send_json( [ 'ok' => true ] );
155 }
156
157 /**
158 * @return Suggestion[]
159 */
160 private function get_suggestions() {
161 // ordered by priority to show
162 $suggestions = [
163 new HeatmapSessionRecording(),
164 new SearchEngineKeywordsPerformance(),
165 new AdvertisingConversionExport(),
166 new WpPremiumBundle(),
167 new UsersFlow(),
168 new Funnels(),
169 ];
170
171 foreach ( $suggestions as $suggestion ) {
172 $suggestion->init();
173 }
174
175 return $suggestions;
176 }
177
178 private function find_suggestion_by_class( $suggestion_to_show ) {
179 foreach ( $this->get_suggestions() as $suggestion ) {
180 $full_class = get_class( $suggestion );
181 $simple_class = explode( '\\', $full_class );
182 $simple_class = end( $simple_class );
183
184 if (
185 $full_class === $suggestion_to_show
186 || $simple_class === $suggestion_to_show
187 ) {
188 return $suggestion;
189 }
190 }
191 return null;
192 }
193
194 private function is_suggestion_dismissed( Suggestion $suggestion, $user = 0 ) {
195 $dismissed_suggestions = get_user_option( self::DISMISSED_SUGGESTIONS_OPTION_NAME, $user );
196 if ( ! is_array( $dismissed_suggestions ) ) {
197 $dismissed_suggestions = [];
198 }
199 return in_array( get_class( $suggestion ), $dismissed_suggestions, true );
200 }
201 }
202