PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.8.2
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.8.2
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 2 months ago views 2 months ago PluginSuggestions.php 2 months ago Suggestion.php 2 months ago
PluginSuggestions.php
199 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 foreach ( $this->get_suggestions() as $suggestion ) {
44 $suggestion->register_hooks();
45 }
46 }
47
48 public function register_ajax() {
49 add_action( 'wp_ajax_matomo_dismiss_suggestion', [ $this, 'dismiss_suggestion_ajax' ] );
50 }
51
52 public function load_scripts() {
53 wp_localize_script(
54 'matomo-admin-js',
55 'mtmDismissSuggestionAjax',
56 [
57 'ajax_url' => admin_url( 'admin-ajax.php' ),
58 'nonce' => wp_create_nonce( self::DISMISS_SUGGESTION_NONCE ),
59 ]
60 );
61 }
62
63 public function show() {
64 if ( ! empty( $_REQUEST[ self::FORCE_SUGGESTION_QUERY_PARAM_NAME ] ) ) {
65 $matomo_suggestion_to_show = sanitize_text_field( wp_unslash( $_REQUEST[ self::FORCE_SUGGESTION_QUERY_PARAM_NAME ] ) );
66 } else {
67 $matomo_suggestion_to_show = get_user_option( self::SUGGESTION_TRIGGERED_OPTION_NAME );
68 }
69
70 $matomo_suggestion_to_show = $this->find_suggestion_by_class( $matomo_suggestion_to_show );
71
72 if ( empty( $matomo_suggestion_to_show ) ) {
73 return;
74 }
75
76 if ( $this->is_suggestion_dismissed( $matomo_suggestion_to_show ) ) {
77 return;
78 }
79
80 require __DIR__ . '/views/suggestion.php';
81 }
82
83 public function check() {
84 Bootstrap::do_bootstrap();
85
86 $triggered_suggestions = [];
87
88 foreach ( $this->get_suggestions() as $suggestion ) {
89 if ( ! $suggestion->is_suggestion_applicable() ) {
90 continue;
91 }
92
93 $should_trigger = \Piwik\Access::doAsSuperUser(
94 function () use ( $suggestion ) {
95 return $suggestion->should_trigger();
96 }
97 );
98 if ( ! $should_trigger ) {
99 continue;
100 }
101
102 $triggered_suggestions[] = $suggestion;
103 }
104
105 $users = get_users( [ 'fields' => 'ID' ] );
106 foreach ( $users as $user_id ) {
107 delete_user_option( $user_id, self::SUGGESTION_TRIGGERED_OPTION_NAME );
108
109 foreach ( $triggered_suggestions as $suggestion ) {
110 if ( $this->is_suggestion_dismissed( $suggestion, $user_id ) ) {
111 continue;
112 }
113
114 update_user_option( $user_id, self::SUGGESTION_TRIGGERED_OPTION_NAME, wp_slash( get_class( $suggestion ) ) );
115 break;
116 }
117 }
118 }
119
120 /**
121 * @param string $suggestion_id simple or full suggestion class name
122 * @return void
123 */
124 public function dismiss_suggestion( $suggestion_id ) {
125 $suggestion = $this->find_suggestion_by_class( $suggestion_id );
126 if ( empty( $suggestion ) ) {
127 return;
128 }
129
130 $dismissed_suggestions = get_user_option( self::DISMISSED_SUGGESTIONS_OPTION_NAME );
131 if ( ! is_array( $dismissed_suggestions ) ) {
132 $dismissed_suggestions = [];
133 }
134
135 // user metadata is unslashed when saving, but is not re-slashed when getting
136 $dismissed_suggestions[] = get_class( $suggestion );
137 $dismissed_suggestions = array_map( 'wp_slash', $dismissed_suggestions );
138 $dismissed_suggestions = array_values( array_unique( $dismissed_suggestions ) );
139
140 update_user_option( get_current_user_id(), self::DISMISSED_SUGGESTIONS_OPTION_NAME, $dismissed_suggestions );
141 }
142
143 public function dismiss_suggestion_ajax() {
144 check_ajax_referer( self::DISMISS_SUGGESTION_NONCE );
145
146 if ( ! empty( $_REQUEST['suggestion'] ) ) {
147 $suggestion_id = sanitize_text_field( wp_unslash( $_REQUEST['suggestion'] ) );
148 $this->dismiss_suggestion( $suggestion_id );
149 }
150
151 wp_send_json( [ 'ok' => true ] );
152 }
153
154 /**
155 * @return Suggestion[]
156 */
157 private function get_suggestions() {
158 // ordered by priority to show
159 $suggestions = [
160 new HeatmapSessionRecording(),
161 new SearchEngineKeywordsPerformance(),
162 new AdvertisingConversionExport(),
163 new WpPremiumBundle(),
164 new UsersFlow(),
165 new Funnels(),
166 ];
167
168 foreach ( $suggestions as $suggestion ) {
169 $suggestion->init();
170 }
171
172 return $suggestions;
173 }
174
175 private function find_suggestion_by_class( $suggestion_to_show ) {
176 foreach ( $this->get_suggestions() as $suggestion ) {
177 $full_class = get_class( $suggestion );
178 $simple_class = explode( '\\', $full_class );
179 $simple_class = end( $simple_class );
180
181 if (
182 $full_class === $suggestion_to_show
183 || $simple_class === $suggestion_to_show
184 ) {
185 return $suggestion;
186 }
187 }
188 return null;
189 }
190
191 private function is_suggestion_dismissed( Suggestion $suggestion, $user = 0 ) {
192 $dismissed_suggestions = get_user_option( self::DISMISSED_SUGGESTIONS_OPTION_NAME, $user );
193 if ( ! is_array( $dismissed_suggestions ) ) {
194 $dismissed_suggestions = [];
195 }
196 return in_array( get_class( $suggestion ), $dismissed_suggestions, true );
197 }
198 }
199