PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.76
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.76
51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / extensions / Smart_Links / Smart_Links_Service.php

Smart_Links_Service.php in King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder 51.1.76, at includes/extensions/Smart_Links/Smart_Links_Service.php

408 lines 10.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Smart Links service layer.
4 *
5 * @package King_Addons
6 */
7
8 namespace King_Addons\Smart_Links;
9
10 use WP_Post;
11 use WP_Query;
12
13 if (!defined('ABSPATH')) {
14 exit;
15 }
16
17 class Smart_Links_Service
18 {
19 public function get_link(int $link_id): ?WP_Post
20 {
21 $post = get_post($link_id);
22 if (!$post || $post->post_type !== Smart_Links::POST_TYPE) {
23 return null;
24 }
25
26 return $post;
27 }
28
29 public function get_link_by_slug(string $slug): ?WP_Post
30 {
31 $slug = $this->sanitize_slug($slug);
32 if ($slug === '') {
33 return null;
34 }
35
36 $query = new WP_Query([
37 'post_type' => Smart_Links::POST_TYPE,
38 'post_status' => 'any',
39 'posts_per_page' => 1,
40 'meta_key' => Smart_Links::META_SLUG,
41 'meta_value' => $slug,
42 ]);
43
44 if (empty($query->posts)) {
45 return null;
46 }
47
48 return $query->posts[0];
49 }
50
51 public function slug_exists(string $slug, int $exclude_id = 0): bool
52 {
53 $slug = $this->sanitize_slug($slug);
54 if ($slug === '') {
55 return false;
56 }
57
58 $args = [
59 'post_type' => Smart_Links::POST_TYPE,
60 'post_status' => 'any',
61 'fields' => 'ids',
62 'posts_per_page' => 1,
63 'meta_key' => Smart_Links::META_SLUG,
64 'meta_value' => $slug,
65 ];
66
67 if ($exclude_id > 0) {
68 $args['post__not_in'] = [$exclude_id];
69 }
70
71 $query = new WP_Query($args);
72 return !empty($query->posts);
73 }
74
75 public function sanitize_slug(string $slug): string
76 {
77 $slug = strtolower($slug);
78 $slug = preg_replace('/[^a-z0-9\-_]+/', '-', $slug);
79 $slug = trim((string) $slug, '-_');
80 return $slug;
81 }
82
83 public function generate_slug(int $length = 6): string
84 {
85 $length = max(4, min(20, $length));
86 $chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
87 $slug = '';
88
89 for ($i = 0; $i < $length; $i++) {
90 $slug .= $chars[wp_rand(0, strlen($chars) - 1)];
91 }
92
93 return $slug;
94 }
95
96 public function ensure_unique_slug(string $slug, int $exclude_id = 0): string
97 {
98 $slug = $this->sanitize_slug($slug);
99 if ($slug === '') {
100 $slug = $this->generate_slug();
101 }
102
103 if (!$this->slug_exists($slug, $exclude_id)) {
104 return $slug;
105 }
106
107 $base = $slug;
108 $suffix = 1;
109
110 while ($suffix < 1000) {
111 $candidate = $base . '-' . $suffix;
112 if (!$this->slug_exists($candidate, $exclude_id)) {
113 return $candidate;
114 }
115 $suffix++;
116 }
117
118 return $this->generate_slug();
119 }
120
121 public function build_short_url(string $slug): string
122 {
123 $base = trim(Smart_Links_Settings::get_base_path(), '/');
124 $path = $base !== '' ? $base . '/' . $slug : $slug;
125 return home_url(user_trailingslashit($path));
126 }
127
128 public function sanitize_destination_url(string $url): string
129 {
130 $url = trim($url);
131 if ($url === '') {
132 return '';
133 }
134
135 $validated = wp_http_validate_url($url);
136 if (!$validated) {
137 return '';
138 }
139
140 $parsed = wp_parse_url($validated);
141 $scheme = $parsed['scheme'] ?? '';
142 if (!in_array($scheme, ['http', 'https'], true)) {
143 return '';
144 }
145
146 return $validated;
147 }
148
149 public function apply_utm(string $url, array $utm): string
150 {
151 if (empty($utm['enabled'])) {
152 return $url;
153 }
154
155 $args = [];
156 foreach (['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content'] as $key) {
157 if (!empty($utm[$key])) {
158 $args[$key] = $utm[$key];
159 }
160 }
161
162 if (empty($args)) {
163 return $url;
164 }
165
166 return add_query_arg($args, $url);
167 }
168
169 public function merge_query_params(string $url, array $incoming, array $settings): string
170 {
171 if (empty($incoming)) {
172 return $url;
173 }
174
175 $filtered = [];
176 foreach ($incoming as $key => $value) {
177 if (!is_scalar($value)) {
178 continue;
179 }
180 if (strpos($key, 'kng_') === 0) {
181 continue;
182 }
183 $filtered[$key] = sanitize_text_field((string) $value);
184 }
185
186 if (empty($filtered)) {
187 return $url;
188 }
189
190 $whitelist = $this->parse_csv($settings['whitelist_query_params'] ?? '');
191 $blacklist = $this->parse_csv($settings['blacklist_query_params'] ?? '');
192
193 if (!empty($whitelist)) {
194 $filtered = array_intersect_key($filtered, array_flip($whitelist));
195 }
196
197 if (!empty($blacklist)) {
198 foreach ($blacklist as $blocked) {
199 unset($filtered[$blocked]);
200 }
201 }
202
203 if (empty($filtered)) {
204 return $url;
205 }
206
207 return add_query_arg($filtered, $url);
208 }
209
210 public function parse_tags(string $tags): array
211 {
212 $parts = array_filter(array_map('trim', explode(',', $tags)));
213 $clean = [];
214 foreach ($parts as $tag) {
215 $tag = sanitize_text_field($tag);
216 if ($tag !== '') {
217 $clean[] = $tag;
218 }
219 }
220
221 return array_values(array_unique($clean));
222 }
223
224 public function parse_csv(string $value): array
225 {
226 $parts = array_filter(array_map('trim', explode(',', $value)));
227 $clean = [];
228 foreach ($parts as $part) {
229 $part = sanitize_key($part);
230 if ($part !== '') {
231 $clean[] = $part;
232 }
233 }
234
235 return array_values(array_unique($clean));
236 }
237
238 public function get_totals(int $days = 7): array
239 {
240 global $wpdb;
241
242 $daily_table = Smart_Links_DB::get_daily_table();
243 $date_from = wp_date('Y-m-d', current_time('timestamp') - (max(0, $days - 1) * DAY_IN_SECONDS));
244
245 $row = $wpdb->get_row(
246 $wpdb->prepare(
247 "SELECT SUM(clicks) AS clicks, SUM(unique_clicks) AS unique_clicks FROM {$daily_table} WHERE date >= %s",
248 $date_from
249 ),
250 ARRAY_A
251 );
252
253 return [
254 'clicks' => isset($row['clicks']) ? (int) $row['clicks'] : 0,
255 'unique_clicks' => isset($row['unique_clicks']) ? (int) $row['unique_clicks'] : 0,
256 ];
257 }
258
259 public function get_top_link(int $days = 7): ?array
260 {
261 global $wpdb;
262
263 $daily_table = Smart_Links_DB::get_daily_table();
264 $date_from = wp_date('Y-m-d', current_time('timestamp') - (max(0, $days - 1) * DAY_IN_SECONDS));
265
266 $row = $wpdb->get_row(
267 $wpdb->prepare(
268 "SELECT link_id, SUM(clicks) AS total FROM {$daily_table} WHERE date >= %s GROUP BY link_id ORDER BY total DESC LIMIT 1",
269 $date_from
270 ),
271 ARRAY_A
272 );
273
274 if (!$row) {
275 return null;
276 }
277
278 $link = $this->get_link((int) $row['link_id']);
279 if (!$link) {
280 return null;
281 }
282
283 return [
284 'link' => $link,
285 'clicks' => (int) $row['total'],
286 ];
287 }
288
289 public function get_daily_stats(int $link_id = 0, string $date_from = '', string $date_to = ''): array
290 {
291 global $wpdb;
292
293 $daily_table = Smart_Links_DB::get_daily_table();
294 $where = [];
295 $params = [];
296
297 if ($link_id > 0) {
298 $where[] = 'link_id = %d';
299 $params[] = $link_id;
300 }
301
302 if ($date_from !== '') {
303 $where[] = 'date >= %s';
304 $params[] = $date_from;
305 }
306
307 if ($date_to !== '') {
308 $where[] = 'date <= %s';
309 $params[] = $date_to;
310 }
311
312 $where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
313 $select = $link_id > 0
314 ? 'date, clicks, unique_clicks'
315 : 'date, SUM(clicks) AS clicks, SUM(unique_clicks) AS unique_clicks';
316 $group = $link_id > 0 ? '' : 'GROUP BY date';
317
318 $sql = "SELECT {$select} FROM {$daily_table} {$where_sql} {$group} ORDER BY date DESC";
319
320 if (!empty($params)) {
321 $prepared = $wpdb->prepare($sql, $params);
322 } else {
323 $prepared = $sql;
324 }
325
326 return $wpdb->get_results($prepared, ARRAY_A);
327 }
328
329 public function get_total_stats(int $link_id = 0, string $date_from = '', string $date_to = ''): array
330 {
331 global $wpdb;
332
333 $daily_table = Smart_Links_DB::get_daily_table();
334 $where = [];
335 $params = [];
336
337 if ($link_id > 0) {
338 $where[] = 'link_id = %d';
339 $params[] = $link_id;
340 }
341
342 if ($date_from !== '') {
343 $where[] = 'date >= %s';
344 $params[] = $date_from;
345 }
346
347 if ($date_to !== '') {
348 $where[] = 'date <= %s';
349 $params[] = $date_to;
350 }
351
352 $where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
353 $sql = "SELECT SUM(clicks) AS clicks, SUM(unique_clicks) AS unique_clicks FROM {$daily_table} {$where_sql}";
354
355 if (!empty($params)) {
356 $sql = $wpdb->prepare($sql, $params);
357 }
358
359 $row = $wpdb->get_row($sql, ARRAY_A);
360
361 return [
362 'clicks' => isset($row['clicks']) ? (int) $row['clicks'] : 0,
363 'unique_clicks' => isset($row['unique_clicks']) ? (int) $row['unique_clicks'] : 0,
364 ];
365 }
366
367 public function get_top_links(int $limit = 10, string $date_from = '', string $date_to = ''): array
368 {
369 global $wpdb;
370
371 $daily_table = Smart_Links_DB::get_daily_table();
372 $where = [];
373 $params = [];
374
375 if ($date_from !== '') {
376 $where[] = 'date >= %s';
377 $params[] = $date_from;
378 }
379
380 if ($date_to !== '') {
381 $where[] = 'date <= %s';
382 $params[] = $date_to;
383 }
384
385 $where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : '';
386 $sql = "SELECT link_id, SUM(clicks) AS clicks, SUM(unique_clicks) AS unique_clicks FROM {$daily_table} {$where_sql} GROUP BY link_id ORDER BY clicks DESC LIMIT %d";
387 $params[] = $limit;
388
389 $prepared = $wpdb->prepare($sql, $params);
390 $rows = $wpdb->get_results($prepared, ARRAY_A);
391
392 $results = [];
393 foreach ($rows as $row) {
394 $link = $this->get_link((int) $row['link_id']);
395 if (!$link) {
396 continue;
397 }
398 $results[] = [
399 'link' => $link,
400 'clicks' => (int) $row['clicks'],
401 'unique_clicks' => (int) $row['unique_clicks'],
402 ];
403 }
404
405 return $results;
406 }
407 }
408