WordPress Popular Posts - Version 5.2.3

Version Description

If you're using a caching plugin, flushing its cache right after installing / upgrading to this version is required.

  • Fixes a compatibility issue with WordPress 5.5.
  • Widget themes: various fixes for better IE11 compatibility.

Release notes

Download this release

Release Info

Developer hcabrera
Plugin Icon 128x128 WordPress Popular Posts
Version 5.2.3
Comparing to
See all releases

Code changes from version 5.2.2 to 5.2.3

assets/themes/cards/style.css CHANGED
@@ -1,63 +1,64 @@
1
- .wpp-cards {
2
- margin-left: 0;
3
- margin-right: 0;
4
- padding: 0;
5
- }
6
-
7
- .wpp-cards li {
8
- display: flex;
9
- list-style: none;
10
- margin: 0 0 1.2em 0;
11
- padding: 0 0 1em 0;
12
- border-bottom: #ddd 1px solid;
13
- }
14
-
15
- .wpp-cards li:last-of-type,
16
- .wpp-cards li:only-child {
17
- margin: 0;
18
- padding: 0;
19
- border-bottom: none;
20
- }
21
-
22
- .wpp-cards li .wpp-thumbnail {
23
- overflow: hidden;
24
- display: inline-block;
25
- flex-grow: 0;
26
- flex-shrink: 0;
27
- margin-right: 1em;
28
- font-size: 0.8em;
29
- line-height: 1;
30
- background: #f0f0f0;
31
- border: none;
32
- }
33
-
34
- .wpp-cards li a {
35
- text-decoration: none;
36
- }
37
-
38
- .wpp-cards li a:hover {
39
- text-decoration: underline;
40
- }
41
-
42
- .wpp-cards li .taxonomies,
43
- .wpp-cards li .wpp-post-title {
44
- display: block;
45
- font-weight: bold;
46
- }
47
-
48
- .wpp-cards li .taxonomies {
49
- margin-bottom: 0.25em;
50
- font-size: 0.7em;
51
- line-height: 1;
52
- }
53
-
54
- .wpp-cards li .wpp-post-title {
55
- margin-bottom: 0.5em;
56
- font-size: 1.2em;
57
- line-height: 1.2;
58
- }
59
-
60
- .wpp-cards li .wpp-excerpt {
61
- margin: 0;
62
- font-size: 0.8em;
 
63
  }
1
+ .wpp-cards {
2
+ margin-left: 0;
3
+ margin-right: 0;
4
+ padding: 0;
5
+ }
6
+
7
+ .wpp-cards li {
8
+ display: flex;
9
+ list-style: none;
10
+ margin: 0 0 1.2em 0;
11
+ padding: 0 0 1em 0;
12
+ border-bottom: #ddd 1px solid;
13
+ }
14
+
15
+ .wpp-cards li:last-of-type,
16
+ .wpp-cards li:only-child {
17
+ margin: 0;
18
+ padding: 0;
19
+ border-bottom: none;
20
+ }
21
+
22
+ .wpp-cards li .wpp-thumbnail {
23
+ overflow: hidden;
24
+ display: inline-block;
25
+ align-self: flex-start;
26
+ flex-grow: 0;
27
+ flex-shrink: 0;
28
+ margin-right: 1em;
29
+ font-size: 0.8em;
30
+ line-height: 1;
31
+ background: #f0f0f0;
32
+ border: none;
33
+ }
34
+
35
+ .wpp-cards li a {
36
+ text-decoration: none;
37
+ }
38
+
39
+ .wpp-cards li a:hover {
40
+ text-decoration: underline;
41
+ }
42
+
43
+ .wpp-cards li .taxonomies,
44
+ .wpp-cards li .wpp-post-title {
45
+ display: block;
46
+ font-weight: bold;
47
+ }
48
+
49
+ .wpp-cards li .taxonomies {
50
+ margin-bottom: 0.25em;
51
+ font-size: 0.7em;
52
+ line-height: 1;
53
+ }
54
+
55
+ .wpp-cards li .wpp-post-title {
56
+ margin-bottom: 0.5em;
57
+ font-size: 1.2em;
58
+ line-height: 1.2;
59
+ }
60
+
61
+ .wpp-cards li .wpp-excerpt {
62
+ margin: 0;
63
+ font-size: 0.8em;
64
  }
assets/themes/cardview-compact/style.css CHANGED
@@ -1,71 +1,72 @@
1
- .wpp-cardview-compact {
2
- margin-left: 0;
3
- margin-right: 0;
4
- padding: 0;
5
- }
6
-
7
- .wpp-cardview-compact li {
8
- list-style: none;
9
- margin: 0 0 1.2em 0;
10
- padding: 0 0 1em 0;
11
- border-bottom: #ddd 1px solid;
12
- }
13
-
14
- .wpp-cardview-compact li:last-of-type,
15
- .wpp-cardview-compact li:only-child {
16
- margin: 0;
17
- padding: 0;
18
- border-bottom: none;
19
- }
20
-
21
- .wpp-cardview-compact .wpp-thumbnail-container {
22
- position: relative;
23
- margin-bottom: .8em;
24
- }
25
-
26
- .wpp-cardview-compact li .wpp-thumbnail {
27
- overflow: hidden;
28
- display: block;
29
- margin: 0;
30
- width: 100%;
31
- height: auto;
32
- font-size: 0.8em;
33
- line-height: 1;
34
- background: #f0f0f0;
35
- border: none;
36
- }
37
-
38
- .wpp-cardview-compact li a {
39
- text-decoration: none;
40
- }
41
-
42
- .wpp-cardview-compact li a:hover {
43
- text-decoration: underline;
44
- }
45
-
46
- .wpp-cardview-compact li .taxonomies,
47
- .wpp-cardview-compact li .wpp-post-title {
48
- display: block;
49
- font-weight: bold;
50
- }
51
-
52
- .wpp-cardview-compact li .taxonomies {
53
- position: absolute;
54
- bottom: 0;
55
- left: 0;
56
- padding: 1em;
57
- color: #aaa;
58
- font-size: 0.7em;
59
- line-height: 1;
60
- background: rgba(0, 0, 0, 0.5);
61
- }
62
-
63
- .wpp-cardview-compact li .taxonomies a {
64
- color: #fff;
65
- }
66
-
67
- .wpp-cardview-compact li .wpp-post-title {
68
- margin: 0 0 0.5em;
69
- font-size: 1.2em;
70
- line-height: 1.2;
71
- }
 
1
+ .wpp-cardview-compact {
2
+ margin-left: 0;
3
+ margin-right: 0;
4
+ padding: 0;
5
+ }
6
+
7
+ .wpp-cardview-compact li {
8
+ list-style: none;
9
+ margin: 0 0 1.2em 0;
10
+ padding: 0 0 1em 0;
11
+ border-bottom: #ddd 1px solid;
12
+ }
13
+
14
+ .wpp-cardview-compact li:last-of-type,
15
+ .wpp-cardview-compact li:only-child {
16
+ margin: 0;
17
+ padding: 0;
18
+ border-bottom: none;
19
+ }
20
+
21
+ .wpp-cardview-compact .wpp-thumbnail-container {
22
+ position: relative;
23
+ margin-bottom: .8em;
24
+ }
25
+
26
+ .wpp-cardview-compact li .wpp-thumbnail {
27
+ overflow: hidden;
28
+ display: block;
29
+ float: none;
30
+ margin: 0;
31
+ width: 100%;
32
+ height: auto;
33
+ font-size: 0.8em;
34
+ line-height: 1;
35
+ background: #f0f0f0;
36
+ border: none;
37
+ }
38
+
39
+ .wpp-cardview-compact li a {
40
+ text-decoration: none;
41
+ }
42
+
43
+ .wpp-cardview-compact li a:hover {
44
+ text-decoration: underline;
45
+ }
46
+
47
+ .wpp-cardview-compact li .taxonomies,
48
+ .wpp-cardview-compact li .wpp-post-title {
49
+ display: block;
50
+ font-weight: bold;
51
+ }
52
+
53
+ .wpp-cardview-compact li .taxonomies {
54
+ position: absolute;
55
+ bottom: 0;
56
+ left: 0;
57
+ padding: 1em;
58
+ color: #aaa;
59
+ font-size: 0.7em;
60
+ line-height: 1;
61
+ background: rgba(0, 0, 0, 0.5);
62
+ }
63
+
64
+ .wpp-cardview-compact li .taxonomies a {
65
+ color: #fff;
66
+ }
67
+
68
+ .wpp-cardview-compact li .wpp-post-title {
69
+ margin: 0 0 0.5em;
70
+ font-size: 1.2em;
71
+ line-height: 1.2;
72
+ }
assets/themes/cardview/style.css CHANGED
@@ -1,76 +1,77 @@
1
- .wpp-cardview {
2
- margin-left: 0;
3
- margin-right: 0;
4
- padding: 0;
5
- }
6
-
7
- .wpp-cardview li {
8
- list-style: none;
9
- margin: 0 0 1.2em 0;
10
- padding: 0 0 1em 0;
11
- border-bottom: #ddd 1px solid;
12
- }
13
-
14
- .wpp-cardview li:last-of-type,
15
- .wpp-cardview li:only-child {
16
- margin: 0;
17
- padding: 0;
18
- border-bottom: none;
19
- }
20
-
21
- .wpp-cardview .wpp-thumbnail-container {
22
- position: relative;
23
- margin-bottom: .8em;
24
- }
25
-
26
- .wpp-cardview li .wpp-thumbnail {
27
- overflow: hidden;
28
- display: block;
29
- margin: 0;
30
- width: 100%;
31
- height: auto;
32
- font-size: 0.8em;
33
- line-height: 1;
34
- background: #f0f0f0;
35
- border: none;
36
- }
37
-
38
- .wpp-cardview li a {
39
- text-decoration: none;
40
- }
41
-
42
- .wpp-cardview li a:hover {
43
- text-decoration: underline;
44
- }
45
-
46
- .wpp-cardview li .taxonomies,
47
- .wpp-cardview li .wpp-post-title {
48
- display: block;
49
- font-weight: bold;
50
- }
51
-
52
- .wpp-cardview li .taxonomies {
53
- position: absolute;
54
- bottom: 0;
55
- left: 0;
56
- padding: 1em;
57
- color: #aaa;
58
- font-size: 0.7em;
59
- line-height: 1;
60
- background: rgba(0, 0, 0, 0.5);
61
- }
62
-
63
- .wpp-cardview li .taxonomies a {
64
- color: #fff;
65
- }
66
-
67
- .wpp-cardview li .wpp-post-title {
68
- margin: 0 0 0.5em;
69
- font-size: 1.2em;
70
- line-height: 1.2;
71
- }
72
-
73
- .wpp-cardview li .wpp-excerpt {
74
- margin: 0;
75
- font-size: 0.8em;
 
76
  }
1
+ .wpp-cardview {
2
+ margin-left: 0;
3
+ margin-right: 0;
4
+ padding: 0;
5
+ }
6
+
7
+ .wpp-cardview li {
8
+ list-style: none;
9
+ margin: 0 0 1.2em 0;
10
+ padding: 0 0 1em 0;
11
+ border-bottom: #ddd 1px solid;
12
+ }
13
+
14
+ .wpp-cardview li:last-of-type,
15
+ .wpp-cardview li:only-child {
16
+ margin: 0;
17
+ padding: 0;
18
+ border-bottom: none;
19
+ }
20
+
21
+ .wpp-cardview .wpp-thumbnail-container {
22
+ position: relative;
23
+ margin-bottom: .8em;
24
+ }
25
+
26
+ .wpp-cardview li .wpp-thumbnail {
27
+ overflow: hidden;
28
+ display: block;
29
+ float: none;
30
+ margin: 0;
31
+ width: 100%;
32
+ height: auto;
33
+ font-size: 0.8em;
34
+ line-height: 1;
35
+ background: #f0f0f0;
36
+ border: none;
37
+ }
38
+
39
+ .wpp-cardview li a {
40
+ text-decoration: none;
41
+ }
42
+
43
+ .wpp-cardview li a:hover {
44
+ text-decoration: underline;
45
+ }
46
+
47
+ .wpp-cardview li .taxonomies,
48
+ .wpp-cardview li .wpp-post-title {
49
+ display: block;
50
+ font-weight: bold;
51
+ }
52
+
53
+ .wpp-cardview li .taxonomies {
54
+ position: absolute;
55
+ bottom: 0;
56
+ left: 0;
57
+ padding: 1em;
58
+ color: #aaa;
59
+ font-size: 0.7em;
60
+ line-height: 1;
61
+ background: rgba(0, 0, 0, 0.5);
62
+ }
63
+
64
+ .wpp-cardview li .taxonomies a {
65
+ color: #fff;
66
+ }
67
+
68
+ .wpp-cardview li .wpp-post-title {
69
+ margin: 0 0 0.5em;
70
+ font-size: 1.2em;
71
+ line-height: 1.2;
72
+ }
73
+
74
+ .wpp-cardview li .wpp-excerpt {
75
+ margin: 0;
76
+ font-size: 0.8em;
77
  }
readme.txt CHANGED
@@ -3,9 +3,9 @@ Contributors: hcabrera
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=hcabrerab%40gmail%2ecom&lc=GB&item_name=WordPress%20Popular%20Posts%20Plugin&currency_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG_global%2egif%3aNonHosted
4
  Tags: popular, posts, widget, popularity, top
5
  Requires at least: 4.9
6
- Tested up to: 5.4.2
7
  Requires PHP: 5.4
