PluginProbe
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions / 260917
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions v260917
260917 260913 260909 260829 260814 260805 110710 110731 110812 110815 110912 110913 110915 110926 110927 111002 111003 111011 111017 111029 111105 111206 111216 111220 120213 All 189 releases
s2member / src / includes / classes / admin-notices.inc.php

admin-notices.inc.php in s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions 260917, at src/includes/classes/admin-notices.inc.php

375 lines 18.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // @codingStandardsIgnoreFile
3 /**
4 * Enqueues/displays administrative notices.
5 *
6 * Copyright: © 2009-2011
7 * {@link http://websharks-inc.com/ WebSharks, Inc.}
8 * (coded in the USA)
9 *
10 * Released under the terms of the GNU General Public License.
11 * You should have received a copy of the GNU General Public License,
12 * along with this software. In the main directory, see: /licensing/
13 * If not, see: {@link http://www.gnu.org/licenses/}.
14 *
15 * @package s2Member\Admin_Notices
16 * @since 3.5
17 */
18 if(!defined('WPINC')) // MUST have WordPress.
19 exit('Do not access this file directly.');
20
21 if(!class_exists('c_ws_plugin__s2member_admin_notices'))
22 {
23 /**
24 * Enqueues/displays administrative notices.
25 *
26 * @package s2Member\Admin_Notices
27 * @since 3.5
28 */
29 class c_ws_plugin__s2member_admin_notices
30 {
31 /**
32 * Enqueues administrative notices.
33 *
34 * @package s2Member\Admin_Notices
35 * @since 3.5
36 *
37 * @param string $notice String value of actual notice *(i.e., the message)*.
38 * @param string|array $on_pages Optional. Defaults to any page. String or array of pages to display this notice on.
39 * @param bool $error Optional. True if this notice is regarding an error. Defaults to false.
40 * @param int $time Optional. Unix timestamp indicating when this notice will be displayed.
41 * @param bool $dismiss Optional. If true, the notice will remain persistent, until dismissed. Defaults to false.
42 */
43 public static function enqueue_admin_notice($notice = '', $on_pages = array(), $error = FALSE, $time = 0, $dismiss = FALSE)
44 {
45 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
46 do_action('ws_plugin__s2member_before_enqueue_admin_notice', get_defined_vars());
47 unset($__refs, $__v); // Allow variables to be modified by reference.
48
49 if($notice && is_string($notice))// Have a valid string.
50 {
51 $notices = (array)get_option('ws_plugin__s2member_notices');
52 array_push($notices, array('notice' => $notice, 'on_pages' => $on_pages, 'error' => $error, 'time' => $time, 'dismiss' => $dismiss));
53
54 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
55 do_action('ws_plugin__s2member_during_enqueue_admin_notice', get_defined_vars());
56 unset($__refs, $__v); // Allow variables to be modified by reference.
57
58 update_option('ws_plugin__s2member_notices', c_ws_plugin__s2member_utils_arrays::array_unique($notices));
59 }
60 do_action('ws_plugin__s2member_after_enqueue_admin_notice', get_defined_vars());
61 }
62
63 /**
64 * Displays an administrative notice.
65 *
66 * @package s2Member\Admin_Notices
67 * @since 3.5
68 *
69 * @param string $notice String value of actual notice *(i.e., the message)*.
70 * @param bool $error Optional. True if this notice is regarding an error. Defaults to false.
71 * @param bool $dismiss Optional. If true, the notice will be displayed with a dismissal link. Defaults to false.
72 */
73 public static function display_admin_notice($notice = '', $error = FALSE, $dismiss = FALSE)
74 {
75 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
76 do_action('ws_plugin__s2member_before_display_admin_notice', get_defined_vars());
77 unset($__refs, $__v); // Allow variables to be modified by reference.
78
79 if($dismiss) $dismissal_link = '<div style="float:right; margin:0 0 0 1em; font-weight:bold;">'.
80 '[ <a href="'.esc_attr(add_query_arg('ws-plugin--s2member-dismiss-admin-notice', urlencode(md5($notice)), $_SERVER['REQUEST_URI'])).'">dismiss</a> ]'.
81 '</div>';
82 if($notice && is_string($notice) && $error)
83 {
84 if($dismiss && !empty($dismissal_link))
85 $notice = $dismissal_link.$notice;
86 echo '<div class="notice notice-error"><p>'.wp_kses_post($notice).'</p></div>';
87 }
88 else if($notice && is_string($notice))
89 {
90 if($dismiss && !empty($dismissal_link))
91 $notice = $dismissal_link.$notice;
92 echo '<div class="notice notice-info"><p>'.wp_kses_post($notice).'</p></div>';
93 }
94 do_action('ws_plugin__s2member_after_display_admin_notice', get_defined_vars());
95 }
96
97 /**
98 * Displays a branded s2Member administrative notice.
99 *
100 * @package s2Member\Admin_Notices
101 * @since 260904.1923
102 *
103 * @param string $title Notice title.
104 * @param string $message Main notice message.
105 * @param bool $error Optional. True for an error notice; otherwise an informational notice.
106 * @param string $dismiss_url Optional explicit URL that dismisses the current notice incident.
107 */
108 public static function display_branded_notice($title = '', $message = '', $error = FALSE, $dismiss_url = '')
109 {
110 $title = trim((string)$title);
111 $message = trim((string)$message);
112 $dismiss_url = trim((string)$dismiss_url);
113 if(!$message)
114 return;
115
116 $_logo_url = $GLOBALS['WS_PLUGIN__']['s2member']['c']['dir_url'].'/src/images/logo-square-big.png';
117 $_notice_class = ($error) ? 'notice notice-error' : 'notice notice-info'; //260910.0818 Use WordPress's standard error style for the immediate Red problem and the quieter informational style for the persistent Orange review notice.
118 $_dismiss = (($dismiss_url !== '') ? '<a href="'.esc_url($dismiss_url).'" title="Dismiss this incident" style="position:absolute; top:8px; right:10px; text-decoration:none;">Dismiss</a>' : ''); //260910.0709 The caller owns the nonce-protected incident URL so dismissal follows incident identity, not mutable notice text.
119
120 //260909.2021 Keep incident dismissal explicit so changing notice text cannot accidentally make a persistent problem look like a new one.
121 echo '<div class="'.esc_attr($_notice_class).'" style="position:relative; margin:0 0 15px 2px !important; padding:8px '.(($dismiss_url !== '') ? '60px' : '8px').' 8px 8px !important;">'.$_dismiss.'<table cellspacing="0" cellpadding="0"><tr><td style="vertical-align:top; padding:0 10px 0 0;"><img src="'.esc_url($_logo_url).'" alt="" width="40" height="40" style="border:0;" /></td><td style="vertical-align:top;">'.(($title !== '') ? '<strong>'.esc_html($title).'</strong><br />' : '').wp_kses_post($message).'</td></tr></table></div>';
122 }
123
124 /**
125 * Warns administrators when Pro is too old to contain the current Pro updater behavior.
126 *
127 * @package s2Member\Admin_Notices
128 * @since 260917.0425
129 *
130 * @attaches-to `add_action('admin_notices');`
131 * @attaches-to `add_action('user_admin_notices');`
132 * @attaches-to `add_action('network_admin_notices');`
133 */
134 public static function outdated_pro_notice()
135 {
136 if(!current_user_can('update_plugins') || !defined('WS_PLUGIN__S2MEMBER_PRO_VERSION') || !defined('WS_PLUGIN__S2MEMBER_VERSION'))
137 return;
138
139 //260917.0425 v260913 introduced the current background Pro updater flow; Framework owns this fallback warning only for older Pro releases.
140 $_current_updater_version = '260913';
141 if(version_compare(WS_PLUGIN__S2MEMBER_PRO_VERSION, $_current_updater_version, '>=') || !version_compare(WS_PLUGIN__S2MEMBER_PRO_VERSION, WS_PLUGIN__S2MEMBER_VERSION, '<'))
142 return;
143
144 $_account_url = 'https://s2member.com/account/';
145 //260917.2113 s2Member versions begin with yymmdd; show the installed Pro version's approximate age so administrators can immediately see how far behind it is.
146 $_pro_release_age = '';
147 if(preg_match('/^(\d{2})(\d{2})(\d{2})/', WS_PLUGIN__S2MEMBER_PRO_VERSION, $_pro_version_parts))
148 {
149 $_pro_release_timestamp = mktime(0, 0, 0, (int) $_pro_version_parts[2], (int) $_pro_version_parts[3], 2000 + (int) $_pro_version_parts[1]);
150 if($_pro_release_timestamp)
151 $_pro_release_age = human_time_diff($_pro_release_timestamp, current_time('timestamp'));
152 }
153
154 //260917.1937 Keep this urgent notice compact and skimmable: short paragraphs, prominent version age/security risk, and a clearly separated update action.
155 $_message = '<p style="line-height:1.3em; margin:.3em 0;"><strong>Your s2Member Pro v'.esc_html(WS_PLUGIN__S2MEMBER_PRO_VERSION).($_pro_release_age ? ' is '.esc_html($_pro_release_age).' old and' : '').' is missing important security fixes</strong>.</p>';
156 $_message .= '<p style="line-height:1.3em; margin:.3em 0;"><em>Please install the latest ZIP from WP Admin &gt; Plugins &gt; Add Plugin &gt; Upload Plugin.</em></p>';
157 $_update_button = '<a class="button button-primary" style="margin-top:.3em; background:darkred; border-color:darkred;" href="'.esc_url($_account_url).'" target="_blank" rel="external noopener">Download the Latest s2Member Pro Now</a>';
158
159 //260917.1937 Keep this Framework-owned warning persistent and red; include the action in the message instead of the helper's review slot so no extra <br> is inserted before it.
160 c_ws_plugin__s2member_admin_notices::display_security_notice($_message.$_update_button, '', array(), '', 'notice-error');
161 }
162
163 /**
164 * Displays a branded s2Member security notice.
165 *
166 * @package s2Member\Admin_Notices
167 * @since 260813
168 *
169 * @param string $message Main notice message.
170 * @param string $review Review prompt shown above the items.
171 * @param array $items Notice items, with safe HTML allowed.
172 * @param string $dismiss_url Optional dismissal URL.
173 * @param string $notice_class Optional WordPress notice severity class.
174 */
175 public static function display_security_notice($message = '', $review = '', $items = array(), $dismiss_url = '', $notice_class = 'notice-warning')
176 {
177 $message = trim((string)$message);
178 $review = trim((string)$review);
179 $items = (array)$items;
180 if(!$message)
181 return;
182
183 //260917.0513 Preserve the existing warning style by default, while allowing especially urgent security notices to use WordPress's stronger error styling.
184 $_notice_class = (($notice_class === 'notice-error') ? 'notice notice-error' : 'notice notice-warning');
185
186 $_items = array();
187 foreach($items as $_item)
188 if(is_string($_item) && trim($_item) !== '')
189 $_items[] = '<em>&bull;&nbsp; '.wp_kses_post($_item).'</em>';
190
191 $_logo_url = $GLOBALS['WS_PLUGIN__']['s2member']['c']['dir_url'].'/src/images/logo-square-big.png';
192 $_dismiss = (($dismiss_url !== '') ? '<a href="'.esc_url($dismiss_url).'" title="Dismiss until detected again" style="position:absolute; top:8px; right:10px; text-decoration:none;">Dismiss</a>' : '');
193
194 //260917.1937 Give urgent red security notices a stronger heading without changing the existing presentation of normal yellow security notices.
195 $_title = (($notice_class === 'notice-error') ? '<h2 style="margin:0 0 .3em; color:darkred;">s2Member Security Notice</h2>' : '<strong>s2Member Security Notice</strong><br />');
196 echo '<div class="'.esc_attr($_notice_class).'" style="position:relative; margin:0 0 15px 2px !important; padding:8px 60px 8px 8px !important;">'.$_dismiss.'<table cellspacing="0" cellpadding="0"><tr><td style="vertical-align:top; padding:0 10px 0 0;"><img src="'.esc_url($_logo_url).'" alt="" width="40" height="40" style="border:0;" /></td><td style="vertical-align:top;">'.$_title.wp_kses_post($message).(($review !== '') ? '<br />'.wp_kses_post($review) : '').(($_items) ? '<br />'.implode('<br />', $_items) : '').'</td></tr></table></div>';
197 }
198
199 /**
200 * Records a shortcode user field that is not approved for cross-user display.
201 *
202 * @package s2Member\Admin_Notices
203 * @since 260813
204 *
205 * @param string $field User field ID.
206 * @param string $shortcode Shortcode name.
207 * @param int $post_id Post/Page ID.
208 */
209 public static function shortcode_user_field_unapproved($field = '', $shortcode = '', $post_id = 0)
210 {
211 $field = trim((string)$field);
212 $shortcode = trim((string)$shortcode);
213 $post_id = (int)$post_id;
214 if(!$field || !$shortcode)
215 return;
216
217 $_fields = (array)get_option('ws_plugin__s2member_shortcode_user_fields_transition_fields', array());
218 $_entry_key = md5(strtolower($field)."\0".strtolower($shortcode)."\0".$post_id);
219
220 //260813 Keep each detected shortcode location separate, while limiting stored warning data.
221 if(!isset($_fields[$_entry_key]) && count($_fields) >= 40)
222 return;
223 $_old_fields = $_fields;
224 $_fields[$_entry_key] = array('field' => $field, 'shortcode' => $shortcode, 'post_id' => $post_id);
225
226 if($_fields !== $_old_fields)
227 update_option('ws_plugin__s2member_shortcode_user_fields_transition_fields', $_fields, FALSE);
228 }
229
230 /**
231 * Dismisses the shortcode user-fields security notice until another affected shortcode is detected.
232 *
233 * @package s2Member\Admin_Notices
234 * @since 260813
235 */
236 public static function dismiss_shortcode_user_fields_notice()
237 {
238 if(!is_admin() || !current_user_can('create_users') || empty($_GET['s2member-dismiss-shortcode-user-fields-notice']))
239 return;
240
241 check_admin_referer('s2member-dismiss-shortcode-user-fields-notice');
242 delete_option('ws_plugin__s2member_shortcode_user_fields_transition_fields');
243
244 wp_safe_redirect(wp_get_referer() ? wp_get_referer() : admin_url());
245 exit;
246 }
247
248 /**
249 * Displays the shared shortcode user-fields security notice.
250 *
251 * @package s2Member\Admin_Notices
252 * @since 260813
253 */
254 public static function shortcode_user_fields_notice()
255 {
256 if(!current_user_can('create_users'))
257 return;
258
259 $_fields = (array)get_option('ws_plugin__s2member_shortcode_user_fields_transition_fields', array());
260 if(!$_fields)
261 return;
262
263 //260813 Use the submitted whitelist on save so the notice updates immediately.
264 $_using_submitted_whitelist = !empty($_POST['ws_plugin__s2member_options_save']) && is_string($_POST['ws_plugin__s2member_options_save']) && wp_verify_nonce($_POST['ws_plugin__s2member_options_save'], 'ws-plugin--s2member-options-save') && isset($_POST['ws_plugin__s2member_sc_user_fields_whitelist']) && is_string($_POST['ws_plugin__s2member_sc_user_fields_whitelist']);
265 $_field_whitelist = ($_using_submitted_whitelist) ? trim((string)wp_unslash($_POST['ws_plugin__s2member_sc_user_fields_whitelist'])) : trim((string)$GLOBALS['WS_PLUGIN__']['s2member']['o']['sc_user_fields_whitelist']);
266 $_field_whitelist = ($_field_whitelist !== '') ? preg_split('/\s*,\s*/', strtolower($_field_whitelist), -1, PREG_SPLIT_NO_EMPTY) : array();
267 $_field_whitelist = array_flip($_field_whitelist);
268 foreach($_fields as $_key => $_details)
269 {
270 if(!is_array($_details) || empty($_details['field']) || empty($_details['shortcode']))
271 unset($_fields[$_key]);
272 else if(isset($_field_whitelist[strtolower($_details['field'])]))
273 unset($_fields[$_key]);
274 }
275
276 if(!$_fields)
277 {
278 delete_option('ws_plugin__s2member_shortcode_user_fields_transition_fields');
279 return;
280 }
281 update_option('ws_plugin__s2member_shortcode_user_fields_transition_fields', $_fields, FALSE);
282
283 // Build a useful field list with a separate entry for each detected shortcode location.
284 $_field_items = array();
285 foreach($_fields as $_details)
286 {
287 $_item = esc_html($_details['field']).' — ['.esc_html($_details['shortcode']).']';
288 $_post_id = (!empty($_details['post_id'])) ? (int)$_details['post_id'] : 0;
289 if($_post_id > 0 && ($_edit_link = get_edit_post_link($_post_id, '')))
290 {
291 $_post_title = get_the_title($_post_id);
292 $_post_title = ($_post_title !== '') ? $_post_title : '(no title)';
293 $_item .= ' — <a href="'.esc_url($_edit_link).'">'.esc_html($_post_title).' (#'.$_post_id.')</a>';
294 }
295 $_field_items[] = $_item;
296 }
297 unset($_details, $_item, $_post_id, $_edit_link, $_post_title);
298
299 $_settings_url = add_query_arg('s2member-open-panel', 'shortcode-user-fields-whitelist', admin_url('/admin.php?page=ws-plugin--s2member-gen-ops')).'#ws-plugin--s2member-shortcode-user-fields-whitelist';
300 $_dismiss_url = wp_nonce_url(add_query_arg('s2member-dismiss-shortcode-user-fields-notice', '1', admin_url()), 's2member-dismiss-shortcode-user-fields-notice');
301 $_message = 'Some s2Member shortcodes attempted to display user fields from other accounts that are not in <em><a href="'.esc_url($_settings_url).'">s2Member → General Options → Shortcode User Fields Whitelist</a></em>. Those cross-user field values were blocked.';
302 c_ws_plugin__s2member_admin_notices::display_security_notice($_message, 'Review the blocked fields below and allow the ones that are okay for other users to see:', $_field_items, $_dismiss_url);
303 }
304
305 /**
306 * Processes all administrative notices.
307 *
308 * @package s2Member\Admin_Notices
309 * @since 3.5
310 *
311 * @attaches-to ``add_action('admin_notices');``
312 * @attaches-to ``add_action('user_admin_notices');``
313 * @attaches-to ``add_action('network_admin_notices');``
314 * @todo Update to ``add_action('all_admin_notices');``.
315 */
316 public static function admin_notices()
317 {
318 global $pagenow; // This holds the current page filename.
319
320 do_action('ws_plugin__s2member_before_admin_notices', get_defined_vars());
321
322 if(is_admin() && is_array($notices = get_option('ws_plugin__s2member_notices')) && !empty($notices))
323 {
324 $a = (is_blog_admin()) ? 'blog' : '';
325 $a = (is_user_admin()) ? 'user' : $a;
326 $a = (is_network_admin()) ? 'network' : $a;
327 $a = (!$a) ? 'blog' : $a; // Default blog admin.
328
329 foreach($notices as $i => $notice) // Check several things about each notice.
330 {
331 //250510 Fixed for PHP 8.1+: safely normalize on_pages before foreach
332 $notice = (array)$notice;
333 $notice['on_pages'] = empty($notice['on_pages']) ? array('*') : (array)$notice['on_pages'];
334 foreach($notice['on_pages'] as $page)
335 {
336 if(!preg_match('/^(.+?)\:/', $page)) // NO prefix?
337 $page = 'blog:'.ltrim($page, ':'); // `blog:`
338
339 $adms = preg_split('/\|/', preg_replace('/\:(.*)$/i', '', $page));
340 $page = preg_replace('/^([^\:]*)\:/i', '', $page);
341
342 if(empty($adms) || in_array('*', $adms) || in_array($a, $adms))
343 if(!$page || '*' === $page || $pagenow === $page || @$_GET['page'] === $page)
344 {
345 if(strtotime('now') >= (int)$notice['time']) // Time to show it?
346 {
347 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
348 do_action('ws_plugin__s2member_during_admin_notices_before_display', get_defined_vars());
349 unset($__refs, $__v); // Allow variables to be modified by reference.
350
351 if(!$notice['dismiss'] || (!empty($_GET['ws-plugin--s2member-dismiss-admin-notice']) && $_GET['ws-plugin--s2member-dismiss-admin-notice'] === md5($notice['notice'])))
352 unset($notices[$i]); // Clear this administrative notice now?
353
354 if(!$notice['dismiss'] || empty($_GET['ws-plugin--s2member-dismiss-admin-notice']) || $_GET['ws-plugin--s2member-dismiss-admin-notice'] !== md5($notice['notice']))
355 c_ws_plugin__s2member_admin_notices::display_admin_notice($notice['notice'], $notice['error'], $notice['dismiss']);
356
357 do_action('ws_plugin__s2member_during_admin_notices_after_display', get_defined_vars());
358 }
359 continue 2; // This notice processed; continue.
360 }
361 }
362 }
363 $notices = array_merge($notices); // Re-index array.
364
365 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
366 do_action('ws_plugin__s2member_during_admin_notices', get_defined_vars());
367 unset($__refs, $__v); // Allow variables to be modified by reference.
368
369 update_option('ws_plugin__s2member_notices', $notices);
370 }
371 do_action('ws_plugin__s2member_after_admin_notices', get_defined_vars());
372 }
373 }
374 }
375