PluginProbe
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions / 261011
s2Member – Excellent for All Kinds of Memberships, Content Restriction Paywalls & Member Access Subscriptions v261011
261011 261001 260927 260917 260913 260909 260829 260814 260805 110710 110731 110812 110815 110912 110913 110915 110926 110927 111002 111003 111011 111017 111029 111105 111206 All 192 releases
← All changes | src/includes/classes/menu-pages.inc.php +107 -11 260909 → 261011 View file →
@@ -95,9 +95,9 @@
95 95 $options = ws_plugin__s2member_configure_options_and_their_defaults(($options = array_merge($options, array('options_version' => (string)($options['options_version'] + 0.001)))));
96 96 update_option('ws_plugin__s2member_options', $options).((is_multisite() && is_main_site()) ? update_site_option('ws_plugin__s2member_options', $options) : NULL).update_option('ws_plugin__s2member_cache', array());
97 97
98 98 if($update_other === TRUE || in_array('auto_eot_system', (array)$update_other)) // Handle the Auto-EOT System now (enable/disable).
99 - ($options['auto_eot_system_enabled'] == 1) ? c_ws_plugin__s2member_auto_eots::add_auto_eot_system() : c_ws_plugin__s2member_auto_eots::delete_auto_eot_system();
99 + ($options['auto_eot_system_enabled'] == 1) ? c_ws_plugin__s2member_eot::add_auto_eot_system() : c_ws_plugin__s2member_eot::delete_auto_eot_system();
100 100
101 101 if(($display_notices === TRUE || in_array('success', (array)$display_notices)) && ($notice = '<strong>Options saved.'.(($request_refresh) ? ' Please <a href="'.esc_attr($_SERVER['REQUEST_URI']).'">refresh</a>.' : '').'</strong>'))
102 102 ($enqueue_notices === TRUE || in_array('success', (array)$enqueue_notices)) ? c_ws_plugin__s2member_admin_notices::enqueue_admin_notice($notice, '*:*') : c_ws_plugin__s2member_admin_notices::display_admin_notice($notice);
103 103
@@ -133,8 +133,85 @@
133 133 return apply_filters('ws_plugin__s2member_update_all_options', (($updated_all_options) ? TRUE : FALSE), get_defined_vars());
134 134 }
135 135
136 136 /**
137 + * Displays shared End-of-Term demotion-role settings on gateway option pages.
138 + *
139 + * @package s2Member\Menu_Pages
140 + * @since 260916.2004
141 + *
142 + * @return null
143 + */
144 + public static function eot_demotion_options()
145 + {
146 + $demotion_to_role = (string)$GLOBALS['WS_PLUGIN__']['s2member']['o']['eot_demotion_to_role'];
147 + $demotion_from = (string)$GLOBALS['WS_PLUGIN__']['s2member']['o']['eot_demotion_from'];
148 + $demotion_disabled = ($GLOBALS['WS_PLUGIN__']['s2member']['o']['membership_eot_behavior'] === 'delete');
149 +
150 + //260917.0003 Keep Demote From/To visible but disabled when EOT behavior is Delete; JavaScript mirrors this immediately when the behavior selector changes.
151 + echo '<tr class="ws-plugin--s2member-eot-demotion-option-row"'.(($demotion_disabled) ? ' style="opacity:0.5;"' : '').'>'."\n";
152 + echo '<th><label for="ws-plugin--s2member-eot-demotion-from">Demote From:</label></th>'."\n";
153 + echo '</tr>'."\n";
154 + echo '<tr class="ws-plugin--s2member-eot-demotion-option-row"'.(($demotion_disabled) ? ' style="opacity:0.5;"' : '').'>'."\n";
155 + echo '<td>'."\n";
156 + echo '<select name="ws_plugin__s2member_eot_demotion_from" id="ws-plugin--s2member-eot-demotion-from"'.(($demotion_disabled) ? ' disabled="disabled"' : '').'>'."\n";
157 + echo '<option value="s2member_level"'.(($demotion_from === 's2member_level') ? ' selected="selected"' : '').'>s2Member Level role (preserve other WordPress roles)</option>'."\n";
158 + echo '<option value="all"'.(($demotion_from === 'all') ? ' selected="selected"' : '').'>All WordPress roles (legacy behavior)</option>'."\n";
159 + echo '</select><br />'."\n";
160 + echo '<em>These settings are used only when Membership End-of-Term Behavior is set to Demote.</em><br /><br />'."\n";
161 + //260916.2004 Explain the fresh-install behavior, legacy compatibility, and capability caveat without treating Subscriber/Level 0 as a role users are demoted from.
162 + echo '<em>New installations remove only the user\'s <code>s2member_levelN</code> role at EOT and preserve other WordPress roles; upgraded installations retain the legacy all-role replacement until you change this setting. Preserved roles also preserve their existing capabilities, so roles such as Editor/Author/Contributor or custom roles may continue to grant s2Member access.</em>'."\n";
163 + echo '</td>'."\n";
164 + echo '</tr>'."\n";
165 + echo '<tr class="ws-plugin--s2member-eot-demotion-option-row"'.(($demotion_disabled) ? ' style="opacity:0.5;"' : '').'>'."\n";
166 + echo '<th><label for="ws-plugin--s2member-eot-demotion-to-role">Demote To Role:</label></th>'."\n";
167 + echo '</tr>'."\n";
168 + echo '<tr class="ws-plugin--s2member-eot-demotion-option-row"'.(($demotion_disabled) ? ' style="opacity:0.5;"' : '').'>'."\n";
169 + echo '<td>'."\n";
170 + //260916.2345 Group Subscriber and s2Member Levels first, then list other registered roles by slug so larger role lists stay predictable and easier to scan.
171 + global $wp_roles;
172 + if(!is_object($wp_roles))
173 + $wp_roles = new WP_Roles();
174 + $level_roles = $other_roles = array();
175 + foreach((array)$wp_roles->roles as $role_slug => $role_data)
176 + {
177 + if(in_array($role_slug, array('administrator', 'editor', 'author', 'contributor', 's2member_pending_deletion'), TRUE))
178 + continue;
179 + $role_name = !empty($role_data['name']) ? translate_user_role($role_data['name']) : $role_slug;
180 + if($role_slug === 'subscriber')
181 + $level_roles[0] = array($role_slug, $role_name);
182 + else if(preg_match('/^s2member_level([1-9][0-9]*)$/', $role_slug, $m))
183 + $level_roles[(int)$m[1]] = array($role_slug, $role_name);
184 + else
185 + $other_roles[$role_slug] = $role_name;
186 + }
187 + ksort($level_roles, SORT_NUMERIC);
188 + ksort($other_roles, SORT_STRING);
189 + echo '<select name="ws_plugin__s2member_eot_demotion_to_role" id="ws-plugin--s2member-eot-demotion-to-role"'.(($demotion_disabled) ? ' disabled="disabled"' : '').'>'."\n";
190 + if($level_roles)
191 + {
192 + echo '<optgroup label="s2Member Levels">'."\n";
193 + foreach($level_roles as $role)
194 + echo '<option value="'.esc_attr($role[0]).'"'.(($demotion_to_role === $role[0]) ? ' selected="selected"' : '').'>'.esc_html($role[1]).' ('.esc_html($role[0]).')</option>'."\n";
195 + echo '</optgroup>'."\n";
196 + }
197 + if($other_roles)
198 + {
199 + echo '<optgroup label="Other Roles">'."\n";
200 + foreach($other_roles as $role_slug => $role_name)
201 + echo '<option value="'.esc_attr($role_slug).'"'.(($demotion_to_role === $role_slug) ? ' selected="selected"' : '').'>'.esc_html($role_name).' ('.esc_html($role_slug).')</option>'."\n";
202 + echo '</optgroup>'."\n";
203 + }
204 + echo '</select><br />'."\n";
205 + echo '<em>Select the WordPress role to assign when s2Member demotes a member at EOT. Subscriber is the default.</em><br /><br />'."\n";
206 + echo '<em>WordPress\'s elevated default roles and s2Member\'s Pending Deletion role are intentionally not available here. Any role selected here keeps the capabilities assigned to it. If those capabilities grant s2Member access, the member may still be able to access protected content after EOT.</em><br /><br />'."\n";
207 + echo '<em>Developers who need a role not available here can use <code>ws_plugin__s2member_force_demotion_role</code>; see <a href="https://s2member.com/kb-article/how-do-i-specify-the-demotion-role-upon-eot/" target="_blank" rel="noopener noreferrer external">How do I specify the demotion role upon EOT?</a></em>'."\n";
208 + echo '</td>'."\n";
209 + echo '</tr>'."\n";
210 + do_action('ws_plugin__s2member_after_eot_demotion_options', get_defined_vars());
211 + }
212 +
213 + /**
137 214 * Adds option menus / sub-menus.
138 215 *
139 216 * @package s2Member\Menu_Pages
140 217 * @since 3.5
@@ -297,11 +374,11 @@
297 374 wp_enqueue_script('thickbox');
298 375 wp_enqueue_script('media-upload');
299 376 wp_enqueue_script('jquery-ui-core');
300 377 wp_enqueue_script('jquery-sprintf', $GLOBALS['WS_PLUGIN__']['s2member']['c']['dir_url'].'/src/includes/jquery/jquery.sprintf/jquery.sprintf.min.js', array('jquery'), c_ws_plugin__s2member_utilities::ver_checksum());
301 - wp_enqueue_script('jquery-json-ps', $GLOBALS['WS_PLUGIN__']['s2member']['c']['dir_url'].'/src/includes/jquery/jquery.json-ps/jquery.json-ps.min.js', array('jquery'), c_ws_plugin__s2member_utilities::ver_checksum());
302 378 wp_enqueue_script('jquery-ui-effects', $GLOBALS['WS_PLUGIN__']['s2member']['c']['dir_url'].'/src/includes/jquery/jquery.ui-effects/jquery.ui-effects.min.js', array('jquery', 'jquery-ui-core'), c_ws_plugin__s2member_utilities::ver_checksum());
303 - wp_enqueue_script('ws-plugin--s2member-menu-pages', admin_url('admin.php?ws_plugin__s2member_menu_pages_js='.urlencode(mt_rand()), is_ssl() ? 'https' : 'http'), array('jquery', 'thickbox', 'media-upload', 'jquery-sprintf', 'jquery-json-ps', 'jquery-ui-core', 'jquery-ui-effects', 'password-strength-meter'), c_ws_plugin__s2member_utilities::ver_checksum());
379 + //260928.0402 Use WordPress's registered JSON2 compatibility script instead of shipping a duplicate JSON parser.
380 + wp_enqueue_script('ws-plugin--s2member-menu-pages', admin_url('admin.php?ws_plugin__s2member_menu_pages_js='.urlencode(mt_rand()), is_ssl() ? 'https' : 'http'), array('jquery', 'thickbox', 'media-upload', 'jquery-sprintf', 'json2', 'jquery-ui-core', 'jquery-ui-effects', 'password-strength-meter'), c_ws_plugin__s2member_utilities::ver_checksum());
304 381
305 382 do_action('ws_plugin__s2member_during_add_admin_scripts', get_defined_vars());
306 383 }
307 384 do_action('ws_plugin__s2member_after_add_admin_scripts', get_defined_vars());
@@ -345,11 +422,13 @@
345 422 if(!empty($_GET['ws_plugin__s2member_download_log_file_v']) && is_string($nonce = $_GET['ws_plugin__s2member_download_log_file_v']) && wp_verify_nonce($nonce, 'ws-plugin--s2member-download-log-file-v'))
346 423 {
347 424 $logs_dir = $GLOBALS['WS_PLUGIN__']['s2member']['c']['logs_dir'];
348 425
349 - if(file_exists($logs_dir.'/'.$log_file))
350 - $log_file_contents = file_get_contents($logs_dir.'/'.$log_file);
351 - else $log_file_contents = '';
426 + $resource = FALSE;
427 + $log_file_size = 0;
428 + if(is_file($logs_dir.'/'.$log_file) && is_readable($logs_dir.'/'.$log_file) && ($resource = @fopen($logs_dir.'/'.$log_file, 'rb')))
429 + if(($log_file_stat = fstat($resource)) !== FALSE)
430 + $log_file_size = $log_file_stat['size'];
352 431
353 432 @set_time_limit(0);
354 433 @ini_set('memory_limit', apply_filters('admin_memory_limit', WP_MAX_MEMORY_LIMIT));
355 434
@@ -363,9 +442,9 @@
363 442
364 443 header('Content-Encoding: none');
365 444 header('Accept-Ranges: none');
366 445 header('Content-Type: text/plain; charset=UTF-8');
367 - header('Content-Length: '.strlen($log_file_contents));
446 + header('Content-Length: '.$log_file_size);
368 447 header('Expires: '.gmdate('D, d M Y H:i:s', strtotime('-1 week')).' GMT');
369 448 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
370 449 header('Cache-Control: no-cache, must-revalidate, max-age=0');
371 450 header('Cache-Control: post-check=0, pre-check=0', FALSE);
@@ -372,9 +451,24 @@
372 451 header('Pragma: no-cache');
373 452
374 453 header('Content-Disposition: attachment; filename="'.$log_file.'"');
375 454
376 - exit($log_file_contents); // Log file.
455 + if(is_resource($resource))
456 + {
457 + //261006.0145 Stream log downloads in bounded chunks so large logs do not exhaust PHP memory.
458 + $chunk_size = max(1, min(2097152, (int)apply_filters('ws_plugin__s2member_file_downloads_chunk_size', 2097152, get_defined_vars())));
459 + $bytes_remaining = $log_file_size;
460 + while($bytes_remaining > 0 && !feof($resource))
461 + {
462 + $chunk = fread($resource, min($chunk_size, $bytes_remaining));
463 + if($chunk === FALSE || $chunk === '') break;
464 + $bytes_remaining -= strlen($chunk);
465 + echo $chunk;
466 + flush();
467 + }
468 + fclose($resource);
469 + }
470 + exit; // Clean exit after serving file.
377 471 }
378 472 }
379 473
380 474 /**
@@ -473,9 +567,10 @@
473 567 $error = TRUE;
474 568 }
475 569 if(!empty($error))
476 570 c_ws_plugin__s2member_admin_notices::display_admin_notice('Unknown error when attempting to archive log files. Please check directory permissions.', TRUE);
477 - else c_ws_plugin__s2member_admin_notices::display_admin_notice('All log files have been archived succesfully.');
571 + else
572 + c_ws_plugin__s2member_admin_notices::display_admin_notice('All log files have been archived successfully.');
478 573 }
479 574 }
480 575
481 576 /**
@@ -500,9 +595,10 @@
500 595 if(!unlink($log_dir_file)) $error = TRUE;
501 596 }
502 597 if(!empty($error))
503 598 c_ws_plugin__s2member_admin_notices::display_admin_notice('Unknown error when attempting to delete log files. Please check directory permissions.', TRUE);
504 - else c_ws_plugin__s2member_admin_notices::display_admin_notice('All log files have been deleted succesfully.');
599 + else
600 + c_ws_plugin__s2member_admin_notices::display_admin_notice('All log files have been deleted successfully.');
505 601 }
506 602 }
507 603
508 604 /**
@@ -972,9 +1068,9 @@
972 1068 // Sanitize display names
973 1069 $name_hdr = str_replace(array("\r\n", "\r", "\n"), ' ', (string) $name);
974 1070 $name_hdr = str_replace('"', "'", $name_hdr);
975 1071
976 - $sitename = wp_parse_url(home_url(), PHP_URL_HOST);
1072 + $sitename = c_ws_plugin__s2member_utils_urls::parse_url(home_url(), PHP_URL_HOST);
977 1073 $sitename = is_string($sitename) ? strtolower($sitename) : '';
978 1074 if (strpos($sitename, 'www.') === 0) $sitename = substr($sitename, 4);
979 1075
980 1076 $default_from_email = 'wordpress@'.$sitename;