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
← All changes | src/includes/classes/admin-notices.inc.php +209 -1 260805260917 View file →
@@ -94,8 +94,216 @@
94 94 do_action('ws_plugin__s2member_after_display_admin_notice', get_defined_vars());
95 95 }
96 96
97 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 + /**
98 306 * Processes all administrative notices.
99 307 *
100 308 * @package s2Member\Admin_Notices
101 309 * @since 3.5
@@ -122,9 +330,9 @@
122 330 {
123 331 //250510 Fixed for PHP 8.1+: safely normalize on_pages before foreach
124 332 $notice = (array)$notice;
125 333 $notice['on_pages'] = empty($notice['on_pages']) ? array('*') : (array)$notice['on_pages'];
126 - foreach($notice['on_pages'] as $page)
334 + foreach($notice['on_pages'] as $page)
127 335 {
128 336 if(!preg_match('/^(.+?)\:/', $page)) // NO prefix?
129 337 $page = 'blog:'.ltrim($page, ':'); // `blog:`
130 338