8
- Stable tag: 5.2.2
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
@@ -90,6 +90,15 @@ The FAQ section has been moved [here](https://github.com/cabrerahector/wordpress
90
  4. Statistics panel.
91
 
92
  == Changelog ==
 
 
 
 
 
 
 
 
 
93
  = 5.2.2 =
94
 
95
  **If you're using a caching plugin, flushing its cache right after installing / upgrading to this version is required.**
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=hcabrerab%40gmail%2ecom&lc=GB&item_name=WordPress%20Popular%20Posts%20Plugin&currency_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG_global%2egif%3aNonHosted
4
  Tags: popular, posts, widget, popularity, top
5
  Requires at least: 4.9
6
+ Tested up to: 5.5
7
  Requires PHP: 5.4
8
+ Stable tag: 5.2.3
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
90
  4. Statistics panel.
91
 
92
  == Changelog ==
93
+ = 5.2.3 =
94
+
95
+ **If you're using a caching plugin, flushing its cache right after installing / upgrading to this version is required.**
96
+
97
+ - Fixes a compatibility issue with WordPress 5.5.
98
+ - Widget themes: various fixes for better IE11 compatibility.
99
+
100
+ [Release notes](https://cabrerahector.com/wordpress/wordpress-popular-posts-5-2-is-here#hotfixes-and-minor-updates)
101
+
102
  = 5.2.2 =
103
 
104
  **If you're using a caching plugin, flushing its cache right after installing / upgrading to this version is required.**
src/Front/Front.php CHANGED
@@ -1,460 +1,474 @@
1
- <?php
2
- /**
3
- * The public-facing functionality of the plugin.
4
- *
5
- * Defines hooks to enqueue the public-specific stylesheet and JavaScript.
6
- *
7
- * @package WordPressPopularPosts
8
- * @subpackage WordPressPopularPosts/Front
9
- * @author Hector Cabrera <me@cabrerahector.com>
10
- */
11
-
12
- namespace WordPressPopularPosts\Front;
13
-
14
- use WordPressPopularPosts\Helper;
15
- use WordPressPopularPosts\Output;
16
- use WordPressPopularPosts\Query;
17
-
18
- class Front {
19
-
20
- /**
21
- * Plugin options.
22
- *
23
- * @var array $config
24
- * @access private
25
- */
26
- private $config;
27
-
28
- /**
29
- * Translate object.
30
- *
31
- * @var \WordPressPopularPosts\Translate $translate
32
- * @access private
33
- */
34
- private $translate;
35
-
36
- /**
37
- * Output object.
38
- *
39
- * @var \WordPressPopularPosts\Output $output
40
- * @access private
41
- */
42
- private $output;
43
-
44
- /**
45
- * Construct.
46
- *
47
- * @since 5.0.0
48
- * @param array $config Admin settings.
49
- * @param \WordPressPopularPosts\Translate $translate Translate class.
50
- */
51
- public function __construct(array $config, \WordPressPopularPosts\Translate $translate, \WordPressPopularPosts\Output $output)
52
- {
53
- $this->config = $config;
54
- $this->translate = $translate;
55
- $this->output = $output;
56
- }
57
-
58
- /**
59
- * WordPress public-facing hooks.
60
- *
61
- * @since 5.0.0
62
- */
63
- public function hooks()
64
- {
65
- add_shortcode('wpp', [$this, 'wpp_shortcode']);
66
- add_action('wp_ajax_update_views_ajax', [$this, 'update_views']);
67
- add_action('wp_ajax_nopriv_update_views_ajax', [$this, 'update_views']);
68
- add_action('wp_enqueue_scripts', [$this, 'enqueue_assets']);
69
- add_filter('script_loader_tag', [$this, 'convert_inline_js_into_json'], 10, 3);
70
- }
71
-
72
- /**
73
- * Enqueues public facing assets.
74
- *
75
- * @since 5.0.0
76
- */
77
- public function enqueue_assets()
78
- {
79
- // Enqueue WPP's stylesheet.
80
- if ( $this->config['tools']['css'] ) {
81
- $theme_file = get_stylesheet_directory() . '/wpp.css';
82
-
83
- if ( @is_file($theme_file) ) {
84
- wp_enqueue_style('wordpress-popular-posts-css', get_stylesheet_directory_uri() . "/wpp.css", [], WPP_VERSION, 'all');
85
- } // Load stock stylesheet
86
- else {
87
- wp_enqueue_style('wordpress-popular-posts-css', plugin_dir_url(dirname(dirname(__FILE__))) . 'assets/css/wpp.css', [], WPP_VERSION, 'all');
88
- }
89
- }
90
-
91
- // Enqueue WPP's library.
92
- $is_single = 0;
93
-
94
- if (
95
- ( 0 == $this->config['tools']['log']['level'] && ! is_user_logged_in() )
96
- || ( 1 == $this->config['tools']['log']['level'] )
97
- || ( 2 == $this->config['tools']['log']['level'] && is_user_logged_in() )
98
- ) {
99
- $is_single = Helper::is_single();
100
- }
101
-
102
- wp_register_script('wpp-js', plugin_dir_url(dirname(dirname(__FILE__))) . 'assets/js/wpp.min.js', [], WPP_VERSION, false);
103
- $params = [
104
- 'sampling_active' => (int) $this->config['tools']['sampling']['active'],
105
- 'sampling_rate' => (int) $this->config['tools']['sampling']['rate'],
106
- 'ajax_url' => esc_url_raw(rest_url('wordpress-popular-posts/v1/popular-posts')),
107
- 'ID' => (int) $is_single,
108
- 'token' => wp_create_nonce('wp_rest'),
109
- 'lang' => function_exists('PLL') ? $this->translate->get_current_language() : 0,
110
- 'debug' => (int) WP_DEBUG
111
- ];
112
- wp_enqueue_script('wpp-js');
113
- wp_add_inline_script('wpp-js', json_encode($params), 'before');
114
- }
115
-
116
- /**
117
- * Converts inline script tag into type=application/json.
118
- *
119
- * This function mods the original script tag as printed
120
- * by WordPress which contains the data for the wpp_params
121
- * object into a JSON script. This improves compatibility
122
- * with Content Security Policy (CSP).
123
- *
124
- * @since 5.2.0
125
- * @param string $tag
126
- * @param string $handle
127
- * @param string $src
128
- * @return string $tag
129
- */
130
- function convert_inline_js_into_json($tag, $handle, $src)
131
- {
132
- if ( 'wpp-js' === $handle ) {
133
- if ( false !== strpos($tag, 'type') ) {
134
- $tag = preg_replace("%[ ]type=[\'\"]text\/javascript[\'\"]%", ' type="application/json" id="wpp-json"', $tag, 1);
135
- } else {
136
- $pos = strpos($tag, '>');
137
- $tag = substr_replace($tag, ' type="application/json" id="wpp-json">', $pos, 1);
138
- }
139
- }
140
-
141
- return $tag;
142
- }
143
-
144
- /**
145
- * Updates views count on page load via AJAX.
146
- *
147
- * @since 2.0.0
148
- */
149
- public function update_views()
150
- {
151
- if ( ! wp_verify_nonce($_POST['token'], 'wpp-token') || ! Helper::is_number($_POST['wpp_id']) ) {
152
- die( "WPP: Oops, invalid request!" );
153
- }
154
-
155
- $nonce = $_POST['token'];
156
- $post_ID = $_POST['wpp_id'];
157
- $exec_time = 0;
158
-
159
- $start = Helper::microtime_float();
160
- $result = $this->update_views_count($post_ID);
161
- $end = Helper::microtime_float();
162
- $exec_time += round($end - $start, 6);
163
-
164
- if ( $result ) {
165
- die("WPP: OK. Execution time: " . $exec_time . " seconds");
166
- }
167
-
168
- die("WPP: Oops, could not update the views count!");
169
- }
170
-
171
- /**
172
- * Updates views count.
173
- *
174
- * @since 1.4.0
175
- * @access private
176
- * @global object $wpdb
177
- * @param int $post_ID
178
- * @return bool|int FALSE if query failed, TRUE on success
179
- */
180
- private function update_views_count($post_ID) {
181
- /*
182
- TODO:
183
- For WordPress Multisite, we must define the DIEONDBERROR constant for database errors to display like so:
184
- <?php define( 'DIEONDBERROR', true ); ?>
185
- */
186
- global $wpdb;
187
- $table = $wpdb->prefix . "popularposts";
188
- $wpdb->show_errors();
189
-
190
- // Get translated object ID
191
- $post_ID = $this->translate->get_object_id(
192
- $post_ID,
193
- get_post_type($post_ID),
194
- true,
195
- $this->translate->get_default_language()
196
- );
197
- $now = Helper::now();
198
- $curdate = Helper::curdate();
199
- $views = ( $this->config['tools']['sampling']['active'] )
200
- ? $this->config['tools']['sampling']['rate']
201
- : 1;
202
-
203
- // Allow WP themers / coders perform an action
204
- // before updating views count
205
- if ( has_action('wpp_pre_update_views') )
206
- do_action('wpp_pre_update_views', $post_ID, $views);
207
-
208
- // Update all-time table
209
- $result1 = $wpdb->query($wpdb->prepare(
210
- "INSERT INTO {$table}data
211
- (postid, day, last_viewed, pageviews) VALUES (%d, %s, %s, %d)
212
- ON DUPLICATE KEY UPDATE pageviews = pageviews + %d, last_viewed = %s;",
213
- $post_ID,
214
- $now,
215
- $now,
216
- $views,
217
- $views,
218
- $now
219
- ));
220
-
221
- // Update range (summary) table
222
- $result2 = $wpdb->query($wpdb->prepare(
223
- "INSERT INTO {$table}summary
224
- (postid, pageviews, view_date, view_datetime) VALUES (%d, %d, %s, %s)
225
- ON DUPLICATE KEY UPDATE pageviews = pageviews + %d, view_datetime = %s;",
226
- $post_ID,
227
- $views,
228
- $curdate,
229
- $now,
230
- $views,
231
- $now
232
- ));
233
-
234
- if ( !$result1 || !$result2 )
235
- return false;
236
-
237
- // Allow WP themers / coders perform an action
238
- // after updating views count
239
- if ( has_action('wpp_post_update_views' ))
240
- do_action('wpp_post_update_views', $post_ID);
241
-
242
- return true;
243
- }
244
-
245
- /**
246
- * WPP shortcode handler.
247
- *
248
- * @since 1.4.0
249
- * @param array $atts User defined attributes in shortcode tag
250
- * @return string
251
- */
252
- public function wpp_shortcode($atts = null) {
253
- /**
254
- * @var string $header
255
- * @var int $limit
256
- * @var int $offset
257
- * @var string $range
258
- * @var bool $freshness
259
- * @var string $order_by
260
- * @var string $post_type
261
- * @var string $pid
262
- * @var string $cat
263
- * @var string $author
264
- * @var int $title_length
265
- * @var int $title_by_words
266
- * @var int $excerpt_length
267
- * @var int $excerpt_format
268
- * @var int $excerpt_by_words
269
- * @var int $thumbnail_width
270
- * @var int $thumbnail_height
271
- * @var bool $rating
272
- * @var bool $stats_comments
273
- * @var bool $stats_views
274
- * @var bool $stats_author
275
- * @var bool $stats_date
276
- * @var string $stats_date_format
277
- * @var bool $stats_category
278
- * @var string $wpp_start
279
- * @var string $wpp_end
280
- * @var string $header_start
281
- * @var string $header_end
282
- * @var string $post_html
283
- * @var bool $php
284
- */
285
- extract(shortcode_atts([
286
- 'header' => '',
287
- 'limit' => 10,
288
- 'offset' => 0,
289
- 'range' => 'daily',
290
- 'time_unit' => 'hour',
291
- 'time_quantity' => 24,
292
- 'freshness' => false,
293
- 'order_by' => 'views',
294
- 'post_type' => 'post',
295
- 'pid' => '',
296
- 'cat' => '',
297
- 'taxonomy' => 'category',
298
- 'term_id' => '',
299
- 'author' => '',
300
- 'title_length' => 0,
301
- 'title_by_words' => 0,
302
- 'excerpt_length' => 0,
303
- 'excerpt_format' => 0,
304
- 'excerpt_by_words' => 0,
305
- 'thumbnail_width' => 0,
306
- 'thumbnail_height' => 0,
307
- 'rating' => false,
308
- 'stats_comments' => false,
309
- 'stats_views' => true,
310
- 'stats_author' => false,
311
- 'stats_date' => false,
312
- 'stats_date_format' => 'F j, Y',
313
- 'stats_category' => false,
314
- 'stats_taxonomy' => false,
315
- 'wpp_start' => '<ul class="wpp-list">',
316
- 'wpp_end' => '</ul>',
317
- 'header_start' => '<h2>',
318
- 'header_end' => '</h2>',
319
- 'post_html' => '',
320
- 'php' => false
321
- ], $atts, 'wpp'));
322
-
323
- // possible values for "Time Range" and "Order by"
324
- $time_units = ["minute", "hour", "day", "week", "month"];
325
- $range_values = ["daily", "last24hours", "weekly", "last7days", "monthly", "last30days", "all", "custom"];
326
- $order_by_values = ["comments", "views", "avg"];
327
-
328
- $shortcode_ops = [
329
- 'title' => strip_tags($header),
330
- 'limit' => ( ! empty($limit ) && Helper::is_number($limit) && $limit > 0 ) ? $limit : 10,
331
- 'offset' => ( ! empty($offset) && Helper::is_number($offset) && $offset >= 0 ) ? $offset : 0,
332
- 'range' => ( in_array($range, $range_values) ) ? $range : 'daily',
333
- 'time_quantity' => ( ! empty($time_quantity ) && Helper::is_number($time_quantity) && $time_quantity > 0 ) ? $time_quantity : 24,
334
- 'time_unit' => ( in_array($time_unit, $time_units) ) ? $time_unit : 'hour',
335
- 'freshness' => empty($freshness) ? false : $freshness,
336
- 'order_by' => ( in_array($order_by, $order_by_values) ) ? $order_by : 'views',
337
- 'post_type' => empty($post_type) ? 'post,page' : $post_type,
338
- 'pid' => rtrim(preg_replace('|[^0-9,]|', '', $pid), ","),
339
- 'cat' => rtrim(preg_replace('|[^0-9,-]|', '', $cat), ","),
340
- 'taxonomy' => empty($taxonomy) ? 'category' : $taxonomy,
341
- 'term_id' => rtrim(preg_replace('|[^0-9,;-]|', '', $term_id), ","),
342
- 'author' => rtrim(preg_replace('|[^0-9,]|', '', $author), ","),
343
- 'shorten_title' => [
344
- 'active' => ( ! empty($title_length) && Helper::is_number($title_length) && $title_length > 0 ),
345
- 'length' => ( ! empty($title_length) && Helper::is_number($title_length) ) ? $title_length : 0,
346
- 'words' => ( ! empty($title_by_words) && Helper::is_number($title_by_words) && $title_by_words > 0 ),
347
- ],
348
- 'post-excerpt' => [
349
- 'active' => ( ! empty($excerpt_length) && Helper::is_number($excerpt_length) && $excerpt_length > 0 ),
350
- 'length' => ( ! empty($excerpt_length) && Helper::is_number($excerpt_length) ) ? $excerpt_length : 0,
351
- 'keep_format' => ( ! empty($excerpt_format) && Helper::is_number($excerpt_format) && $excerpt_format > 0 ),
352
- 'words' => ( ! empty($excerpt_by_words) && Helper::is_number($excerpt_by_words) && $excerpt_by_words > 0 ),
353
- ],
354
- 'thumbnail' => [
355
- 'active' => ( ! empty($thumbnail_width) && Helper::is_number($thumbnail_width) && $thumbnail_width > 0 ),
356
- 'width' => ( ! empty($thumbnail_width) && Helper::is_number($thumbnail_width) && $thumbnail_width > 0 ) ? $thumbnail_width : 0,
357
- 'height' => ( ! empty($thumbnail_height) && Helper::is_number($thumbnail_height) && $thumbnail_height > 0 ) ? $thumbnail_height : 0,
358
- ],
359
- 'rating' => empty($rating) ? false : $rating,
360
- 'stats_tag' => [
361
- 'comment_count' => empty($stats_comments) ? false : $stats_comments,
362
- 'views' => empty($stats_views) ? false : $stats_views,
363
- 'author' => empty($stats_author) ? false : $stats_author,
364
- 'date' => [
365
- 'active' => empty($stats_date) ? false : $stats_date,
366
- 'format' => empty($stats_date_format) ? 'F j, Y' : $stats_date_format
367
- ],
368
- 'category' => empty($stats_category) ? false : $stats_category,
369
- 'taxonomy' => [
370
- 'active' => empty($stats_taxonomy) ? false : $stats_taxonomy,
371
- 'name' => empty($taxonomy) ? 'category' : $taxonomy,
372
- ]
373
- ],
374
- 'markup' => [
375
- 'custom_html' => true,
376
- 'wpp-start' => empty($wpp_start) ? '' : $wpp_start,
377
- 'wpp-end' => empty($wpp_end) ? '' : $wpp_end,
378
- 'title-start' => empty($header_start) ? '' : $header_start,
379
- 'title-end' => empty($header_end) ? '' : $header_end,
380
- 'post-html' => empty($post_html) ? '<li>{thumb} {title} <span class="wpp-meta post-stats">{stats}</span></li>' : $post_html
381
- ]
382
- ];
383
-
384
- // Post / Page / CTP filter
385
- $ids = array_filter(explode(",", $shortcode_ops['pid']), 'is_numeric');
386
- // Got no valid IDs, clear
387
- if ( empty($ids) ) {
388
- $shortcode_ops['pid'] = '';
389
- }
390
-
391
- // Category filter
392
- $ids = array_filter(explode(",", $shortcode_ops['cat']), 'is_numeric');
393
- // Got no valid IDs, clear
394
- if ( empty($ids) ) {
395
- $shortcode_ops['cat'] = '';
396
- }
397
-
398
- // Author filter
399
- $ids = array_filter(explode( ",", $shortcode_ops['author']), 'is_numeric');
400
- // Got no valid IDs, clear
401
- if ( empty($ids) ) {
402
- $shortcode_ops['author'] = '';
403
- }
404
-
405
- $shortcode_content = '';
406
-
407
- // is there a title defined by user?
408
- if (
409
- ! empty($header)
410
- && ! empty($header_start)
411
- && ! empty($header_end)
412
- ) {
413
- $shortcode_content .= htmlspecialchars_decode($header_start, ENT_QUOTES) . $header . htmlspecialchars_decode($header_end, ENT_QUOTES);
414
- }
415
-
416
- $cached = false;
417
-
418
- // Return cached results
419
- if ( $this->config['tools']['cache']['active'] ) {
420
-
421
- $key = md5(json_encode($shortcode_ops));
422
- $popular_posts = \WordPressPopularPosts\Cache::get($key);
423
-
424
- if ( false === $popular_posts ) {
425
- $popular_posts = new Query($shortcode_ops);
426
-
427
- $time_value = $this->config['tools']['cache']['interval']['value']; // eg. 5
428
- $time_unit = $this->config['tools']['cache']['interval']['time']; // eg. 'minute'
429
-
430
- // No popular posts found, check again in 1 minute
431
- if ( ! $popular_posts->get_posts() ) {
432
- $time_value = 1;
433
- $time_unit = 'minute';
434
- }
435
-
436
- \WordPressPopularPosts\Cache::set(
437
- $key,
438
- $popular_posts,
439
- $time_value,
440
- $time_unit
441
- );
442
- }
443
-
444
- $cached = true;
445
-
446
- } // Get popular posts
447
- else {
448
- $popular_posts = new Query($shortcode_ops);
449
- }
450
-
451
- $this->output->set_data($popular_posts->get_posts());
452
- $this->output->set_public_options($shortcode_ops);
453
- $this->output->build_output();
454
-
455
- $shortcode_content .= $this->output->get_output();
456
-
457
- return $shortcode_content;
458
- }
459
-
460
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * The public-facing functionality of the plugin.
4
+ *
5
+ * Defines hooks to enqueue the public-specific stylesheet and JavaScript.
6
+ *
7
+ * @package WordPressPopularPosts
8
+ * @subpackage WordPressPopularPosts/Front
9
+ * @author Hector Cabrera <me@cabrerahector.com>
10
+ */
11
+
12
+ namespace WordPressPopularPosts\Front;
13
+
14
+ use WordPressPopularPosts\Helper;
15
+ use WordPressPopularPosts\Output;
16
+ use WordPressPopularPosts\Query;
17
+
18
+ class Front {
19
+
20
+ /**
21
+ * Plugin options.
22
+ *
23
+ * @var array $config
24
+ * @access private
25
+ */
26
+ private $config;
27
+
28
+ /**
29
+ * Translate object.
30
+ *
31
+ * @var \WordPressPopularPosts\Translate $translate
32
+ * @access private
33
+ */
34
+ private $translate;
35
+
36
+ /**
37
+ * Output object.
38
+ *
39
+ * @var \WordPressPopularPosts\Output $output
40
+ * @access private
41
+ */
42
+ private $output;
43
+
44
+ /**
45
+ * Construct.
46
+ *
47
+ * @since 5.0.0
48
+ * @param array $config Admin settings.
49
+ * @param \WordPressPopularPosts\Translate $translate Translate class.
50
+ */
51
+ public function __construct(array $config, \WordPressPopularPosts\Translate $translate, \WordPressPopularPosts\Output $output)
52
+ {
53
+ $this->config = $config;
54
+ $this->translate = $translate;
55
+ $this->output = $output;
56
+ }
57
+
58
+ /**
59
+ * WordPress public-facing hooks.
60
+ *
61
+ * @since 5.0.0
62
+ */
63
+ public function hooks()
64
+ {
65
+ add_shortcode('wpp', [$this, 'wpp_shortcode']);
66
+ add_action('wp_ajax_update_views_ajax', [$this, 'update_views']);
67
+ add_action('wp_ajax_nopriv_update_views_ajax', [$this, 'update_views']);
68
+ add_action('wp_enqueue_scripts', [$this, 'enqueue_assets']);
69
+ add_filter('script_loader_tag', [$this, 'convert_inline_js_into_json'], 10, 3);
70
+ }
71
+
72
+ /**
73
+ * Enqueues public facing assets.
74
+ *
75
+ * @since 5.0.0
76
+ */
77
+ public function enqueue_assets()
78
+ {
79
+ // Enqueue WPP's stylesheet.
80
+ if ( $this->config['tools']['css'] ) {
81
+ $theme_file = get_stylesheet_directory() . '/wpp.css';
82
+
83
+ if ( @is_file($theme_file) ) {
84
+ wp_enqueue_style('wordpress-popular-posts-css', get_stylesheet_directory_uri() . "/wpp.css", [], WPP_VERSION, 'all');
85
+ } // Load stock stylesheet
86
+ else {
87
+ wp_enqueue_style('wordpress-popular-posts-css', plugin_dir_url(dirname(dirname(__FILE__))) . 'assets/css/wpp.css', [], WPP_VERSION, 'all');
88
+ }
89
+ }
90
+
91
+ // Enqueue WPP's library.
92
+ $is_single = 0;
93
+
94
+ if (
95
+ ( 0 == $this->config['tools']['log']['level'] && ! is_user_logged_in() )
96
+ || ( 1 == $this->config['tools']['log']['level'] )
97
+ || ( 2 == $this->config['tools']['log']['level'] && is_user_logged_in() )
98
+ ) {
99
+ $is_single = Helper::is_single();
100
+ }
101
+
102
+ wp_register_script('wpp-js', plugin_dir_url(dirname(dirname(__FILE__))) . 'assets/js/wpp.min.js', [], WPP_VERSION, false);
103
+ $params = [
104
+ 'sampling_active' => (int) $this->config['tools']['sampling']['active'],
105
+ 'sampling_rate' => (int) $this->config['tools']['sampling']['rate'],
106
+ 'ajax_url' => esc_url_raw(rest_url('wordpress-popular-posts/v1/popular-posts')),
107
+ 'ID' => (int) $is_single,
108
+ 'token' => wp_create_nonce('wp_rest'),
109
+ 'lang' => function_exists('PLL') ? $this->translate->get_current_language() : 0,
110
+ 'debug' => (int) WP_DEBUG
111
+ ];
112
+ wp_enqueue_script('wpp-js');
113
+ wp_add_inline_script('wpp-js', json_encode($params), 'before');
114
+ }
115
+
116
+ /**
117
+ * Converts inline script tag into type=application/json.
118
+ *
119
+ * This function mods the original script tag as printed
120
+ * by WordPress which contains the data for the wpp_params
121
+ * object into a JSON script. This improves compatibility
122
+ * with Content Security Policy (CSP).
123
+ *
124
+ * @since 5.2.0
125
+ * @param string $tag
126
+ * @param string $handle
127
+ * @param string $src
128
+ * @return string $tag
129
+ */
130
+ function convert_inline_js_into_json($tag, $handle, $src)
131
+ {
132
+ if ( 'wpp-js' === $handle ) {
133
+ // id attribute found, replace it
134
+ if ( false !== strpos($tag, 'wpp-js-js-before') ) {
135
+ $tag = str_replace('wpp-js-js-before', 'wpp-json', $tag);
136
+ } // id attribute missing, let's add it
137
+ else {
138
+ $pos = strpos($tag, '>');
139
+ $tag = substr_replace($tag, ' id="wpp-json">', $pos, 1);
140
+ }
141
+
142
+ // type attribute found, replace it
143
+ if ( false !== strpos($tag, 'type') ) {
144
+ $pos = strpos($tag, 'text/javascript');
145
+
146
+ if ( false !== $pos )
147
+ $tag = substr_replace($tag, 'application/json', $pos, strlen('text/javascript'));
148
+ } // type attribute missing, let's add it
149
+ else {
150
+ $pos = strpos($tag, '>');
151
+ $tag = substr_replace($tag, ' type="application/json">', $pos, 1);
152
+ }
153
+ }
154
+
155
+ return $tag;
156
+ }
157
+
158
+ /**
159
+ * Updates views count on page load via AJAX.
160
+ *
161
+ * @since 2.0.0
162
+ */
163
+ public function update_views()
164
+ {
165
+ if ( ! wp_verify_nonce($_POST['token'], 'wpp-token') || ! Helper::is_number($_POST['wpp_id']) ) {
166
+ die( "WPP: Oops, invalid request!" );
167
+ }
168
+
169
+ $nonce = $_POST['token'];
170
+ $post_ID = $_POST['wpp_id'];
171
+ $exec_time = 0;
172
+
173
+ $start = Helper::microtime_float();
174
+ $result = $this->update_views_count($post_ID);
175
+ $end = Helper::microtime_float();
176
+ $exec_time += round($end - $start, 6);
177
+
178
+ if ( $result ) {
179
+ die("WPP: OK. Execution time: " . $exec_time . " seconds");
180
+ }
181
+
182
+ die("WPP: Oops, could not update the views count!");
183
+ }
184
+
185
+ /**
186
+ * Updates views count.
187
+ *
188
+ * @since 1.4.0
189
+ * @access private
190
+ * @global object $wpdb
191
+ * @param int $post_ID
192
+ * @return bool|int FALSE if query failed, TRUE on success
193
+ */
194
+ private function update_views_count($post_ID) {
195
+ /*
196
+ TODO:
197
+ For WordPress Multisite, we must define the DIEONDBERROR constant for database errors to display like so:
198
+ <?php define( 'DIEONDBERROR', true ); ?>
199
+ */
200
+ global $wpdb;
201
+ $table = $wpdb->prefix . "popularposts";
202
+ $wpdb->show_errors();
203
+
204
+ // Get translated object ID
205
+ $post_ID = $this->translate->get_object_id(
206
+ $post_ID,
207
+ get_post_type($post_ID),
208
+ true,
209
+ $this->translate->get_default_language()
210
+ );
211
+ $now = Helper::now();
212
+ $curdate = Helper::curdate();
213
+ $views = ( $this->config['tools']['sampling']['active'] )
214
+ ? $this->config['tools']['sampling']['rate']
215
+ : 1;
216
+
217
+ // Allow WP themers / coders perform an action
218
+ // before updating views count
219
+ if ( has_action('wpp_pre_update_views') )
220
+ do_action('wpp_pre_update_views', $post_ID, $views);
221
+
222
+ // Update all-time table
223
+ $result1 = $wpdb->query($wpdb->prepare(
224
+ "INSERT INTO {$table}data
225
+ (postid, day, last_viewed, pageviews) VALUES (%d, %s, %s, %d)
226
+ ON DUPLICATE KEY UPDATE pageviews = pageviews + %d, last_viewed = %s;",
227
+ $post_ID,
228
+ $now,
229
+ $now,
230
+ $views,
231
+ $views,
232
+ $now
233
+ ));
234
+
235
+ // Update range (summary) table
236
+ $result2 = $wpdb->query($wpdb->prepare(
237
+ "INSERT INTO {$table}summary
238
+ (postid, pageviews, view_date, view_datetime) VALUES (%d, %d, %s, %s)
239
+ ON DUPLICATE KEY UPDATE pageviews = pageviews + %d, view_datetime = %s;",
240
+ $post_ID,
241
+ $views,
242
+ $curdate,
243
+ $now,
244
+ $views,
245
+ $now
246
+ ));
247
+
248
+ if ( !$result1 || !$result2 )
249
+ return false;
250
+
251
+ // Allow WP themers / coders perform an action
252
+ // after updating views count
253
+ if ( has_action('wpp_post_update_views' ))
254
+ do_action('wpp_post_update_views', $post_ID);
255
+
256
+ return true;
257
+ }
258
+
259
+ /**
260
+ * WPP shortcode handler.
261
+ *
262
+ * @since 1.4.0
263
+ * @param array $atts User defined attributes in shortcode tag
264
+ * @return string
265
+ */
266
+ public function wpp_shortcode($atts = null) {
267
+ /**
268
+ * @var string $header
269
+ * @var int $limit
270
+ * @var int $offset
271
+ * @var string $range
272
+ * @var bool $freshness
273
+ * @var string $order_by
274
+ * @var string $post_type
275
+ * @var string $pid
276
+ * @var string $cat
277
+ * @var string $author
278
+ * @var int $title_length
279
+ * @var int $title_by_words
280
+ * @var int $excerpt_length
281
+ * @var int $excerpt_format
282
+ * @var int $excerpt_by_words
283
+ * @var int $thumbnail_width
284
+ * @var int $thumbnail_height
285
+ * @var bool $rating
286
+ * @var bool $stats_comments
287
+ * @var bool $stats_views
288
+ * @var bool $stats_author
289
+ * @var bool $stats_date
290
+ * @var string $stats_date_format
291
+ * @var bool $stats_category
292
+ * @var string $wpp_start
293
+ * @var string $wpp_end
294
+ * @var string $header_start
295
+ * @var string $header_end
296
+ * @var string $post_html
297
+ * @var bool $php
298
+ */
299
+ extract(shortcode_atts([
300
+ 'header' => '',
301
+ 'limit' => 10,
302
+ 'offset' => 0,
303
+ 'range' => 'daily',
304
+ 'time_unit' => 'hour',
305
+ 'time_quantity' => 24,
306
+ 'freshness' => false,
307
+ 'order_by' => 'views',
308
+ 'post_type' => 'post',
309
+ 'pid' => '',
310
+ 'cat' => '',
311
+ 'taxonomy' => 'category',
312
+ 'term_id' => '',
313
+ 'author' => '',
314
+ 'title_length' => 0,
315
+ 'title_by_words' => 0,
316
+ 'excerpt_length' => 0,
317
+ 'excerpt_format' => 0,
318
+ 'excerpt_by_words' => 0,
319
+ 'thumbnail_width' => 0,
320
+ 'thumbnail_height' => 0,
321
+ 'rating' => false,
322
+ 'stats_comments' => false,
323
+ 'stats_views' => true,
324
+ 'stats_author' => false,
325
+ 'stats_date' => false,
326
+ 'stats_date_format' => 'F j, Y',
327
+ 'stats_category' => false,
328
+ 'stats_taxonomy' => false,
329
+ 'wpp_start' => '<ul class="wpp-list">',
330
+ 'wpp_end' => '</ul>',
331
+ 'header_start' => '<h2>',
332
+ 'header_end' => '</h2>',
333
+ 'post_html' => '',
334
+ 'php' => false
335
+ ], $atts, 'wpp'));
336
+
337
+ // possible values for "Time Range" and "Order by"
338
+ $time_units = ["minute", "hour", "day", "week", "month"];
339
+ $range_values = ["daily", "last24hours", "weekly", "last7days", "monthly", "last30days", "all", "custom"];
340
+ $order_by_values = ["comments", "views", "avg"];
341
+
342
+ $shortcode_ops = [
343
+ 'title' => strip_tags($header),
344
+ 'limit' => ( ! empty($limit ) && Helper::is_number($limit) && $limit > 0 ) ? $limit : 10,
345
+ 'offset' => ( ! empty($offset) && Helper::is_number($offset) && $offset >= 0 ) ? $offset : 0,
346
+ 'range' => ( in_array($range, $range_values) ) ? $range : 'daily',
347
+ 'time_quantity' => ( ! empty($time_quantity ) && Helper::is_number($time_quantity) && $time_quantity > 0 ) ? $time_quantity : 24,
348
+ 'time_unit' => ( in_array($time_unit, $time_units) ) ? $time_unit : 'hour',
349
+ 'freshness' => empty($freshness) ? false : $freshness,
350
+ 'order_by' => ( in_array($order_by, $order_by_values) ) ? $order_by : 'views',
351
+ 'post_type' => empty($post_type) ? 'post,page' : $post_type,
352
+ 'pid' => rtrim(preg_replace('|[^0-9,]|', '', $pid), ","),
353
+ 'cat' => rtrim(preg_replace('|[^0-9,-]|', '', $cat), ","),
354
+ 'taxonomy' => empty($taxonomy) ? 'category' : $taxonomy,
355
+ 'term_id' => rtrim(preg_replace('|[^0-9,;-]|', '', $term_id), ","),
356
+ 'author' => rtrim(preg_replace('|[^0-9,]|', '', $author), ","),
357
+ 'shorten_title' => [
358
+ 'active' => ( ! empty($title_length) && Helper::is_number($title_length) && $title_length > 0 ),
359
+ 'length' => ( ! empty($title_length) && Helper::is_number($title_length) ) ? $title_length : 0,
360
+ 'words' => ( ! empty($title_by_words) && Helper::is_number($title_by_words) && $title_by_words > 0 ),
361
+ ],
362
+ 'post-excerpt' => [
363
+ 'active' => ( ! empty($excerpt_length) && Helper::is_number($excerpt_length) && $excerpt_length > 0 ),
364
+ 'length' => ( ! empty($excerpt_length) && Helper::is_number($excerpt_length) ) ? $excerpt_length : 0,
365
+ 'keep_format' => ( ! empty($excerpt_format) && Helper::is_number($excerpt_format) && $excerpt_format > 0 ),
366
+ 'words' => ( ! empty($excerpt_by_words) && Helper::is_number($excerpt_by_words) && $excerpt_by_words > 0 ),
367
+ ],
368
+ 'thumbnail' => [
369
+ 'active' => ( ! empty($thumbnail_width) && Helper::is_number($thumbnail_width) && $thumbnail_width > 0 ),
370
+ 'width' => ( ! empty($thumbnail_width) && Helper::is_number($thumbnail_width) && $thumbnail_width > 0 ) ? $thumbnail_width : 0,
371
+ 'height' => ( ! empty($thumbnail_height) && Helper::is_number($thumbnail_height) && $thumbnail_height > 0 ) ? $thumbnail_height : 0,
372
+ ],
373
+ 'rating' => empty($rating) ? false : $rating,
374
+ 'stats_tag' => [
375
+ 'comment_count' => empty($stats_comments) ? false : $stats_comments,
376
+ 'views' => empty($stats_views) ? false : $stats_views,
377
+ 'author' => empty($stats_author) ? false : $stats_author,
378
+ 'date' => [
379
+ 'active' => empty($stats_date) ? false : $stats_date,
380
+ 'format' => empty($stats_date_format) ? 'F j, Y' : $stats_date_format
381
+ ],
382
+ 'category' => empty($stats_category) ? false : $stats_category,
383
+ 'taxonomy' => [
384
+ 'active' => empty($stats_taxonomy) ? false : $stats_taxonomy,
385
+ 'name' => empty($taxonomy) ? 'category' : $taxonomy,
386
+ ]
387
+ ],
388
+ 'markup' => [
389
+ 'custom_html' => true,
390
+ 'wpp-start' => empty($wpp_start) ? '' : $wpp_start,
391
+ 'wpp-end' => empty($wpp_end) ? '' : $wpp_end,
392
+ 'title-start' => empty($header_start) ? '' : $header_start,
393
+ 'title-end' => empty($header_end) ? '' : $header_end,
394
+ 'post-html' => empty($post_html) ? '<li>{thumb} {title} <span class="wpp-meta post-stats">{stats}</span></li>' : $post_html
395
+ ]
396
+ ];
397
+
398
+ // Post / Page / CTP filter
399
+ $ids = array_filter(explode(",", $shortcode_ops['pid']), 'is_numeric');
400
+ // Got no valid IDs, clear
401
+ if ( empty($ids) ) {
402
+ $shortcode_ops['pid'] = '';
403
+ }
404
+
405
+ // Category filter
406
+ $ids = array_filter(explode(",", $shortcode_ops['cat']), 'is_numeric');
407
+ // Got no valid IDs, clear
408
+ if ( empty($ids) ) {
409
+ $shortcode_ops['cat'] = '';
410
+ }
411
+
412
+ // Author filter
413
+ $ids = array_filter(explode( ",", $shortcode_ops['author']), 'is_numeric');
414
+ // Got no valid IDs, clear
415
+ if ( empty($ids) ) {
416
+ $shortcode_ops['author'] = '';
417
+ }
418
+
419
+ $shortcode_content = '';
420
+
421
+ // is there a title defined by user?
422
+ if (
423
+ ! empty($header)
424
+ && ! empty($header_start)
425
+ && ! empty($header_end)
426
+ ) {
427
+ $shortcode_content .= htmlspecialchars_decode($header_start, ENT_QUOTES) . $header . htmlspecialchars_decode($header_end, ENT_QUOTES);
428
+ }
429
+
430
+ $cached = false;
431
+
432
+ // Return cached results
433
+ if ( $this->config['tools']['cache']['active'] ) {
434
+
435
+ $key = md5(json_encode($shortcode_ops));
436
+ $popular_posts = \WordPressPopularPosts\Cache::get($key);
437
+
438
+ if ( false === $popular_posts ) {
439
+ $popular_posts = new Query($shortcode_ops);
440
+
441
+ $time_value = $this->config['tools']['cache']['interval']['value']; // eg. 5
442
+ $time_unit = $this->config['tools']['cache']['interval']['time']; // eg. 'minute'
443
+
444
+ // No popular posts found, check again in 1 minute
445
+ if ( ! $popular_posts->get_posts() ) {
446
+ $time_value = 1;
447
+ $time_unit = 'minute';
448
+ }
449
+
450
+ \WordPressPopularPosts\Cache::set(
451
+ $key,
452
+ $popular_posts,
453
+ $time_value,
454
+ $time_unit
455
+ );
456
+ }
457
+
458
+ $cached = true;
459
+
460
+ } // Get popular posts
461
+ else {
462
+ $popular_posts = new Query($shortcode_ops);
463
+ }
464
+
465
+ $this->output->set_data($popular_posts->get_posts());
466
+ $this->output->set_public_options($shortcode_ops);
467
+ $this->output->build_output();
468
+
469
+ $shortcode_content .= $this->output->get_output();
470
+
471
+ return $shortcode_content;
472
+ }
473
+
474
+ }
src/Image.php CHANGED
@@ -1,806 +1,806 @@
1
- <?php
2
- /**
3
- * This class builds/retrieves the thumbnail image of each popular posts.
4
- *
5
- *
6
- * @package WordPressPopularPosts
7
- * @author Hector Cabrera <me@cabrerahector.com>
8
- */
9
-
10
- namespace WordPressPopularPosts;
11
-
12
- class Image {
13
-
14
- /**
15
- * Default thumbnail.
16
- *
17
- * @since 2.2.0
18
- * @var string
19
- */
20
- private $default_thumbnail = '';
21
-
22
- /**
23
- * Plugin uploads directory.
24
- *
25
- * @since 3.0.4
26
- * @var array
27
- */
28
- private $uploads_dir = [];
29
-
30
- /**
31
- * Admin settings.
32
- *
33
- * @since 5.0.0
34
- * @var array
35
- */
36
- private $admin_options = [];
37
-
38
- /**
39
- * Available image sizes.
40
- *
41
- * @since 5.0.0
42
- * @var array
43
- */
44
- private $available_sizes = [];
45
-
46
- /**
47
- * Construct.
48
- *
49
- * @since 4.0.0
50
- * @param array $admin_options
51
- */
52
- public function __construct(array $admin_options)
53
- {
54
- $this->admin_options = $admin_options;
55
-
56
- // Set default thumbnail
57
- $this->default_thumbnail = plugins_url() . "/wordpress-popular-posts/assets/images/no_thumb.jpg";
58
-
59
- if ( $this->is_image_url($this->admin_options['tools']['thumbnail']['default']) )
60
- $this->default_thumbnail = $this->admin_options['tools']['thumbnail']['default'];
61
-
62
- // Set uploads folder
63
- $wp_upload_dir = wp_get_upload_dir();
64
- $this->uploads_dir['basedir'] = $wp_upload_dir['basedir'] . "/" . 'wordpress-popular-posts';
65
- $this->uploads_dir['baseurl'] = $wp_upload_dir['baseurl'] . "/" . 'wordpress-popular-posts';
66
-
67
- if ( ! is_dir($this->uploads_dir['basedir']) ) {
68
- // Couldn't create the folder, store thumbnails in Uploads
69
- if ( ! wp_mkdir_p($this->uploads_dir['basedir']) ) {
70
- $this->uploads_dir['basedir'] = $wp_upload_dir['basedir'];
71
- $this->uploads_dir['baseurl'] = $wp_upload_dir['baseurl'];
72
- }
73
- }
74
- }
75
-
76
- /**
77
- * Get WPP's uploads folder.
78
- *
79
- * @since 4.0.0
80
- * @access public
81
- * @return array|bool
82
- */
83
- public function get_plugin_uploads_dir()
84
- {
85
- if ( is_array($this->uploads_dir) && ! empty($this->uploads_dir) )
86
- return $this->uploads_dir;
87
- return false;
88
- }
89
-
90
- /**
91
- * Returns an image.
92
- *
93
- * @since 5.0.0
94
- * @param \stdClass $post_object Post object
95
- * @param array $size Image size (width & height)
96
- * @param string $source Image source
97
- * @param bool $crop Whether to crop the image or not
98
- * @param string $build Whether to build the image or get an existing one
99
- * @return string
100
- */
101
- public function get($post_object, $size, $source, $crop = true, $build = 'manual')
102
- {
103
- // Bail, $post_object is not an actual object
104
- if ( false === $post_object instanceof \stdClass || ! isset($post_object->id) ) {
105
- return '';
106
- }
107
-
108
- $alt = '';
109
- $classes = ['wpp-thumbnail', 'wpp_' . $source];
110
- $filename = $post_object->id . '-' . $source . '-' . $size[0] . 'x' . $size[1];
111
- $cached = $this->exists($filename);
112
-
113
- // We have a thumbnail already, return it
114
- if ( $cached ) {
115
- $classes[] = 'wpp_cached_thumb';
116
-
117
- /**
118
- * Filters CSS classes assigned to the thumbnail
119
- *
120
- * @since 5.0.0
121
- * @param array CSS classes
122
- * @param int The post ID
123
- * @return array The new CSS classes
124
- */
125
- $classes = apply_filters(
126
- 'wpp_thumbnail_class_attribute',
127
- $classes,
128
- $post_object->id
129
- );
130
-
131
- /**
132
- * Filters CSS classes assigned to the thumbnail
133
- *
134
- * @since 5.0.0
135
- * @param string Original ALT attribute
136
- * @param int The post ID
137
- * @return string The new ALT attribute
138
- */
139
- $alt = apply_filters(
140
- 'wpp_thumbnail_alt_attribute',
141
- $this->get_alt_attribute($post_object->id, $source),
142
- $post_object->id
143
- );
144
-
145
- return $this->render(
146
- $cached,
147
- $size,
148
- is_array($classes) ? implode(' ', $classes) : 'wpp-thumbnail wpp_' . $source,
149
- is_string($alt) ? $alt : ''
150
- );
151
- }
152
-
153
- $thumb_url = null;
154
-
155
- // Return image as-is, no need to create a new thumbnail
156
- if (
157
- ( 'custom_field' == $source && ! $this->admin_options['tools']['thumbnail']['resize'] )
158
- || ( 'featured' == $source && 'predefined' == $build )
159
- ){
160
- /**
161
- * Filters CSS classes assigned to the thumbnail
162
- *
163
- * @since 5.0.0
164
- * @param array CSS classes
165
- * @param int The post ID
166
- * @return array The new CSS classes
167
- */
168
- $classes = apply_filters(
169
- 'wpp_thumbnail_class_attribute',
170
- $classes,
171
- $post_object->id
172
- );
173
-
174
- // Get custom field image URL
175
- if ( 'custom_field' == $source && ! $this->admin_options['tools']['thumbnail']['resize'] ) {
176
- $thumb_url = get_post_meta(
177
- $post_object->id,
178
- $this->admin_options['tools']['thumbnail']['field'],
179
- true
180
- );
181
-
182
- if ( ! $thumb_url || ! $this->is_image_url($thumb_url) ) {
183
- // Is this an attachment ID instead of an image URL?
184
- if ( Helper::is_number($thumb_url) ) {
185
- $thumb_url = wp_get_attachment_image_src($thumb_url, 'full');
186
- $thumb_url = is_array($thumb_url) ? $thumb_url[0] : null;
187
- } else {
188
- $thumb_url = null;
189
- }
190
- }
191
-
192
- if ( $thumb_url ) {
193
- /**
194
- * Filters CSS classes assigned to the thumbnail
195
- *
196
- * @since 5.0.0
197
- * @param string Original ALT attribute
198
- * @param int The post ID
199
- * @return string The new ALT attribute
200
- */
201
- $alt = apply_filters(
202
- 'wpp_thumbnail_alt_attribute',
203
- '',
204
- $post_object->id
205
- );
206
- }
207
- }
208
- // Get Post Thumbnail
209
- else {
210
- if (
211
- current_theme_supports('post-thumbnails')
212
- && has_post_thumbnail($post_object->id)
213
- ) {
214
- // Find corresponding image size
215
- $stock_size = null;
216
- $images_sizes = $this->get_sizes();
217
-
218
- foreach ( $images_sizes as $name => $attr ) :
219
- if (
220
- $attr['width'] == $size[0]
221
- && $attr['height'] == $size[1]
222
- && $attr['crop'] == $crop
223
- ) {
224
- $stock_size = $name;
225
- break;
226
- }
227
- endforeach;
228
-
229
- // Couldn't find a matching size so let's go with width/height combo instead
230
- // (this should never happen but better safe than sorry!)
231
- if ( null == $stock_size ) {
232
- $stock_size = $size;
233
- }
234
-
235
- $featured_image = get_the_post_thumbnail(
236
- $post_object->id,
237
- $stock_size
238
- );
239
-
240
- if ( strpos($featured_image, 'class="') && is_array($classes) && ! empty($classes) )
241
- $featured_image = str_replace('class="', 'class="'. esc_attr(implode(' ', $classes)) . ' ', $featured_image);
242
-
243
- if ( $this->admin_options['tools']['thumbnail']['lazyload'] && false == strpos($featured_image, 'loading="lazy"') ) {
244
- $featured_image = str_replace('src="', 'loading="lazy" src="', $featured_image);
245
- }
246
-
247
- return $featured_image;
248
- }
249
- }
250
- }
251
- // Build a new thumbnail and return it
252
- else {
253
- $file_path = null;
254
-
255
- if ( 'custom_field' == $source && $this->admin_options['tools']['thumbnail']['resize'] ) {
256
- $thumb_url = get_post_meta(
257
- $post_object->id,
258
- $this->admin_options['tools']['thumbnail']['field'],
259
- true
260
- );
261
-
262
- if ( ! $thumb_url || ! $this->is_image_url($thumb_url) ) {
263
- // Is this an attachment ID instead of an image URL?
264
- // If so, try to fetch the image
265
- if ( Helper::is_number($thumb_url) ) {
266
- $thumb_url = wp_get_attachment_image_src($thumb_url, 'full');
267
- $thumb_url = is_array($thumb_url) ? $thumb_url[0] : null;
268
- } else {
269
- $thumb_url = null;
270
- }
271
- }
272
-
273
- if ( $thumb_url && $this->is_image_url($thumb_url) ) {
274
- $file_path = $this->url_to_path($thumb_url, $post_object->id);
275
- }
276
- } else {
277
- $file_meta = $this->get_file_meta($post_object->id, $source);
278
-
279
- if ( is_array($file_meta) && isset($file_meta['path']) ) {
280
- $alt = isset($file_meta['alt']) ? $file_meta['alt'] : '';
281
- $file_path = $file_meta['path'];
282
- }
283
- }
284
-
285
- if ( $file_path ) {
286
- $extension = pathinfo($file_path, PATHINFO_EXTENSION);
287
- $thumb_url = $this->resize(
288
- $file_path,
289
- $filename . '.' . $extension,
290
- $size,
291
- $crop
292
- );
293
- }
294
- }
295
-
296
- if ( ! $thumb_url ) {
297
- $classes[] = 'wpp_def_no_src';
298
- $thumb_url = $this->get_default_url($post_object->id);
299
- }
300
-
301
- return $this->render(
302
- $thumb_url,
303
- $size,
304
- is_array($classes) ? implode(' ', $classes) : 'wpp-thumbnail wpp_' . $source,
305
- is_string($alt) ? $alt : ''
306
- );
307
- }
308
-
309
- /**
310
- * Checks whether a given thumbnail exists.
311
- *
312
- * @since 5.0.0
313
- * @access private
314
- * @param string $filename
315
- * @return string|bool Full URL to image
316
- */
317
- private function exists($filename)
318
- {
319
- // Do we have thumbnail already?
320
- $file = $this->resolve(trailingslashit($this->get_plugin_uploads_dir()['basedir']) . $filename);
321
-
322
- if ( $file && is_file($file) ) {
323
- $extension = pathinfo($file, PATHINFO_EXTENSION);
324
- return trailingslashit($this->get_plugin_uploads_dir()['baseurl']) . $filename . '.' . $extension;
325
- }
326
-
327
- return false;
328
- }
329
-
330
- /**
331
- * Resolves filename.
332
- *
333
- * @since 5.0.0
334
- * @access private
335
- * @author Ioan Chiriac
336
- * @link https://stackoverflow.com/a/29468093/9131961
337
- * @param string $name
338
- * @return string|bool Resolved path, or false if not found
339
- */
340
- private function resolve($name)
341
- {
342
- $info = pathinfo($name);
343
-
344
- // File already contains an extension, return it
345
- if ( isset($info['extension']) && ! empty($info['extension']) ) {
346
- return $name;
347
- }
348
-
349
- $filename = $info['filename'];
350
- $len = strlen($filename);
351
-
352
- // open the folder
353
- $dh = opendir($info['dirname']);
354
-
355
- if ( ! $dh ) {
356
- return false;
357
- }
358
-
359
- // scan each file in the folder
360
- while ( ($file = readdir($dh)) !== false ) {
361
- if ( strncmp($file, $filename, $len) === 0 ) {
362
- if ( strlen($name) > $len ) {
363
- // if name contains a directory part
364
- $name = substr($name, 0, strlen($name) - $len) . $file;
365
- } else {
366
- // if the name is at the path root
367
- $name = $file;
368
- }
369
-
370
- closedir($dh);
371
- return $name;
372
- }
373
- }
374
-
375
- // file not found
376
- closedir($dh);
377
- return false;
378
- }
379
-
380
- /**
381
- * Retrieves local path to image.
382
- *
383
- * @since 5.0.0
384
- * @access private
385
- * @param string $url
386
- * @param integer $post_ID
387
- * @return string|boolean Path to image, or false if not found
388
- */
389
- private function url_to_path($url, $post_ID = null)
390
- {
391
- if ( $this->is_image_url($url) ) {
392
- $attachment_id = $this->get_attachment_id($url);
393
-
394
- // Image is hosted locally
395
- if ( $attachment_id ) {
396
- return get_attached_file($attachment_id);
397
- }
398
-
399
- // Image hosted elsewhere?
400
- if ( $post_ID && Helper::is_number($post_ID) )
401
- return $this->fetch_external_image($post_ID, $url);
402
- }
403
-
404
- return false;
405
- }
406
-
407
- /**
408
- * Gets image meta.
409
- *
410
- * @since 5.0.0
411
- * @access private
412
- * @param int $id Post ID
413
- * @param string $source Image source
414
- * @return array|bool
415
- */
416
- private function get_file_meta($id, $source)
417
- {
418
- // get thumbnail path from the Featured Image
419
- if ( "featured" == $source ) {
420
- if ( $thumbnail_id = get_post_thumbnail_id($id) ) {
421
- // image path
422
- return [
423
- 'path' => get_attached_file($thumbnail_id),
424
- 'alt' => get_post_meta($thumbnail_id, '_wp_attachment_image_alt', true)
425
- ];
426
- }
427
- }
428
- // get thumbnail path from first image attachment
429
- elseif ( "first_attachment" == $source ) {
430
- $args = [
431
- 'numberposts' => 1,
432
- 'order' => 'ASC',
433
- 'post_parent' => $id,
434
- 'post_type' => 'attachment',
435
- 'post_mime_type' => 'image'
436
- ];
437
- $post_attachments = get_children($args);
438
-
439
- if ( ! empty($post_attachments) ) {
440
- $first_img = array_shift($post_attachments);
441
-
442
- return [
443
- 'path' => get_attached_file($first_img->ID),
444
- 'alt' => get_post_meta($first_img->ID, '_wp_attachment_image_alt', true)
445
- ];
446
- }
447
- }
448
- // get thumbnail path from post content
449
- elseif ( "first_image" == $source ) {
450
- /** @var wpdb $wpdb */
451
- global $wpdb;
452
-
453
- if ( $content = $wpdb->get_var("SELECT post_content FROM {$wpdb->posts} WHERE ID = {$id};") ) {
454
- // at least one image has been found
455
- if ( preg_match('/<img[^>]+>/i', $content, $img) ) {
456
- // get img src attribute from the first image found
457
- preg_match('/(src)="([^"]*)"/i', $img[0], $src_attr);
458
-
459
- if ( isset($src_attr[2]) && ! empty($src_attr[2]) ) {
460
- // get img alt attribute from the first image found
461
- $alt = '';
462
- preg_match('/(alt)="([^"]*)"/i', $img[0], $alt_attr);
463
-
464
- if ( isset($alt_attr[2]) && !empty($alt_attr[2]) ) {
465
- $alt = $alt_attr[2];
466
- }
467
-
468
- // image from Media Library
469
- if ( $attachment_id = $this->get_attachment_id($src_attr[2]) ) {
470
- return [
471
- 'path' => get_attached_file($attachment_id),
472
- 'alt' => $alt
473
- ];
474
- } // external image?
475
- else {
476
- return [
477
- 'path' => $this->fetch_external_image($id, $src_attr[2]),
478
- 'alt' => $alt
479
- ];
480
- }
481
- }
482
- }
483
- }
484
- }
485
-
486
- return false;
487
- }
488
-
489
- /**
490
- * Gets image ALT attribute.
491
- *
492
- * @since 5.0.0
493
- * @access private
494
- * @param int $id Post ID
495
- * @param string $source Image source
496
- * @return string
497
- */
498
- private function get_alt_attribute($id, $source)
499
- {
500
- $alt = '';
501
-
502
- // get thumbnail path from the Featured Image
503
- if ( "featured" == $source ) {
504
- if ( $thumbnail_id = get_post_thumbnail_id($id) ) {
505
- // image path
506
- $alt = get_post_meta($thumbnail_id, '_wp_attachment_image_alt', true);
507
- }
508
- }
509
- // get thumbnail path from first image attachment
510
- elseif ( "first_attachment" == $source ) {
511
- $args = [
512
- 'numberposts' => 1,
513
- 'order' => 'ASC',
514
- 'post_parent' => $id,
515
- 'post_type' => 'attachment',
516
- 'post_mime_type' => 'image'
517
- ];
518
- $post_attachments = get_children($args);
519
-
520
- if ( ! empty($post_attachments) ) {
521
- $first_img = array_shift($post_attachments);
522
- $alt = get_post_meta($first_img->ID, '_wp_attachment_image_alt', true);
523
- }
524
- }
525
- // get thumbnail path from post content
526
- elseif ( "first_image" == $source ) {
527
- /** @var wpdb $wpdb */
528
- global $wpdb;
529
-
530
- if ( $content = $wpdb->get_var("SELECT post_content FROM {$wpdb->posts} WHERE ID = {$id};") ) {
531
- // at least one image has been found
532
- if ( preg_match('/<img[^>]+>/i', $content, $img) ) {
533
- // get img alt attribute from the first image found
534
- preg_match('/(alt)="([^"]*)"/i', $img[0], $alt_attr);
535
-
536
- if ( isset($alt_attr[2]) && !empty($alt_attr[2]) ) {
537
- $alt = $alt_attr[2];
538
- }
539
- }
540
- }
541
- }
542
-
543
- return $alt;
544
- }
545
-
546
- /**
547
- * Get the Attachment ID for a given image URL.
548
- *
549
- * @since 3.0.0
550
- * @access private
551
- * @author Frankie Jarrett
552
- * @link http://frankiejarrett.com/get-an-attachment-id-by-url-in-wordpress/
553
- * @param string $url
554
- * @return int|null
555
- */
556
- private function get_attachment_id($url)
557
- {
558
- $url = Helper::add_scheme(
559
- $url,
560
- is_ssl() ? 'https://' : 'http://'
561
- );
562
-
563
- // Split the $url into two parts with the wp-content directory as the separator.
564
- $parse_url = explode(parse_url(WP_CONTENT_URL, PHP_URL_PATH), $url);
565
-
566
- // Get the host of the current site and the host of the $url, ignoring www.
567
- $this_host = str_ireplace('www.', '', parse_url(home_url(), PHP_URL_HOST));
568
- $file_host = str_ireplace('www.', '', parse_url($url, PHP_URL_HOST));
569
-
570
- // Return nothing if there aren't any $url parts or if the current host and $url host do not match.
571
- if (
572
- ! isset($parse_url[1])
573
- || empty($parse_url[1])
574
- || ($this_host != $file_host)
575
- ) {
576
- return false;
577
- }
578
-
579
- // Now we're going to quickly search the DB for any attachment GUID with a partial path match.
580
- // Example: /uploads/2013/05/test-image.jpg
581
- global $wpdb;
582
-
583
- if ( ! $attachment = $wpdb->get_col($wpdb->prepare("SELECT ID FROM {$wpdb->prefix}posts WHERE guid RLIKE %s;", $parse_url[1])) ) {
584
- // Maybe it's a resized image, so try to get the full one
585
- $parse_url[1] = preg_replace('/-[0-9]{1,4}x[0-9]{1,4}\.(jpg|jpeg|png|gif|bmp)$/i', '.$1', $parse_url[1]);
586
- $attachment = $wpdb->get_col($wpdb->prepare("SELECT ID FROM {$wpdb->prefix}posts WHERE guid RLIKE %s;", $parse_url[1]));
587
- }
588
-
589
- // Returns null if no attachment is found.
590
- return isset($attachment[0]) ? $attachment[0] : NULL;
591
- }
592
-
593
- /**
594
- * Fetchs external images.
595
- *
596
- * @since 2.3.3
597
- * @access private
598
- * @param int $id Post ID.
599
- * @param string $url Image url.
600
- * @return string|bool Image path, or false on failure.
601
- */
602
- private function fetch_external_image($id, $url)
603
- {
604
- $full_image_path = trailingslashit($this->get_plugin_uploads_dir()['basedir']) . "{$id}_" . sanitize_file_name(rawurldecode(wp_basename($url)));
605
-
606
- // if the file exists already, return URL and path
607
- if ( file_exists($full_image_path) )
608
- return $full_image_path;
609
-
610
- $url = Helper::add_scheme(
611
- $url,
612
- is_ssl() ? 'https://' : 'http://'
613
- );
614
-
615
- $accepted_status_codes = [200, 301, 302];
616
- $response = wp_remote_head($url, ['timeout' => 5, 'sslverify' => false]);
617
-
618
- if (
619
- ! is_wp_error($response)
620
- && in_array(wp_remote_retrieve_response_code($response), $accepted_status_codes)
621
- ) {
622
- require_once(ABSPATH . 'wp-admin/includes/file.php');
623
-
624
- $url = str_replace('https://', 'http://', $url);
625
- $tmp = download_url($url);
626
-
627
- // File was downloaded successfully
628
- if ( ! is_wp_error($tmp) ) {
629
- // Determine image type
630
- if ( function_exists('exif_imagetype') ) {
631
- $image_type = exif_imagetype($tmp);
632
- } else {
633
- $image_type = getimagesize($tmp);
634
- $image_type = ( isset($image_type[2]) ) ? $image_type[2] : NULL;
635
- }
636
-
637
- // Valid image, save it
638
- if ( in_array($image_type, [IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG]) ) {
639
- // move file to Uploads
640
- if ( @rename($tmp, $full_image_path) ) {
641
- // borrowed from WP - set correct file permissions
642
- $stat = stat(dirname($full_image_path));
643
- $perms = $stat['mode'] & 0000644;
644
- @chmod($full_image_path, $perms);
645
-
646
- return $full_image_path;
647
- }
648
- }
649
-
650
- // Invalid file, remove it
651
- @unlink($tmp);
652
- }
653
- }
654
-
655
- return false;
656
- }
657
-
658
- /**
659
- * Resizes image.
660
- *
661
- * @since 3.0.0
662
- * @access private
663
- * @param string $path Image path
664
- * @param string $filename Image filename
665
- * @param array $size Image size
666
- * @param bool $crop Whether to crop the image or not
667
- * @return string|bool Image URL on success, false on error
668
- */
669
- private function resize($path, $filename, $size, $crop = true)
670
- {
671
- $image = wp_get_image_editor($path);
672
-
673
- // valid image, create thumbnail
674
- if ( ! is_wp_error($image) ) {
675
- /**
676
- * Hook to change the image compression quality of WPP's thumbnails.
677
- * @since 4.2.1
678
- */
679
- $quality = apply_filters('wpp_thumbnail_compression_quality', null);
680
-
681
- if ( ! ctype_digit($quality) )
682
- $quality = null; // Fallback to core's default
683
-
684
- $image->set_quality($quality);
685
-
686
- $image->resize($size[0], $size[1], $crop);
687
- $new_img = $image->save(trailingslashit($this->get_plugin_uploads_dir()['basedir']) . $filename);
688
-
689
- if ( ! is_wp_error($new_img) )
690
- return trailingslashit($this->get_plugin_uploads_dir()['baseurl']) . $filename;
691
- }
692
-
693
- return false;
694
- }
695
-
696
- /**
697
- * Render image tag.
698
- *
699
- * @since 3.0.0
700
- * @access public
701
- * @param string $src Image URL
702
- * @param array $dimension Image's width and height
703
- * @param string $class CSS class
704
- * @param object $alt Alternative text
705
- * @param string $error Error, if the image could not be created
706
- * @return string
707
- */
708
- public function render($src, $size, $class, $alt = '', $error = null)
709
- {
710
- $img_tag = '';
711
-
712
- if ( $error ) {
713
- $img_tag = '<!-- ' . $error . ' --> ';
714
- }
715
-
716
- $src = 'src="' . esc_url(is_ssl() ? str_ireplace("http://", "https://", $src) : $src) . '"';
717
- $lazyload = ( $this->admin_options['tools']['thumbnail']['lazyload'] ) ? ' loading="lazy"' : '';
718
-
719
- $img_tag .= '<img ' . $src . ' width="' . $size[0] . '" height="' . $size[1] . '" alt="' . esc_attr($alt) . '" class="' . esc_attr($class) . '"' . $lazyload . ' />';
720
-
721
- return apply_filters('wpp_render_image', $img_tag);
722
- }
723
-
724
- /**
725
- * Gets list of available thumbnails sizes
726
- *
727
- * @since 3.2.0
728
- * @link http://codex.wordpress.org/Function_Reference/get_intermediate_image_sizes
729
- * @param string $size
730
- * @return array|bool
731
- */
732
- public function get_sizes($size = '')
733
- {
734
- if ( ! is_array($this->available_sizes) || empty($this->available_sizes) ) {
735
- global $_wp_additional_image_sizes;
736
-
737
- $this->available_sizes = [];
738
- $get_intermediate_image_sizes = get_intermediate_image_sizes();
739
-
740
- // Create the full array with sizes and crop info
741
- foreach( $get_intermediate_image_sizes as $_size ) {
742
- if ( in_array($_size, ['thumbnail', 'medium', 'large']) ) {
743
- $this->available_sizes[$_size]['width'] = get_option($_size . '_size_w');
744
- $this->available_sizes[$_size]['height'] = get_option($_size . '_size_h');
745
- $this->available_sizes[$_size]['crop'] = (bool) get_option($_size . '_crop');
746
- } elseif ( isset($_wp_additional_image_sizes[$_size]) ) {
747
- $this->available_sizes[$_size] = [
748
- 'width' => $_wp_additional_image_sizes[$_size]['width'],
749
- 'height' => $_wp_additional_image_sizes[$_size]['height'],
750
- 'crop' => $_wp_additional_image_sizes[$_size]['crop']
751
- ];
752
- }
753
- }
754
- }
755
-
756
- // Get only 1 size if found
757
- if ( $size ) {
758
- if ( isset($this->available_sizes[$size]) ) {
759
- return $this->available_sizes[$size];
760
- }
761
- return false;
762
- }
763
-
764
- return $this->available_sizes;
765
- }
766
-
767
- /**
768
- * Returns the URL of the default thumbnail image.
769
- *
770
- * @since 5.0.0
771
- * @param int|null
772
- * @return string
773
- */
774
- public function get_default_url($post_ID = null)
775
- {
776
- if ( has_filter('wpp_default_thumbnail_url') ) {
777
- $default_thumbnail_url = apply_filters('wpp_default_thumbnail_url', $this->default_thumbnail, $post_ID);
778
-
779
- if ( $default_thumbnail_url != $this->default_thumbnail && $this->is_image_url($default_thumbnail_url) )
780
- return $default_thumbnail_url;
781
- }
782
-
783
- return $this->default_thumbnail;
784
- }
785
-
786
- /**
787
- * Checks whether an URL points to an actual image.
788
- *
789
- * @since 5.0.0
790
- * @access private
791
- * @param string
792
- * @return array|bool
793
- */
794
- private function is_image_url($url)
795
- {
796
- if ( ! filter_var($url, FILTER_VALIDATE_URL) )
797
- return false;
798
-
799
- // sanitize URL, just in case
800
- $image_url = esc_url($url);
801
- // remove querystring
802
- preg_match('/[^\?]+\.(jpg|JPG|jpe|JPE|jpeg|JPEG|gif|GIF|png|PNG)/', $image_url, $matches);
803
-
804
- return ( is_array($matches) && ! empty($matches) ) ? $matches : false;
805
- }
806
- }
1
+ <?php
2
+ /**
3
+ * This class builds/retrieves the thumbnail image of each popular posts.
4
+ *
5
+ *
6
+ * @package WordPressPopularPosts
7
+ * @author Hector Cabrera <me@cabrerahector.com>
8
+ */
9
+
10
+ namespace WordPressPopularPosts;
11
+
12
+ class Image {
13
+
14
+ /**
15
+ * Default thumbnail.
16
+ *
17
+ * @since 2.2.0
18
+ * @var string
19
+ */
20
+ private $default_thumbnail = '';
21
+
22
+ /**
23
+ * Plugin uploads directory.
24
+ *
25
+ * @since 3.0.4
26
+ * @var array
27
+ */
28
+ private $uploads_dir = [];
29
+
30
+ /**
31
+ * Admin settings.
32
+ *
33
+ * @since 5.0.0
34
+ * @var array
35
+ */
36
+ private $admin_options = [];
37
+
38
+ /**
39
+ * Available image sizes.
40
+ *
41
+ * @since 5.0.0
42
+ * @var array
43
+ */
44
+ private $available_sizes = [];
45
+
46
+ /**
47
+ * Construct.
48
+ *
49
+ * @since 4.0.0
50
+ * @param array $admin_options
51
+ */
52
+ public function __construct(array $admin_options)
53
+ {
54
+ $this->admin_options = $admin_options;
55
+
56
+ // Set default thumbnail
57
+ $this->default_thumbnail = plugins_url() . "/wordpress-popular-posts/assets/images/no_thumb.jpg";
58
+
59
+ if ( $this->is_image_url($this->admin_options['tools']['thumbnail']['default']) )
60
+ $this->default_thumbnail = $this->admin_options['tools']['thumbnail']['default'];
61
+
62
+ // Set uploads folder
63
+ $wp_upload_dir = wp_get_upload_dir();
64
+ $this->uploads_dir['basedir'] = $wp_upload_dir['basedir'] . "/" . 'wordpress-popular-posts';
65
+ $this->uploads_dir['baseurl'] = $wp_upload_dir['baseurl'] . "/" . 'wordpress-popular-posts';
66
+
67
+ if ( ! is_dir($this->uploads_dir['basedir']) ) {
68
+ // Couldn't create the folder, store thumbnails in Uploads
69
+ if ( ! wp_mkdir_p($this->uploads_dir['basedir']) ) {
70
+ $this->uploads_dir['basedir'] = $wp_upload_dir['basedir'];
71
+ $this->uploads_dir['baseurl'] = $wp_upload_dir['baseurl'];
72
+ }
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Get WPP's uploads folder.
78
+ *
79
+ * @since 4.0.0
80
+ * @access public
81
+ * @return array|bool
82
+ */
83
+ public function get_plugin_uploads_dir()
84
+ {
85
+ if ( is_array($this->uploads_dir) && ! empty($this->uploads_dir) )
86
+ return $this->uploads_dir;
87
+ return false;
88
+ }
89
+
90
+ /**
91
+ * Returns an image.
92
+ *
93
+ * @since 5.0.0
94
+ * @param \stdClass $post_object Post object
95
+ * @param array $size Image size (width & height)
96
+ * @param string $source Image source
97
+ * @param bool $crop Whether to crop the image or not
98
+ * @param string $build Whether to build the image or get an existing one
99
+ * @return string
100
+ */
101
+ public function get($post_object, $size, $source, $crop = true, $build = 'manual')
102
+ {
103
+ // Bail, $post_object is not an actual object
104
+ if ( false === $post_object instanceof \stdClass || ! isset($post_object->id) ) {
105
+ return '';
106
+ }
107
+
108
+ $alt = '';
109
+ $classes = ['wpp-thumbnail', 'wpp_' . $source];
110
+ $filename = $post_object->id . '-' . $source . '-' . $size[0] . 'x' . $size[1];
111
+ $cached = $this->exists($filename);
112
+
113
+ // We have a thumbnail already, return it
114
+ if ( $cached ) {
115
+ $classes[] = 'wpp_cached_thumb';
116
+
117
+ /**
118
+ * Filters CSS classes assigned to the thumbnail
119
+ *
120
+ * @since 5.0.0
121
+ * @param array CSS classes
122
+ * @param int The post ID
123
+ * @return array The new CSS classes
124
+ */
125
+ $classes = apply_filters(
126
+ 'wpp_thumbnail_class_attribute',
127
+ $classes,
128
+ $post_object->id
129
+ );
130
+
131
+ /**
132
+ * Filters CSS classes assigned to the thumbnail
133
+ *
134
+ * @since 5.0.0
135
+ * @param string Original ALT attribute
136
+ * @param int The post ID
137
+ * @return string The new ALT attribute
138
+ */
139
+ $alt = apply_filters(
140
+ 'wpp_thumbnail_alt_attribute',
141
+ $this->get_alt_attribute($post_object->id, $source),
142
+ $post_object->id
143
+ );
144
+
145
+ return $this->render(
146
+ $cached,
147
+ $size,
148
+ is_array($classes) ? implode(' ', $classes) : 'wpp-thumbnail wpp_' . $source,
149
+ is_string($alt) ? $alt : ''
150
+ );
151
+ }
152
+
153
+ $thumb_url = null;
154
+
155
+ // Return image as-is, no need to create a new thumbnail
156
+ if (
157
+ ( 'custom_field' == $source && ! $this->admin_options['tools']['thumbnail']['resize'] )
158
+ || ( 'featured' == $source && 'predefined' == $build )
159
+ ){
160
+ /**
161
+ * Filters CSS classes assigned to the thumbnail
162
+ *
163
+ * @since 5.0.0
164
+ * @param array CSS classes
165
+ * @param int The post ID
166
+ * @return array The new CSS classes
167
+ */
168
+ $classes = apply_filters(
169
+ 'wpp_thumbnail_class_attribute',
170
+ $classes,
171
+ $post_object->id
172
+ );
173
+
174
+ // Get custom field image URL
175
+ if ( 'custom_field' == $source && ! $this->admin_options['tools']['thumbnail']['resize'] ) {
176
+ $thumb_url = get_post_meta(
177
+ $post_object->id,
178
+ $this->admin_options['tools']['thumbnail']['field'],
179
+ true
180
+ );
181
+
182
+ if ( ! $thumb_url || ! $this->is_image_url($thumb_url) ) {
183
+ // Is this an attachment ID instead of an image URL?
184
+ if ( Helper::is_number($thumb_url) ) {
185
+ $thumb_url = wp_get_attachment_image_src($thumb_url, 'full');
186
+ $thumb_url = is_array($thumb_url) ? $thumb_url[0] : null;
187
+ } else {
188
+ $thumb_url = null;
189
+ }
190
+ }
191
+
192
+ if ( $thumb_url ) {
193
+ /**
194
+ * Filters CSS classes assigned to the thumbnail
195
+ *
196
+ * @since 5.0.0
197
+ * @param string Original ALT attribute
198
+ * @param int The post ID
199
+ * @return string The new ALT attribute
200
+ */
201
+ $alt = apply_filters(
202
+ 'wpp_thumbnail_alt_attribute',
203
+ '',
204
+ $post_object->id
205
+ );
206
+ }
207
+ }
208
+ // Get Post Thumbnail
209
+ else {
210
+ if (
211
+ current_theme_supports('post-thumbnails')
212
+ && has_post_thumbnail($post_object->id)
213
+ ) {
214
+ // Find corresponding image size
215
+ $stock_size = null;
216
+ $images_sizes = $this->get_sizes();
217
+
218
+ foreach ( $images_sizes as $name => $attr ) :
219
+ if (
220
+ $attr['width'] == $size[0]
221
+ && $attr['height'] == $size[1]
222
+ && $attr['crop'] == $crop
223
+ ) {
224
+ $stock_size = $name;
225
+ break;
226
+ }
227
+ endforeach;
228
+
229
+ // Couldn't find a matching size so let's go with width/height combo instead
230
+ // (this should never happen but better safe than sorry!)
231
+ if ( null == $stock_size ) {
232
+ $stock_size = $size;
233
+ }
234
+
235
+ $featured_image = get_the_post_thumbnail(
236
+ $post_object->id,
237
+ $stock_size
238
+ );
239
+
240
+ if ( strpos($featured_image, 'class="') && is_array($classes) && ! empty($classes) )
241
+ $featured_image = str_replace('class="', 'class="'. esc_attr(implode(' ', $classes)) . ' ', $featured_image);
242
+
243
+ if ( $this->admin_options['tools']['thumbnail']['lazyload'] && false == strpos($featured_image, 'loading="lazy"') ) {
244
+ $featured_image = str_replace('src="', 'loading="lazy" src="', $featured_image);
245
+ }
246
+
247
+ return $featured_image;
248
+ }
249
+ }
250
+ }
251
+ // Build a new thumbnail and return it
252
+ else {
253
+ $file_path = null;
254
+
255
+ if ( 'custom_field' == $source && $this->admin_options['tools']['thumbnail']['resize'] ) {
256
+ $thumb_url = get_post_meta(
257
+ $post_object->id,
258
+ $this->admin_options['tools']['thumbnail']['field'],
259
+ true
260
+ );
261
+
262
+ if ( ! $thumb_url || ! $this->is_image_url($thumb_url) ) {
263
+ // Is this an attachment ID instead of an image URL?
264
+ // If so, try to fetch the image
265
+ if ( Helper::is_number($thumb_url) ) {
266
+ $thumb_url = wp_get_attachment_image_src($thumb_url, 'full');
267
+ $thumb_url = is_array($thumb_url) ? $thumb_url[0] : null;
268
+ } else {
269
+ $thumb_url = null;
270
+ }
271
+ }
272
+
273
+ if ( $thumb_url && $this->is_image_url($thumb_url) ) {
274
+ $file_path = $this->url_to_path($thumb_url, $post_object->id);
275
+ }
276
+ } else {
277
+ $file_meta = $this->get_file_meta($post_object->id, $source);
278
+
279
+ if ( is_array($file_meta) && isset($file_meta['path']) ) {
280
+ $alt = isset($file_meta['alt']) ? $file_meta['alt'] : '';
281
+ $file_path = $file_meta['path'];
282
+ }
283
+ }
284
+
285
+ if ( $file_path ) {
286
+ $extension = pathinfo($file_path, PATHINFO_EXTENSION);
287
+ $thumb_url = $this->resize(
288
+ $file_path,
289
+ $filename . '.' . $extension,
290
+ $size,
291
+ $crop
292
+ );
293
+ }
294
+ }
295
+
296
+ if ( ! $thumb_url ) {
297
+ $classes[] = 'wpp_def_no_src';
298
+ $thumb_url = $this->get_default_url($post_object->id);
299
+ }
300
+
301
+ return $this->render(
302
+ $thumb_url,
303
+ $size,
304
+ is_array($classes) ? implode(' ', $classes) : 'wpp-thumbnail wpp_' . $source,
305
+ is_string($alt) ? $alt : ''
306
+ );
307
+ }
308
+
309
+ /**
310
+ * Checks whether a given thumbnail exists.
311
+ *
312
+ * @since 5.0.0
313
+ * @access private
314
+ * @param string $filename
315
+ * @return string|bool Full URL to image
316
+ */
317
+ private function exists($filename)
318
+ {
319
+ // Do we have thumbnail already?
320
+ $file = $this->resolve(trailingslashit($this->get_plugin_uploads_dir()['basedir']) . $filename);
321
+
322
+ if ( $file && is_file($file) ) {
323
+ $extension = pathinfo($file, PATHINFO_EXTENSION);
324
+ return trailingslashit($this->get_plugin_uploads_dir()['baseurl']) . $filename . '.' . $extension;
325
+ }
326
+
327
+ return false;
328
+ }
329
+
330
+ /**
331
+ * Resolves filename.
332
+ *
333
+ * @since 5.0.0
334
+ * @access private
335
+ * @author Ioan Chiriac
336
+ * @link https://stackoverflow.com/a/29468093/9131961
337
+ * @param string $name
338
+ * @return string|bool Resolved path, or false if not found
339
+ */
340
+ private function resolve($name)
341
+ {
342
+ $info = pathinfo($name);
343
+
344
+ // File already contains an extension, return it
345
+ if ( isset($info['extension']) && ! empty($info['extension']) ) {
346
+ return $name;
347
+ }
348
+
349
+ $filename = $info['filename'];
350
+ $len = strlen($filename);
351
+
352
+ // open the folder
353
+ $dh = opendir($info['dirname']);
354
+
355
+ if ( ! $dh ) {
356
+ return false;
357
+ }
358
+
359
+ // scan each file in the folder
360
+ while ( ($file = readdir($dh)) !== false ) {
361
+ if ( strncmp($file, $filename, $len) === 0 ) {
362
+ if ( strlen($name) > $len ) {
363
+ // if name contains a directory part
364
+ $name = substr($name, 0, strlen($name) - $len) . $file;
365
+ } else {
366
+ // if the name is at the path root
367
+ $name = $file;
368
+ }
369
+
370
+ closedir($dh);
371
+ return $name;
372
+ }
373
+ }
374
+
375
+ // file not found
376
+ closedir($dh);
377
+ return false;
378
+ }
379
+
380
+ /**
381
+ * Retrieves local path to image.
382
+ *
383
+ * @since 5.0.0
384
+ * @access private
385
+ * @param string $url
386
+ * @param integer $post_ID
387
+ * @return string|boolean Path to image, or false if not found
388
+ */
389
+ private function url_to_path($url, $post_ID = null)
390
+ {
391
+ if ( $this->is_image_url($url) ) {
392
+ $attachment_id = $this->get_attachment_id($url);
393
+
394
+ // Image is hosted locally
395
+ if ( $attachment_id ) {
396
+ return get_attached_file($attachment_id);
397
+ }
398
+
399
+ // Image hosted elsewhere?
400
+ if ( $post_ID && Helper::is_number($post_ID) )
401
+ return $this->fetch_external_image($post_ID, $url);
402
+ }
403
+
404
+ return false;
405
+ }
406
+
407
+ /**
408
+ * Gets image meta.
409
+ *
410
+ * @since 5.0.0
411
+ * @access private
412
+ * @param int $id Post ID
413
+ * @param string $source Image source
414
+ * @return array|bool
415
+ */
416
+ private function get_file_meta($id, $source)
417
+ {
418
+ // get thumbnail path from the Featured Image
419
+ if ( "featured" == $source ) {
420
+ if ( $thumbnail_id = get_post_thumbnail_id($id) ) {
421
+ // image path
422
+ return [
423
+ 'path' => get_attached_file($thumbnail_id),
424
+ 'alt' => get_post_meta($thumbnail_id, '_wp_attachment_image_alt', true)
425
+ ];
426
+ }
427
+ }
428
+ // get thumbnail path from first image attachment
429
+ elseif ( "first_attachment" == $source ) {
430
+ $args = [
431
+ 'numberposts' => 1,
432
+ 'order' => 'ASC',
433
+ 'post_parent' => $id,
434
+ 'post_type' => 'attachment',
435
+ 'post_mime_type' => 'image'
436
+ ];
437
+ $post_attachments = get_children($args);
438
+
439
+ if ( ! empty($post_attachments) ) {
440
+ $first_img = array_shift($post_attachments);
441
+
442
+ return [
443
+ 'path' => get_attached_file($first_img->ID),
444
+ 'alt' => get_post_meta($first_img->ID, '_wp_attachment_image_alt', true)
445
+ ];
446
+ }
447
+ }
448
+ // get thumbnail path from post content
449
+ elseif ( "first_image" == $source ) {
450
+ /** @var wpdb $wpdb */
451
+ global $wpdb;
452
+
453
+ if ( $content = $wpdb->get_var("SELECT post_content FROM {$wpdb->posts} WHERE ID = {$id};") ) {
454
+ // at least one image has been found
455
+ if ( preg_match('/<img[^>]+>/i', $content, $img) ) {
456
+ // get img src attribute from the first image found
457
+ preg_match('/(src)="([^"]*)"/i', $img[0], $src_attr);
458
+
459
+ if ( isset($src_attr[2]) && ! empty($src_attr[2]) ) {
460
+ // get img alt attribute from the first image found
461
+ $alt = '';
462
+ preg_match('/(alt)="([^"]*)"/i', $img[0], $alt_attr);
463
+
464
+ if ( isset($alt_attr[2]) && !empty($alt_attr[2]) ) {
465
+ $alt = $alt_attr[2];
466
+ }
467
+
468
+ // image from Media Library
469
+ if ( $attachment_id = $this->get_attachment_id($src_attr[2]) ) {
470
+ return [
471
+ 'path' => get_attached_file($attachment_id),
472
+ 'alt' => $alt
473
+ ];
474
+ } // external image?
475
+ else {
476
+ return [
477
+ 'path' => $this->fetch_external_image($id, $src_attr[2]),
478
+ 'alt' => $alt
479
+ ];
480
+ }
481
+ }
482
+ }
483
+ }
484
+ }
485
+
486
+ return false;
487
+ }
488
+
489
+ /**
490
+ * Gets image ALT attribute.
491
+ *
492
+ * @since 5.0.0
493
+ * @access private
494
+ * @param int $id Post ID
495
+ * @param string $source Image source
496
+ * @return string
497
+ */
498
+ private function get_alt_attribute($id, $source)
499
+ {
500
+ $alt = '';
501
+
502
+ // get thumbnail path from the Featured Image
503
+ if ( "featured" == $source ) {
504
+ if ( $thumbnail_id = get_post_thumbnail_id($id) ) {
505
+ // image path
506
+ $alt = get_post_meta($thumbnail_id, '_wp_attachment_image_alt', true);
507
+ }
508
+ }
509
+ // get thumbnail path from first image attachment
510
+ elseif ( "first_attachment" == $source ) {
511
+ $args = [
512
+ 'numberposts' => 1,
513
+ 'order' => 'ASC',
514
+ 'post_parent' => $id,
515
+ 'post_type' => 'attachment',
516
+ 'post_mime_type' => 'image'
517
+ ];
518
+ $post_attachments = get_children($args);
519
+
520
+ if ( ! empty($post_attachments) ) {
521
+ $first_img = array_shift($post_attachments);
522
+ $alt = get_post_meta($first_img->ID, '_wp_attachment_image_alt', true);
523
+ }
524
+ }
525
+ // get thumbnail path from post content
526
+ elseif ( "first_image" == $source ) {
527
+ /** @var wpdb $wpdb */
528
+ global $wpdb;
529
+
530
+ if ( $content = $wpdb->get_var("SELECT post_content FROM {$wpdb->posts} WHERE ID = {$id};") ) {
531
+ // at least one image has been found
532
+ if ( preg_match('/<img[^>]+>/i', $content, $img) ) {
533
+ // get img alt attribute from the first image found
534
+ preg_match('/(alt)="([^"]*)"/i', $img[0], $alt_attr);
535
+
536
+ if ( isset($alt_attr[2]) && !empty($alt_attr[2]) ) {
537
+ $alt = $alt_attr[2];
538
+ }
539
+ }
540
+ }
541
+ }
542
+
543
+ return $alt;
544
+ }
545
+
546
+ /**
547
+ * Get the Attachment ID for a given image URL.
548
+ *
549
+ * @since 3.0.0
550
+ * @access private
551
+ * @author Frankie Jarrett
552
+ * @link http://frankiejarrett.com/get-an-attachment-id-by-url-in-wordpress/
553
+ * @param string $url
554
+ * @return int|null
555
+ */
556
+ private function get_attachment_id($url)
557
+ {
558
+ $url = Helper::add_scheme(
559
+ $url,
560
+ is_ssl() ? 'https://' : 'http://'
561
+ );
562
+
563
+ // Split the $url into two parts with the wp-content directory as the separator.
564
+ $parse_url = explode(parse_url(WP_CONTENT_URL, PHP_URL_PATH), $url);
565
+
566
+ // Get the host of the current site and the host of the $url, ignoring www.
567
+ $this_host = str_ireplace('www.', '', parse_url(home_url(), PHP_URL_HOST));
568
+ $file_host = str_ireplace('www.', '', parse_url($url, PHP_URL_HOST));
569
+
570
+ // Return nothing if there aren't any $url parts or if the current host and $url host do not match.
571
+ if (
572
+ ! isset($parse_url[1])
573
+ || empty($parse_url[1])
574
+ || ($this_host != $file_host)
575
+ ) {
576
+ return false;
577
+ }
578
+
579
+ // Now we're going to quickly search the DB for any attachment GUID with a partial path match.
580
+ // Example: /uploads/2013/05/test-image.jpg
581
+ global $wpdb;
582
+
583
+ if ( ! $attachment = $wpdb->get_col($wpdb->prepare("SELECT ID FROM {$wpdb->prefix}posts WHERE guid RLIKE %s;", $parse_url[1])) ) {
584
+ // Maybe it's a resized image, so try to get the full one
585
+ $parse_url[1] = preg_replace('/-[0-9]{1,4}x[0-9]{1,4}\.(jpg|jpeg|png|gif|bmp)$/i', '.$1', $parse_url[1]);
586
+ $attachment = $wpdb->get_col($wpdb->prepare("SELECT ID FROM {$wpdb->prefix}posts WHERE guid RLIKE %s;", $parse_url[1]));
587
+ }
588
+
589
+ // Returns null if no attachment is found.
590
+ return isset($attachment[0]) ? $attachment[0] : NULL;
591
+ }
592
+
593
+ /**
594
+ * Fetchs external images.
595
+ *
596
+ * @since 2.3.3
597
+ * @access private
598
+ * @param int $id Post ID.
599
+ * @param string $url Image url.
600
+ * @return string|bool Image path, or false on failure.
601
+ */
602
+ private function fetch_external_image($id, $url)
603
+ {
604
+ $full_image_path = trailingslashit($this->get_plugin_uploads_dir()['basedir']) . "{$id}_" . sanitize_file_name(rawurldecode(wp_basename($url)));
605
+
606
+ // if the file exists already, return URL and path
607
+ if ( file_exists($full_image_path) )
608
+ return $full_image_path;
609
+
610
+ $url = Helper::add_scheme(
611
+ $url,
612
+ is_ssl() ? 'https://' : 'http://'
613
+ );
614
+
615
+ $accepted_status_codes = [200, 301, 302];
616
+ $response = wp_remote_head($url, ['timeout' => 5, 'sslverify' => false]);
617
+
618
+ if (
619
+ ! is_wp_error($response)
620
+ && in_array(wp_remote_retrieve_response_code($response), $accepted_status_codes)
621
+ ) {
622
+ require_once(ABSPATH . 'wp-admin/includes/file.php');
623
+
624
+ $url = str_replace('https://', 'http://', $url);
625
+ $tmp = download_url($url);
626
+
627
+ // File was downloaded successfully
628
+ if ( ! is_wp_error($tmp) ) {
629
+ // Determine image type
630
+ if ( function_exists('exif_imagetype') ) {
631
+ $image_type = exif_imagetype($tmp);
632
+ } else {
633
+ $image_type = getimagesize($tmp);
634
+ $image_type = ( isset($image_type[2]) ) ? $image_type[2] : NULL;
635
+ }
636
+
637
+ // Valid image, save it
638
+ if ( in_array($image_type, [IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG]) ) {
639
+ // move file to Uploads
640
+ if ( @rename($tmp, $full_image_path) ) {
641
+ // borrowed from WP - set correct file permissions
642
+ $stat = stat(dirname($full_image_path));
643
+ $perms = $stat['mode'] & 0000644;
644
+ @chmod($full_image_path, $perms);
645
+
646
+ return $full_image_path;
647
+ }
648
+ }
649
+
650
+ // Invalid file, remove it
651
+ @unlink($tmp);
652
+ }
653
+ }
654
+
655
+ return false;
656
+ }
657
+
658
+ /**
659
+ * Resizes image.
660
+ *
661
+ * @since 3.0.0
662
+ * @access private
663
+ * @param string $path Image path
664
+ * @param string $filename Image filename
665
+ * @param array $size Image size
666
+ * @param bool $crop Whether to crop the image or not
667
+ * @return string|bool Image URL on success, false on error
668
+ */
669
+ private function resize($path, $filename, $size, $crop = true)
670
+ {
671
+ $image = wp_get_image_editor($path);
672
+
673
+ // valid image, create thumbnail
674
+ if ( ! is_wp_error($image) ) {
675
+ /**
676
+ * Hook to change the image compression quality of WPP's thumbnails.
677
+ * @since 4.2.1
678
+ */
679
+ $quality = apply_filters('wpp_thumbnail_compression_quality', null);
680
+
681
+ if ( ! ctype_digit($quality) )
682
+ $quality = null; // Fallback to core's default
683
+
684
+ $image->set_quality($quality);
685
+
686
+ $image->resize($size[0], $size[1], $crop);
687
+ $new_img = $image->save(trailingslashit($this->get_plugin_uploads_dir()['basedir']) . $filename);
688
+
689
+ if ( ! is_wp_error($new_img) )
690
+ return trailingslashit($this->get_plugin_uploads_dir()['baseurl']) . $filename;
691
+ }
692
+
693
+ return false;
694
+ }
695
+
696
+ /**
697
+ * Render image tag.
698
+ *
699
+ * @since 3.0.0
700
+ * @access public
701
+ * @param string $src Image URL
702
+ * @param array $dimension Image's width and height
703
+ * @param string $class CSS class
704
+ * @param object $alt Alternative text
705
+ * @param string $error Error, if the image could not be created
706
+ * @return string
707
+ */
708
+ public function render($src, $size, $class, $alt = '', $error = null)
709
+ {
710
+ $img_tag = '';
711
+
712
+ if ( $error ) {
713
+ $img_tag = '<!-- ' . $error . ' --> ';
714
+ }
715
+
716
+ $src = 'src="' . esc_url(is_ssl() ? str_ireplace("http://", "https://", $src) : $src) . '"';
717
+ $lazyload = ( $this->admin_options['tools']['thumbnail']['lazyload'] ) ? ' loading="lazy"' : '';
718
+
719
+ $img_tag .= '<img ' . $src . ' width="' . $size[0] . '" height="' . $size[1] . '" alt="' . esc_attr($alt) . '" class="' . esc_attr($class) . '"' . $lazyload . ' />';
720
+
721
+ return apply_filters('wpp_render_image', $img_tag);
722
+ }
723
+
724
+ /**
725
+ * Gets list of available thumbnails sizes
726
+ *
727
+ * @since 3.2.0
728
+ * @link http://codex.wordpress.org/Function_Reference/get_intermediate_image_sizes
729
+ * @param string $size
730
+ * @return array|bool
731
+ */
732
+ public function get_sizes($size = '')
733
+ {
734
+ if ( ! is_array($this->available_sizes) || empty($this->available_sizes) ) {
735
+ global $_wp_additional_image_sizes;
736
+
737
+ $this->available_sizes = [];
738
+ $get_intermediate_image_sizes = get_intermediate_image_sizes();
739
+
740
+ // Create the full array with sizes and crop info
741
+ foreach( $get_intermediate_image_sizes as $_size ) {
742
+ if ( in_array($_size, ['thumbnail', 'medium', 'large']) ) {
743
+ $this->available_sizes[$_size]['width'] = get_option($_size . '_size_w');
744
+ $this->available_sizes[$_size]['height'] = get_option($_size . '_size_h');
745
+ $this->available_sizes[$_size]['crop'] = (bool) get_option($_size . '_crop');
746
+ } elseif ( isset($_wp_additional_image_sizes[$_size]) ) {
747
+ $this->available_sizes[$_size] = [
748
+ 'width' => $_wp_additional_image_sizes[$_size]['width'],
749
+ 'height' => $_wp_additional_image_sizes[$_size]['height'],
750
+ 'crop' => $_wp_additional_image_sizes[$_size]['crop']
751
+ ];
752
+ }
753
+ }
754
+ }
755
+
756
+ // Get only 1 size if found
757
+ if ( $size ) {
758
+ if ( isset($this->available_sizes[$size]) ) {
759
+ return $this->available_sizes[$size];
760
+ }
761
+ return false;
762
+ }
763
+
764
+ return $this->available_sizes;
765
+ }
766
+
767
+ /**
768
+ * Returns the URL of the default thumbnail image.
769
+ *
770
+ * @since 5.0.0
771
+ * @param int|null
772
+ * @return string
773
+ */
774
+ public function get_default_url($post_ID = null)
775
+ {
776
+ if ( has_filter('wpp_default_thumbnail_url') ) {
777
+ $default_thumbnail_url = apply_filters('wpp_default_thumbnail_url', $this->default_thumbnail, $post_ID);
778
+
779
+ if ( $default_thumbnail_url != $this->default_thumbnail && $this->is_image_url($default_thumbnail_url) )
780
+ return $default_thumbnail_url;
781
+ }
782
+
783
+ return $this->default_thumbnail;
784
+ }
785
+
786
+ /**
787
+ * Checks whether an URL points to an actual image.
788
+ *
789
+ * @since 5.0.0
790
+ * @access private
791
+ * @param string
792
+ * @return array|bool
793
+ */
794
+ private function is_image_url($url)
795
+ {
796
+ if ( ! filter_var($url, FILTER_VALIDATE_URL) )
797
+ return false;
798
+
799
+ // sanitize URL, just in case
800
+ $image_url = esc_url($url);
801
+ // remove querystring
802
+ preg_match('/[^\?]+\.(jpg|JPG|jpe|JPE|jpeg|JPEG|gif|GIF|png|PNG)/', $image_url, $matches);
803
+
804
+ return ( is_array($matches) && ! empty($matches) ) ? $matches : false;
805
+ }
806
+ }
wordpress-popular-posts.php CHANGED
@@ -1,60 +1,60 @@
1
- <?php
2
-
3
- /**
4
- * The plugin bootstrap file
5
- *
6
- * This file is read by WordPress to generate the plugin information in the plugin
7
- * admin area. This file also includes all of the dependencies used by the plugin,
8
- * registers the activation and deactivation functions, and defines a function
9
- * that starts the plugin.
10
- *
11
- * @link https://cabrerahector.com/
12
- * @since 4.0.0
13
- * @package WordPressPopularPosts
14
- *
15
- * @wordpress-plugin
16
- * Plugin Name: WordPress Popular Posts
17
- * Plugin URI: https://wordpress.org/plugins/wordpress-popular-posts/
18
- * Description: A highly customizable widget that displays the most popular posts on your blog.
19
- * Version: 5.2.2
20
- * Author: Hector Cabrera
21
- * Author URI: https://cabrerahector.com/
22
- * License: GPL-2.0+
23
- * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
24
- * Text Domain: wordpress-popular-posts
25
- * Domain Path: /languages
26
- */
27
-
28
- if ( ! defined( 'WPINC' ) ) {
29
- die();
30
- }
31
-
32
- define('WPP_VERSION', '5.2.2');
33
- define('WPP_MIN_PHP_VERSION', '5.4');
34
- define('WPP_MIN_WP_VERSION', '4.9');
35
-
36
- /** Requirements check */
37
- global $wp_version;
38
-
39
- // We're good, continue!
40
- if ( version_compare(PHP_VERSION, WPP_MIN_PHP_VERSION, '>=') && version_compare($wp_version, WPP_MIN_WP_VERSION, '>=') ) {
41
- $wpp_main_plugin_file = __FILE__;
42
- // Load plugin bootstrap
43
- require __DIR__ . '/src/Bootstrap.php';
44
- } // Nope.
45
- else {
46
- if ( isset($_GET['activate']) )
47
- unset($_GET['activate']);
48
-
49
- function wpp_render_min_requirements_notice() {
50
- global $wp_version;
51
- echo '<div class="notice notice-error"><p>' . sprintf(
52
- __('WordPress Popular Posts requires at least PHP %1$s and WordPress %2$s to function correctly. Your site uses PHP %3$s and WordPress %4$s.', 'wordpress-popular-posts'),
53
- WPP_MIN_PHP_VERSION,
54
- WPP_MIN_WP_VERSION,
55
- PHP_VERSION,
56
- $wp_version
57
- ) . '</p></div>';
58
- }
59
- add_action('admin_notices', 'wpp_render_min_requirements_notice');
60
- }
1
+ <?php
2
+
3
+ /**
4
+ * The plugin bootstrap file
5
+ *
6
+ * This file is read by WordPress to generate the plugin information in the plugin
7
+ * admin area. This file also includes all of the dependencies used by the plugin,
8
+ * registers the activation and deactivation functions, and defines a function
9
+ * that starts the plugin.
10
+ *
11
+ * @link https://cabrerahector.com/
12
+ * @since 4.0.0
13
+ * @package WordPressPopularPosts
14
+ *
15
+ * @wordpress-plugin
16
+ * Plugin Name: WordPress Popular Posts
17
+ * Plugin URI: https://wordpress.org/plugins/wordpress-popular-posts/
18
+ * Description: A highly customizable widget that displays the most popular posts on your blog.
19
+ * Version: 5.2.3
20
+ * Author: Hector Cabrera
21
+ * Author URI: https://cabrerahector.com/
22
+ * License: GPL-2.0+
23
+ * License URI: http://www.gnu.org/licenses/gpl-2.0.txt
24
+ * Text Domain: wordpress-popular-posts
25
+ * Domain Path: /languages
26
+ */
27
+
28
+ if ( ! defined( 'WPINC' ) ) {
29
+ die();
30
+ }
31
+
32
+ define('WPP_VERSION', '5.2.3');
33
+ define('WPP_MIN_PHP_VERSION', '5.4');
34
+ define('WPP_MIN_WP_VERSION', '4.9');
35
+
36
+ /** Requirements check */
37
+ global $wp_version;
38
+
39
+ // We're good, continue!
40
+ if ( version_compare(PHP_VERSION, WPP_MIN_PHP_VERSION, '>=') && version_compare($wp_version, WPP_MIN_WP_VERSION, '>=') ) {
41
+ $wpp_main_plugin_file = __FILE__;
42
+ // Load plugin bootstrap
43
+ require __DIR__ . '/src/Bootstrap.php';
44
+ } // Nope.
45
+ else {
46
+ if ( isset($_GET['activate']) )
47
+ unset($_GET['activate']);
48
+
49
+ function wpp_render_min_requirements_notice() {
50
+ global $wp_version;
51
+ echo '<div class="notice notice-error"><p>' . sprintf(
52
+ __('WordPress Popular Posts requires at least PHP %1$s and WordPress %2$s to function correctly. Your site uses PHP %3$s and WordPress %4$s.', 'wordpress-popular-posts'),
53
+ WPP_MIN_PHP_VERSION,
54
+ WPP_MIN_WP_VERSION,
55
+ PHP_VERSION,
56
+ $wp_version
57
+ ) . '</p></div>';
58
+ }
59
+ add_action('admin_notices', 'wpp_render_min_requirements_notice');
60
+ }