PluginProbe ʕ •ᴥ•ʔ
WP Popular Posts / 7.1.0
WP Popular Posts v7.1.0
4.0.8 4.0.9 4.1.0 4.1.1 4.1.2 4.2.0 4.2.1 4.2.2 5.0.0 5.0.1 5.0.2 5.1.0 5.2.0 5.2.1 5.2.2 5.2.3 5.2.4 5.3.0 5.3.1 5.3.2 5.3.3 5.3.4 5.3.5 5.3.6 5.4.0 5.4.1 5.4.2 5.5.0 5.5.1 6.0.0 6.0.1 6.0.2 6.0.3 6.0.4 6.0.5 6.1.0 6.1.1 6.1.2 6.1.3 6.1.4 6.2.0 6.2.1 6.3.0 6.3.1 6.3.2 6.3.3 6.3.4 6.4.0 6.4.1 6.4.2 7.0.0 7.0.1 7.1.0 7.2.0 7.3.0 7.3.1 7.3.2 7.3.3 7.3.4 7.3.5 7.3.6 7.3.7 7.3.8 7.4.0 trunk 2.3.7 3.0.0 3.0.1 3.0.2 3.0.3 3.1.0 3.1.1 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 4.0.0 4.0.1 4.0.10 4.0.11 4.0.12 4.0.13 4.0.2 4.0.3 4.0.5 4.0.6
wordpress-popular-posts / src / Rest / ViewLoggerEndpoint.php
wordpress-popular-posts / src / Rest Last commit date
Controller.php 1 year ago Endpoint.php 1 year ago PostsEndpoint.php 1 year ago TaxonomiesEndpoint.php 1 year ago ThemesEndpoint.php 1 year ago ThumbnailsEndpoint.php 1 year ago ViewLoggerEndpoint.php 1 year ago WidgetEndpoint.php 1 year ago
ViewLoggerEndpoint.php
363 lines
1 <?php
2 namespace WordPressPopularPosts\Rest;
3
4 use WordPressPopularPosts\Helper;
5
6 class ViewLoggerEndpoint extends Endpoint {
7
8 /**
9 * Registers the endpoint(s).
10 *
11 * @since 5.3.0
12 */
13 public function register()
14 {
15 $version = '2';
16 $namespace = 'wordpress-popular-posts/v' . $version;
17
18 /** @TODO: This endpoint has been superseeded by /views, please remove */
19 register_rest_route('wordpress-popular-posts/v1', '/popular-posts', [
20 [
21 'methods' => \WP_REST_Server::CREATABLE,
22 'callback' => [$this, 'update_views_count'],
23 'permission_callback' => '__return_true',
24 'args' => $this->get_tracking_params(),
25 ]
26 ]);
27
28 register_rest_route($namespace, '/views/(?P<id>[\d]+)', [
29 [
30 'methods' => \WP_REST_Server::READABLE,
31 'callback' => [$this, 'get_views_count'],
32 'permission_callback' => '__return_true',
33 'args' => $this->get_views_params(),
34 ]
35 ]);
36
37 register_rest_route($namespace, '/views/(?P<id>[\d]+)', [
38 [
39 'methods' => \WP_REST_Server::CREATABLE,
40 'callback' => [$this, 'update_views_count'],
41 'permission_callback' => '__return_true',
42 'args' => $this->get_tracking_params(),
43 ]
44 ]);
45 }
46
47 /**
48 * Returs the views count of a post/page.
49 *
50 * @since 7.0.0
51 *
52 * @param \WP_REST_Request $request Full details about the request.
53 * @return string Views count string.
54 */
55 public function get_views_count($request) {
56 $post_id = $request->get_param('id');
57 $range = in_array( $request->get_param('range'), ['last24hours', 'last7days', 'last30days', 'all', 'custom'] ) ? $request->get_param('range') : 'all';
58 $time_unit = in_array( $request->get_param('time_unit'), ['minute', 'hour', 'day', 'week', 'month'] ) ? $request->get_param('time_unit') : 'hour';
59 $time_quantity = $request->get_param('time_quantity');
60 $include_views_text = 1 == $request->get_param('include_views_text') ? 1 : 0;
61
62 $views_count_shortcode = '[wpp_views_count post_id=' . $post_id . ' include_views_text=' . $include_views_text . ' range="' . $range . '"';
63
64 if ( 'custom' == $range ) {
65 $views_count_shortcode .= ' time_unit="' . $time_unit . '" time_quantity=' . $time_quantity;
66 }
67
68 $views_count_shortcode .= ']';
69
70 $response['text'] = do_shortcode($views_count_shortcode);
71
72 return new \WP_REST_Response( $response, 200 );
73 }
74
75 /**
76 * Updates the views count of a post / page.
77 *
78 * @since 4.1.0
79 *
80 * @param \WP_REST_Request $request Full details about the request.
81 * @return string
82 */
83 public function update_views_count($request) {
84 global $wpdb;
85
86 /** @TODO: Remove this check once the /v1/popular-posts is removed */
87 if ( false !== strpos($request->get_route(), '/v1/popular-posts') ) {
88 $post_ID = $request->get_param('wpp_id');
89 // Throw warning to let developers know that
90 // the /v1/popular-posts endpoint is going away
91 trigger_error('The /wordpress-popular-posts/v1/popular-posts POST endpoint has been deprecated, please POST to /wordpress-popular-posts/v2/views/[ID] instead.', E_USER_WARNING);
92 }
93 else {
94 $post_ID = $request->get_param('id');
95 }
96
97 $sampling = $request->get_param('sampling');
98 $sampling_rate = $request->get_param('sampling_rate');
99
100 // Sampling settings from database
101 $_sampling = $this->config['tools']['sampling']['active'];
102 $_sampling_rate = $this->config['tools']['sampling']['rate'];
103
104 // Let's make sure that sampling settings we got
105 // on this request are what we expect
106 $sampling = $sampling != $_sampling ? $_sampling : $sampling;
107 $sampling_rate = $sampling_rate != $_sampling_rate ? $_sampling_rate : $sampling_rate;
108
109 $table = $wpdb->prefix . 'popularposts';
110 $wpdb->show_errors();
111
112 // Get translated object ID
113 $post_ID = $this->translate->get_object_id(
114 $post_ID,
115 get_post_type($post_ID),
116 true,
117 $this->translate->get_default_language()
118 );
119
120 $now = Helper::now();
121 $curdate = Helper::curdate();
122 $views = ($sampling)
123 ? $sampling_rate
124 : 1;
125
126 $original_views_count = $views;
127 $views = apply_filters('wpp_update_views_count_value', $views, $post_ID, $sampling, $sampling_rate);
128
129 if ( ! Helper::is_number($views) || $views <= 0 ) {
130 $views = $original_views_count;
131 }
132
133 // Allow WP themers / coders perform an action
134 // before updating views count
135 if ( has_action('wpp_pre_update_views') ) {
136 do_action('wpp_pre_update_views', $post_ID, $views);
137 }
138
139 $result1 = false;
140 $result2 = false;
141
142 $exec_time = 0;
143 $start = Helper::microtime_float();
144
145 // Store views data in persistent object cache
146 if (
147 wp_using_ext_object_cache()
148 && defined('WPP_CACHE_VIEWS')
149 && WPP_CACHE_VIEWS
150 ) {
151
152 $now_datetime = new \DateTime($now, wp_timezone());
153 $timestamp = $now_datetime->getTimestamp();
154 $date_time = $now_datetime->format('Y-m-d H:i');
155 $date_time_with_seconds = $now_datetime->format('Y-m-d H:i:s');
156 $high_accuracy = false;
157
158 $key = $high_accuracy ? $timestamp : $date_time;
159
160 $wpp_cache = wp_cache_get('_wpp_cache', 'transient');
161
162 if ( ! $wpp_cache ) {
163 $wpp_cache = [
164 'last_updated' => $date_time_with_seconds,
165 'data' => [
166 $post_ID => [
167 $key => 1
168 ]
169 ]
170 ];
171 } else {
172 if ( ! isset($wpp_cache['data'][$post_ID][$key]) ) {
173 $wpp_cache['data'][$post_ID][$key] = 1;
174 } else {
175 $wpp_cache['data'][$post_ID][$key] += 1;
176 }
177 }
178
179 // Update cache
180 wp_cache_set('_wpp_cache', $wpp_cache, 'transient', 0);
181
182 // How long has it been since the last time we saved to the database?
183 $last_update = $now_datetime->diff(new \DateTime($wpp_cache['last_updated'], wp_timezone()));
184 $diff_in_minutes = $last_update->days * 24 * 60;
185 $diff_in_minutes += $last_update->h * 60;
186 $diff_in_minutes += $last_update->i;
187
188 // It's been more than 2 minutes, save everything to DB
189 if ( $diff_in_minutes > 2 ) {
190
191 $query_data = "INSERT INTO {$table}data (`postid`,`day`,`last_viewed`,`pageviews`) VALUES ";
192 $query_summary = "INSERT INTO {$table}summary (`postid`,`pageviews`,`view_date`,`view_datetime`) VALUES ";
193
194 foreach( $wpp_cache['data'] as $pid => $data ) {
195 $views_count = 0;
196
197 foreach( $data as $ts => $cached_views ){
198 $views_count += $cached_views;
199 $ts = Helper::is_timestamp($ts) ? $ts : strtotime($ts);
200
201 $query_summary .= $wpdb->prepare('(%d,%d,%s,%s),', [
202 $pid,
203 $cached_views,
204 date('Y-m-d', $ts),
205 date('Y-m-d H:i:s', $ts)
206 ]);
207 }
208
209 $query_data .= $wpdb->prepare( '(%d,%s,%s,%s),', [
210 $pid,
211 $date_time_with_seconds,
212 $date_time_with_seconds,
213 $views_count
214 ]);
215 }
216
217 $query_data = rtrim($query_data, ',') . ' ON DUPLICATE KEY UPDATE pageviews=pageviews+VALUES(pageviews),last_viewed=VALUES(last_viewed);';
218 $query_summary = rtrim($query_summary, ',') . ';';
219
220 // Clear cache
221 $wpp_cache['last_updated'] = $date_time_with_seconds;
222 $wpp_cache['data'] = [];
223 wp_cache_set('_wpp_cache', $wpp_cache, 'transient', 0);
224
225 // Save
226 //phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.PreparedSQL.NotPrepared -- We already prepared $query_data and $query_summary above
227 $result1 = $wpdb->query($query_data);
228 $result2 = $wpdb->query($query_summary);
229 //phpcs:enable
230 }
231 else {
232 $result1 = true;
233 $result2 = true;
234 }
235 } // Live update to the DB
236 else {
237 //phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery -- $table is safe to use
238 // Update all-time table
239 $result1 = $wpdb->query($wpdb->prepare(
240 "INSERT INTO {$table}data
241 (postid, day, last_viewed, pageviews) VALUES (%d, %s, %s, %d)
242 ON DUPLICATE KEY UPDATE pageviews = pageviews + %d, last_viewed = %s;",
243 $post_ID,
244 $now,
245 $now,
246 $views,
247 $views,
248 $now
249 ));
250
251 // Update range (summary) table
252 $result2 = $wpdb->query($wpdb->prepare(
253 "INSERT INTO {$table}summary
254 (postid, pageviews, view_date, view_datetime) VALUES (%d, %d, %s, %s)
255 ON DUPLICATE KEY UPDATE pageviews = pageviews + %d, view_datetime = %s;",
256 $post_ID,
257 $views,
258 $curdate,
259 $now,
260 $views,
261 $now
262 ));
263 //phpcs:enable
264 }
265
266 $end = Helper::microtime_float();
267 $exec_time += round($end - $start, 6);
268
269 $response = ['results' => ''];
270
271 if ( ! $result1 || ! $result2 ) {
272 $response['results'] = 'WPP: failed to update views count!';
273 return new \WP_REST_Response($response, 500);
274 }
275
276 // Allow WP themers / coders perform an action
277 // after updating views count
278 if ( has_action('wpp_post_update_views') ) {
279 do_action('wpp_post_update_views', $post_ID);
280 }
281
282 $response['results'] = 'WPP: OK. Execution time: ' . $exec_time . ' seconds';
283 return new \WP_REST_Response($response, 201);
284 }
285
286 /**
287 * Retrieves the query params for tracking views count.
288 *
289 * @since 4.1.0
290 *
291 * @return array Query parameters for tracking views count.
292 */
293 public function get_tracking_params()
294 {
295 /** @TODO: Remove wpp_id key once the /v1/popular-posts is removed */
296 return [
297 'wpp_id' => [
298 'description' => __('The post / page ID.'),
299 'type' => 'integer',
300 'default' => 0,
301 'sanitize_callback' => 'absint',
302 'validate_callback' => 'rest_validate_request_arg',
303 ],
304 'sampling' => [
305 'description' => __('Enables Data Sampling.'),
306 'type' => 'integer',
307 'default' => 0,
308 'sanitize_callback' => 'absint',
309 'validate_callback' => 'rest_validate_request_arg',
310 ],
311 'sampling_rate' => [
312 'description' => __('Sets the Sampling Rate.'),
313 'type' => 'integer',
314 'default' => 100,
315 'sanitize_callback' => 'absint',
316 'validate_callback' => 'rest_validate_request_arg',
317 ]
318 ];
319 }
320
321 /**
322 * Retrieves the query params for getting post/page/cpt views count.
323 *
324 * @since 7.0.0
325 *
326 * @return array Query parameters for getting post/page/cpt views count.
327 */
328 public function get_views_params()
329 {
330 return [
331 'range' => [
332 'type' => 'string',
333 'enum' => ['last24hours', 'last7days', 'last30days', 'all', 'custom'],
334 'default' => 'all',
335 'sanitize_callback' => 'sanitize_text_field',
336 'validate_callback' => '__return_true'
337 ],
338 'time_unit' => [
339 'type' => 'string',
340 'enum' => ['minute', 'hour', 'day', 'week', 'month'],
341 'default' => 'hour',
342 'sanitize_callback' => 'sanitize_text_field',
343 'validate_callback' => 'rest_validate_request_arg',
344 ],
345 'time_quantity' => [
346 'type' => 'integer',
347 'default' => 24,
348 'minimum' => 1,
349 'sanitize_callback' => 'absint',
350 'validate_callback' => 'rest_validate_request_arg',
351 ],
352 'include_views_text' => [
353 'type' => 'integer',
354 'default' => 1,
355 'sanitize_callback' => 'absint',
356 'validate_callback' => function($param, $request, $key) {
357 return is_numeric($param);
358 }
359 ],
360 ];
361 }
362 }
363