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

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

1,102 lines 64.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // @codingStandardsIgnoreFile
3 /**
4 * Administrative menu pages.
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\Menu_Pages
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_menu_pages'))
22 {
23 /**
24 * Administrative menu pages.
25 *
26 * @package s2Member\Menu_Pages
27 * @since 3.5
28 */
29 class c_ws_plugin__s2member_menu_pages
30 {
31 /**
32 * Pre-display errors.
33 *
34 * @package s2Member\Menu_Pages
35 * @since 111209
36 *
37 * @var array
38 */
39 public static $pre_display_errors = array();
40
41 /**
42 * Saves all options from any menu page.
43 *
44 * Can also be self-verified; and configured extensively with function parameters.
45 *
46 * @package s2Member\Menu_Pages
47 * @since 3.5
48 *
49 * @param null|array $new_options Optional. Force feed an array of new options. Defaults to ``$_POST`` vars.
50 * If ``$new_options`` are passed in, be SURE that you've already applied ``stripslashes_deep()``.
51 * @param bool $verified Optional. Defaults to false. If true, ``wp_verify_nonce()`` is skipped in this routine.
52 * @param bool $update_other Optional. Defaults to true. If false, other option-dependent routines will not be processed.
53 * @param bool|array $display_notices Optional. Defaults to true. Can be false, or an array of certain notices that can be displayed.
54 * @param bool|array $enqueue_notices Optional. Defaults to false. Can be true, or an array of certain notices that should be enqueued.
55 * @param bool $request_refresh Optional. Defaults to false. If true, resulting `success` notice will include a link to refresh the menu page.
56 *
57 * @return bool True if all s2Member options were updated successfully, else false.
58 */
59 public static function update_all_options($new_options = NULL, $verified = FALSE, $update_other = TRUE, $display_notices = TRUE, $enqueue_notices = FALSE, $request_refresh = FALSE)
60 {
61 $updated_all_options = FALSE; // Initializing this variable here makes it an available reference-variable to Hooks/Filters.
62
63 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
64 do_action('ws_plugin__s2member_before_update_all_options', get_defined_vars()); // If you use this Hook, be sure to use ``wp_verify_nonce()``.
65 unset($__refs, $__v); // Housekeeping.
66
67 if($verified || (!empty($_POST['ws_plugin__s2member_options_save']) && ($nonce = $_POST['ws_plugin__s2member_options_save']) && wp_verify_nonce($nonce, 'ws-plugin--s2member-options-save')))
68 {
69 $options = $GLOBALS['WS_PLUGIN__']['s2member']['o']; // Acquire the full existing configuration options array here.
70
71 $new_options = (is_array($new_options)) ? $new_options : ((!empty($_POST) && is_array($_POST)) ? stripslashes_deep($_POST) : array());
72 $new_options = c_ws_plugin__s2member_utils_strings::trim_deep($new_options);
73
74 foreach($new_options as $key => $value) // Find all keys contained within ``$new_options`` matching `^ws_plugin__s2member_`.
75 if(strpos($key, 'ws_plugin__s2member_') === 0) // A relevant ``$new_options`` key matching `^ws_plugin__s2member_`?
76
77 if($key === 'ws_plugin__s2member_configured') // s2Member is now configured (according to these options)?
78 ($GLOBALS['WS_PLUGIN__']['s2member']['c']['configured'] = $value).update_option('ws_plugin__s2member_configured', $value);
79
80 else if(!is_array($value) || (is_array($value) && array_shift($value) === 'update-signal')) // Updating an array?
81 $options[preg_replace('/^'.preg_quote('ws_plugin__s2member_', '/').'/', '', $key)] = $value;
82
83 unset($key, $value); // Unset these utility variables now. This prevents bleeding vars into Hooks/Filters that are of no use.
84
85 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
86 do_action('ws_plugin__s2member_during_update_all_options', get_defined_vars());
87 unset($__refs, $__v); // Housekeeping.
88
89 //260812 Keep the old s2Get option synchronized for rollback compatibility, while accepting legacy updates during the transition.
90 if(isset($new_options['ws_plugin__s2member_sc_s2get_userid_whitelist']) && !isset($new_options['ws_plugin__s2member_sc_user_fields_whitelist']))
91 $options['sc_user_fields_whitelist'] = $options['sc_s2get_userid_whitelist'];
92 if(isset($options['sc_user_fields_whitelist']))
93 $options['sc_s2get_userid_whitelist'] = $options['sc_user_fields_whitelist'];
94
95 $options = ws_plugin__s2member_configure_options_and_their_defaults(($options = array_merge($options, array('options_version' => (string)($options['options_version'] + 0.001)))));
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
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();
100
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 ($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
104 if(empty($_GET['page']) || $_GET['page'] !== 'ws-plugin--s2member-mms-ops') // Do NOT display page-conflict-warnings on the Main Multisite Configuration panel.
105 {
106 if(!$options['membership_options_page'] && ($display_notices === TRUE || in_array('page-conflict-warnings', (array)$display_notices)) && ($notice = '<strong>NOTE:</strong> s2Member security restrictions will NOT be enforced until you\'ve configured a Membership Options Page. See: <strong>s2Member → General Options → Membership Options Page</strong>.'))
107 ($enqueue_notices === TRUE || in_array('page-conflict-warnings', (array)$enqueue_notices)) ? c_ws_plugin__s2member_admin_notices::enqueue_admin_notice($notice, '*:*', TRUE) : c_ws_plugin__s2member_admin_notices::display_admin_notice($notice, TRUE);
108
109 if($options['login_welcome_page'] && $options['login_welcome_page'] === $options['membership_options_page'] && ($display_notices === TRUE || in_array('page-conflict-warnings', (array)$display_notices)) && ($notice = '<strong>s2Member:</strong> Your Login Welcome Page is the same as your Membership Options Page. Please correct this. See: <strong>s2Member → General Options → Login Welcome Page</strong>.'))
110 ($enqueue_notices === TRUE || in_array('page-conflict-warnings', (array)$enqueue_notices)) ? c_ws_plugin__s2member_admin_notices::enqueue_admin_notice($notice, '*:*', TRUE) : c_ws_plugin__s2member_admin_notices::display_admin_notice($notice, TRUE);
111
112 if($options['membership_options_page'] && (string)get_option('page_on_front') === $options['membership_options_page'] && ($display_notices === TRUE || in_array('page-conflict-warnings', (array)$display_notices)) && ($notice = '<strong>s2Member:</strong> Your Membership Options Page is currently configured as your Home Page (i.e., static page) for WordPress. This causes internal conflicts with s2Member. Your Membership Options Page MUST stand alone. Please correct this. See: <strong>WordPress → Reading Options</strong>. Or change: <strong>s2Member → General Options → Membership Options Page</strong>.'))
113 ($enqueue_notices === TRUE || in_array('page-conflict-warnings', (array)$enqueue_notices)) ? c_ws_plugin__s2member_admin_notices::enqueue_admin_notice($notice, '*:*', TRUE) : c_ws_plugin__s2member_admin_notices::display_admin_notice($notice, TRUE);
114
115 if($options['login_welcome_page'] && (string)get_option('page_on_front') === $options['login_welcome_page'] && ($display_notices === TRUE || in_array('page-conflict-warnings', (array)$display_notices)) && ($notice = '<strong>s2Member:</strong> Your Login Welcome Page is currently configured as your Home Page (i.e., static page) for WordPress. This causes internal conflicts with s2Member. Your Login Welcome Page MUST stand alone. Please correct this. See: <strong>WordPress → Reading Options</strong>. Or change: <strong>s2Member → General Options → Login Welcome Page</strong>.'))
116 ($enqueue_notices === TRUE || in_array('page-conflict-warnings', (array)$enqueue_notices)) ? c_ws_plugin__s2member_admin_notices::enqueue_admin_notice($notice, '*:*', TRUE) : c_ws_plugin__s2member_admin_notices::display_admin_notice($notice, TRUE);
117
118 if($options['membership_options_page'] && (string)get_option('page_for_posts') === $options['membership_options_page'] && ($display_notices === TRUE || in_array('page-conflict-warnings', (array)$display_notices)) && ($notice = '<strong>s2Member:</strong> Your Membership Options Page is currently configured as your Posts Page (i.e., static page) for WordPress. This causes internal conflicts with s2Member. Your Membership Options Page MUST stand alone. Please correct this. See: <strong>WordPress → Reading Options</strong>. Or change: <strong>s2Member → General Options → Membership Options Page</strong>.'))
119 ($enqueue_notices === TRUE || in_array('page-conflict-warnings', (array)$enqueue_notices)) ? c_ws_plugin__s2member_admin_notices::enqueue_admin_notice($notice, '*:*', TRUE) : c_ws_plugin__s2member_admin_notices::display_admin_notice($notice, TRUE);
120
121 if($options['login_welcome_page'] && (string)get_option('page_for_posts') === $options['login_welcome_page'] && ($display_notices === TRUE || in_array('page-conflict-warnings', (array)$display_notices)) && ($notice = '<strong>s2Member:</strong> Your Login Welcome Page is currently configured as your Posts Page (i.e., static page) for WordPress. This causes internal conflicts with s2Member. Your Login Welcome Page MUST stand alone. Please correct this. See: <strong>WordPress → Reading Options</strong>. Or change: <strong>s2Member → General Options → Login Welcome Page</strong>.'))
122 ($enqueue_notices === TRUE || in_array('page-conflict-warnings', (array)$enqueue_notices)) ? c_ws_plugin__s2member_admin_notices::enqueue_admin_notice($notice, '*:*', TRUE) : c_ws_plugin__s2member_admin_notices::display_admin_notice($notice, TRUE);
123
124 if($options['file_download_limit_exceeded_page'] && $options['file_download_limit_exceeded_page'] === $options['membership_options_page'] && ($display_notices === TRUE || in_array('page-conflict-warnings', (array)$display_notices)) && ($notice = '<strong>s2Member:</strong> Your Download Limit Exceeded Page is the same as your Membership Options Page. Please correct this. See: <strong>s2Member → Download Options</strong>.'))
125 ($enqueue_notices === TRUE || in_array('page-conflict-warnings', (array)$enqueue_notices)) ? c_ws_plugin__s2member_admin_notices::enqueue_admin_notice($notice, '*:*', TRUE) : c_ws_plugin__s2member_admin_notices::display_admin_notice($notice, TRUE);
126 }
127 $updated_all_options = TRUE; // Flag indicating this routine was processed successfully; and that all s2Member options have been updated successfully.
128 }
129 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
130 do_action('ws_plugin__s2member_after_update_all_options', get_defined_vars());
131 unset($__refs, $__v); // Housekeeping.
132
133 return apply_filters('ws_plugin__s2member_update_all_options', (($updated_all_options) ? TRUE : FALSE), get_defined_vars());
134 }
135
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 }
211
212 /**
213 * Adds option menus / sub-menus.
214 *
215 * @package s2Member\Menu_Pages
216 * @since 3.5
217 *
218 * @attaches-to ``add_action('admin_menu');``
219 */
220 public static function add_admin_options()
221 {
222 do_action('ws_plugin__s2member_before_add_admin_options', get_defined_vars());
223
224 add_filter('plugin_action_links', 'c_ws_plugin__s2member_menu_pages::_add_settings_link', 10, 2);
225
226 if(apply_filters('ws_plugin__s2member_during_add_admin_options_create_menu_items', TRUE, get_defined_vars()))
227 {
228 if((is_multisite() && c_ws_plugin__s2member_utils_conds::is_multisite_farm() && !is_main_site()) || apply_filters('ws_plugin__s2member_during_add_admin_options_clear_right_side', FALSE, get_defined_vars()))
229 $GLOBALS['WS_PLUGIN__']['s2member']['c']['menu_pages'] = array(); // Clear right side.
230
231 $menu = apply_filters('ws_plugin__s2member_during_add_admin_options_menu_slug', 'ws-plugin--s2member-start', get_defined_vars());
232
233 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_menu_page', TRUE, get_defined_vars()))
234 add_menu_page(((c_ws_plugin__s2member_utils_conds::pro_is_installed()) ? 's2Member (Pro)' : 's2Member'), ((c_ws_plugin__s2member_utils_conds::pro_is_installed()) ? 's2Member (Pro)' : 's2Member'),
235 'create_users', $menu, 'c_ws_plugin__s2member_menu_pages::start_page', $GLOBALS['WS_PLUGIN__']['s2member']['c']['dir_url'].'/src/images/brand-favicon.png');
236
237 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_start_page', TRUE, get_defined_vars()))
238 add_submenu_page($menu, 'Getting Started w/ s2Member', 'Getting Started', 'create_users', 'ws-plugin--s2member-start', 'c_ws_plugin__s2member_menu_pages::start_page');
239
240 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_help_page', !is_multisite () || !c_ws_plugin__s2member_utils_conds::is_multisite_farm () || is_main_site (), get_defined_vars()))
241 add_submenu_page($menu, 'Getting Help w/ s2Member', 'Getting Help', 'create_users', 'ws-plugin--s2member-help', 'c_ws_plugin__s2member_menu_pages::help_page');
242
243 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_divider_1', TRUE, get_defined_vars()))
244 add_submenu_page($menu, '', '<span style="display:block; margin:1px 0 1px -5px; padding:0; height:1px; line-height:1px; background:#CCCCCC;"></span>', 'create_users', '#');
245
246 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_mms_ops_page', (!is_multisite() || is_main_site()), get_defined_vars()))
247 add_submenu_page($menu, 's2Member Multisite Configuration', 'Multisite (Config)', 'create_users', 'ws-plugin--s2member-mms-ops', 'c_ws_plugin__s2member_menu_pages::mms_ops_page');
248
249 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_gen_ops_page', TRUE, get_defined_vars()))
250 add_submenu_page($menu, 's2Member General Options', 'General Options', 'create_users', 'ws-plugin--s2member-gen-ops', 'c_ws_plugin__s2member_menu_pages::gen_ops_page');
251
252 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_res_ops_page', TRUE, get_defined_vars()))
253 add_submenu_page($menu, 's2Member Restriction Options', 'Restriction Options', 'create_users', 'ws-plugin--s2member-res-ops', 'c_ws_plugin__s2member_menu_pages::res_ops_page');
254
255 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_down_ops_page', (!is_multisite() || !c_ws_plugin__s2member_utils_conds::is_multisite_farm() || is_main_site()), get_defined_vars()))
256 add_submenu_page($menu, 's2Member Download Options', 'Download Options', 'create_users', 'ws-plugin--s2member-down-ops', 'c_ws_plugin__s2member_menu_pages::down_ops_page');
257
258 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_divider_2', TRUE, get_defined_vars()))
259 add_submenu_page($menu, '', '<span style="display:block; margin:1px 0 1px -5px; padding:0; height:1px; line-height:1px; background:#CCCCCC;"></span>', 'create_users', '#');
260
261 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_paypal_ops_page', TRUE, get_defined_vars()))
262 add_submenu_page($menu, 's2Member PayPal Options', 'PayPal Options', 'create_users', 'ws-plugin--s2member-paypal-ops', 'c_ws_plugin__s2member_menu_pages::paypal_ops_page');
263
264 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_paypal_buttons_page', TRUE, get_defined_vars()))
265 add_submenu_page($menu, 's2Member PayPal Buttons', 'PayPal Buttons', 'create_users', 'ws-plugin--s2member-paypal-buttons', 'c_ws_plugin__s2member_menu_pages::paypal_buttons_page');
266
267 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_divider_3', TRUE, get_defined_vars()))
268 add_submenu_page($menu, '', '<span style="display:block; margin:1px 0 1px -5px; padding:0; height:1px; line-height:1px; background:#CCCCCC;"></span>', 'create_users', '#');
269
270 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_trk_ops_page', TRUE, get_defined_vars()))
271 add_submenu_page($menu, 's2Member API / Tracking', 'API / Tracking', 'create_users', 'ws-plugin--s2member-trk-ops', 'c_ws_plugin__s2member_menu_pages::trk_ops_page');
272
273 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_els_ops_page', TRUE, get_defined_vars()))
274 add_submenu_page($menu, 's2Member API / List Servers', 'API / List Servers', 'create_users', 'ws-plugin--s2member-els-ops', 'c_ws_plugin__s2member_menu_pages::els_ops_page');
275
276 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_api_ops_page', TRUE, get_defined_vars()))
277 add_submenu_page($menu, 's2Member API / Notifications', 'API / Notifications', 'create_users', 'ws-plugin--s2member-api-ops', 'c_ws_plugin__s2member_menu_pages::api_ops_page');
278
279 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_scripting_page', TRUE, get_defined_vars()))
280 add_submenu_page($menu, 's2Member API / Scripting', 'API / Scripting', 'create_users', 'ws-plugin--s2member-scripting', 'c_ws_plugin__s2member_menu_pages::scripting_page');
281
282 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_divider_4', TRUE, get_defined_vars()))
283 add_submenu_page($menu, '', '<span style="display:block; margin:1px 0 1px -5px; padding:0; height:1px; line-height:1px; background:#CCCCCC;"></span>', 'create_users', '#');
284
285 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_integrations_page', (!is_multisite() || !c_ws_plugin__s2member_utils_conds::is_multisite_farm() || is_main_site()), get_defined_vars()))
286 add_submenu_page($menu, 's2Member / Other Integrations', 'Other Integrations', 'create_users', 'ws-plugin--s2member-integrations', 'c_ws_plugin__s2member_menu_pages::integrations_page');
287
288 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_divider_5', TRUE, get_defined_vars()))
289 add_submenu_page($menu, '', '<span style="display:block; margin:1px 0 1px -5px; padding:0; height:1px; line-height:1px; background:#CCCCCC;"></span>', 'create_users', '#');
290
291 if(apply_filters('ws_plugin__s2member_during_add_admin_options_add_logs_page', (!is_multisite() || !c_ws_plugin__s2member_utils_conds::is_multisite_farm() || is_main_site()), get_defined_vars()))
292 add_submenu_page($menu, 's2Member Logs', 'Log Files (Debug)', 'create_users', 'ws-plugin--s2member-logs', 'c_ws_plugin__s2member_menu_pages::logs_page');
293
294 do_action('ws_plugin__s2member_during_add_admin_options_additional_pages', get_defined_vars());
295 }
296 do_action('ws_plugin__s2member_after_add_admin_options', get_defined_vars());
297 }
298
299 /**
300 * Adds network option menus / sub-menus.
301 *
302 * @package s2Member\Menu_Pages
303 * @since 3.5
304 *
305 * @attaches-to ``add_action('network_admin_menu');``
306 */
307 public static function add_network_admin_options()
308 {
309 do_action('ws_plugin__s2member_before_add_network_admin_options', get_defined_vars());
310
311 if(apply_filters('ws_plugin__s2member_during_add_network_admin_options_create_menu_items', TRUE, get_defined_vars()))
312 {
313 if((is_multisite() && c_ws_plugin__s2member_utils_conds::is_multisite_farm() && !is_main_site()) || apply_filters('ws_plugin__s2member_during_add_network_admin_options_clear_right_side', FALSE, get_defined_vars()))
314 $GLOBALS['WS_PLUGIN__']['s2member']['c']['menu_pages'] = array(); // Clear right side.
315
316 $menu = 'ws-plugin--s2member-mms-ops'; // Used below for nesting additional sub-menu pages.
317
318 add_menu_page('s2Member', 's2Member', 'create_users', $menu, 'c_ws_plugin__s2member_menu_pages::mms_ops_page', $GLOBALS['WS_PLUGIN__']['s2member']['c']['dir_url'].'/src/images/brand-favicon.png');
319
320 add_submenu_page($menu, 's2Member Multisite (Configuration)', 'Multisite (Config)', 'create_users', 'ws-plugin--s2member-mms-ops', 'c_ws_plugin__s2member_menu_pages::mms_ops_page');
321
322 do_action('ws_plugin__s2member_during_add_network_admin_options_additional_pages', get_defined_vars());
323 }
324 do_action('ws_plugin__s2member_after_add_network_admin_options', get_defined_vars());
325 }
326
327 /**
328 * A sort of callback function to add the settings link.
329 *
330 * @package s2Member\Menu_Pages
331 * @since 3.5
332 *
333 * @attaches-to ``add_filter('plugin_action_links');``
334 *
335 * @param array $actions Expects an existing array of actions links, passed in by the Filter.
336 * @param string $plugin_file Expects path to a plugin file. We need to test against this for s2Member.
337 *
338 * @return array An array of links, Filtered by this routine.
339 */
340 public static function _add_settings_link($actions = array(), $plugin_file = '')
341 {
342 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
343 do_action('_ws_plugin__s2member_before_add_settings_link', get_defined_vars());
344 unset($__refs, $__v); // Housekeeping.
345
346 if($plugin_file === $GLOBALS['WS_PLUGIN__']['s2member']['c']['plugin_basename'] && is_array($actions))
347 {
348 $settings = '<a href="'.esc_attr(admin_url('/admin.php?page=ws-plugin--s2member-gen-ops')).'">Settings</a>';
349 array_unshift($actions, apply_filters('ws_plugin__s2member_add_settings_link', $settings, get_defined_vars()));
350
351 foreach(array_keys(get_defined_vars()) as $__v) $__refs[$__v] =& $$__v;
352 do_action('_ws_plugin__s2member_during_add_settings_link', get_defined_vars());
353 unset($__refs, $__v); // Housekeeping.
354 }
355 return apply_filters('_ws_plugin__s2member_add_settings_link', $actions, get_defined_vars());
356 }
357
358 /**
359 * Enqueue scripts for administrative menu pages.
360 *
361 * @package s2Member\Menu_Pages
362 * @since 3.5
363 *
364 * @attaches-to ``add_action('admin_print_scripts');``
365 */
366 public static function add_admin_scripts()
367 {
368 do_action('ws_plugin__s2member_before_add_admin_scripts', get_defined_vars());
369
370 if(!empty($_GET['page']) && preg_match('/ws-plugin--s2member-/', $_GET['page']))
371 {
372 wp_enqueue_script('jquery');
373 wp_enqueue_script('thickbox');
374 wp_enqueue_script('media-upload');
375 wp_enqueue_script('jquery-ui-core');
376 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());
377 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());
378 //260928.0402 Use WordPress's registered JSON2 compatibility script instead of shipping a duplicate JSON parser.
379 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());
380
381 do_action('ws_plugin__s2member_during_add_admin_scripts', get_defined_vars());
382 }
383 do_action('ws_plugin__s2member_after_add_admin_scripts', get_defined_vars());
384 }
385
386 /**
387 * Enqueue styles for administrative menu pages.
388 *
389 * @package s2Member\Menu_Pages
390 * @since 3.5
391 *
392 * @attaches-to ``add_action('admin_print_styles');``
393 */
394 public static function add_admin_styles()
395 {
396 do_action('ws_plugin__s2member_before_add_admin_styles', get_defined_vars());
397
398 if(!empty($_GET['page']) && preg_match('/ws-plugin--s2member-/', $_GET['page']))
399 {
400 wp_enqueue_style('thickbox');
401 wp_enqueue_style('ws-plugin--s2member-menu-pages', admin_url('admin.php?ws_plugin__s2member_menu_pages_css='.urlencode(mt_rand()), is_ssl() ? 'https' : 'http'), array('thickbox'), c_ws_plugin__s2member_utilities::ver_checksum(), 'all');
402
403 do_action('ws_plugin__s2member_during_add_admin_styles', get_defined_vars());
404 }
405 do_action('ws_plugin__s2member_after_add_admin_styles', get_defined_vars());
406 }
407
408 /**
409 * Handles log file downloads.
410 *
411 * @package s2Member\Menu_Pages
412 * @since 120310
413 */
414 public static function log_file_downloader()
415 {
416 if(!current_user_can('create_users')) return;
417 if(is_multisite() && c_ws_plugin__s2member_utils_conds::is_multisite_farm() && !is_main_site())
418 return; // We do NOT provide this functionality on Child Blogs of a Blog Farm Network.
419
420 if(!empty($_GET['ws_plugin__s2member_download_log_file']) && is_string($log_file = $_GET['ws_plugin__s2member_download_log_file']) && strpos($log_file, '..') === FALSE && strpos(basename($log_file), '.') !== 0)
421 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'))
422 {
423 $logs_dir = $GLOBALS['WS_PLUGIN__']['s2member']['c']['logs_dir'];
424
425 if(file_exists($logs_dir.'/'.$log_file))
426 $log_file_contents = file_get_contents($logs_dir.'/'.$log_file);
427 else $log_file_contents = '';
428
429 @set_time_limit(0);
430 @ini_set('memory_limit', apply_filters('admin_memory_limit', WP_MAX_MEMORY_LIMIT));
431
432 @ini_set('zlib.output_compression', 0);
433 if(function_exists('apache_setenv'))
434 @apache_setenv('no-gzip', '1');
435
436 while(@ob_end_clean()) ;
437
438 status_header(200); // 200 OK status header.
439
440 header('Content-Encoding: none');
441 header('Accept-Ranges: none');
442 header('Content-Type: text/plain; charset=UTF-8');
443 header('Content-Length: '.strlen($log_file_contents));
444 header('Expires: '.gmdate('D, d M Y H:i:s', strtotime('-1 week')).' GMT');
445 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
446 header('Cache-Control: no-cache, must-revalidate, max-age=0');
447 header('Cache-Control: post-check=0, pre-check=0', FALSE);
448 header('Pragma: no-cache');
449
450 header('Content-Disposition: attachment; filename="'.$log_file.'"');
451
452 exit($log_file_contents); // Log file.
453 }
454 }
455
456 /**
457 * Handles log file downloads (in ZIP format).
458 *
459 * @package s2Member\Menu_Pages
460 * @since 120310
461 */
462 public static function logs_zip_downloader()
463 {
464 if(!current_user_can('create_users')) return;
465 if(is_multisite() && c_ws_plugin__s2member_utils_conds::is_multisite_farm() && !is_main_site())
466 return; // We do NOT provide this functionality on Child Blogs of a Blog Farm Network.
467
468 if(!empty($_POST['ws_plugin__s2member_logs_download_zip']) && is_string($nonce = $_POST['ws_plugin__s2member_logs_download_zip']) && wp_verify_nonce($nonce, 'ws-plugin--s2member-logs-download-zip'))
469 {
470 $logs_dir = $GLOBALS['WS_PLUGIN__']['s2member']['c']['logs_dir'];
471 $s2member_logs_zip = $logs_dir.'/'.$_SERVER['HTTP_HOST'].'--s2member-logs.zip';
472
473 if(is_dir($logs_dir)) // Do we have a logs directory?
474 {
475 include_once ABSPATH.'wp-admin/includes/class-pclzip.php';
476
477 if(file_exists($s2member_logs_zip) && is_writable($s2member_logs_zip))
478 unlink($s2member_logs_zip);
479
480 $archive = new PclZip($s2member_logs_zip);
481 $archive->create($logs_dir, PCLZIP_OPT_REMOVE_ALL_PATH);
482 }
483 if(file_exists($s2member_logs_zip))
484 $s2member_logs_zip_size = filesize($s2member_logs_zip);
485 else $s2member_logs_zip_size = 0;
486
487 @set_time_limit(0);
488 @ini_set('memory_limit', apply_filters('admin_memory_limit', WP_MAX_MEMORY_LIMIT));
489
490 @ini_set('zlib.output_compression', 0);
491 if(function_exists('apache_setenv'))
492 @apache_setenv('no-gzip', '1');
493
494 while(@ob_end_clean()) ;
495
496 status_header(200); // 200 OK status header.
497
498 header('Content-Encoding: none');
499 header('Accept-Ranges: none');
500 header('Content-Type: application/zip');
501 header('Content-Length: '.$s2member_logs_zip_size);
502 header('Expires: '.gmdate('D, d M Y H:i:s', strtotime('-1 week')).' GMT');
503 header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
504 header('Cache-Control: no-cache, must-revalidate, max-age=0');
505 header('Cache-Control: post-check=0, pre-check=0', FALSE);
506 header('Pragma: no-cache');
507
508 header('Content-Disposition: attachment; filename="'.basename($s2member_logs_zip).'"');
509
510 if($s2member_logs_zip_size && is_resource($resource = fopen($s2member_logs_zip, 'rb')))
511 {
512 $_bytes_to_read = $s2member_logs_zip_size; // Total bytes we need to read for this file.
513
514 $chunk_size = apply_filters('ws_plugin__s2member_file_downloads_chunk_size', 2097152, get_defined_vars());
515
516 while($_bytes_to_read) // We have bytes to read here.
517 {
518 $_bytes_to_read -= ($_reading = ($_bytes_to_read > $chunk_size) ? $chunk_size : $_bytes_to_read);
519 echo fread($resource, $_reading); // Serve file in chunks (default chunk size is 2MB).
520 flush(); // Flush each chunk to the browser as it is served (avoids high memory consumption).
521 }
522 fclose($resource); // Close file resource handle.
523 unset($_bytes_to_read, $_reading); // Housekeeping.
524 }
525 exit; // Clean exit after serving file.
526 }
527 }
528
529 /**
530 * Archives existing log files and starts fresh with new logs.
531 *
532 * @package s2Member\Menu_Pages
533 * @since 120310
534 */
535 public static function archive_logs_start_fresh()
536 {
537 if(!current_user_can('create_users')) return;
538 if(is_multisite() && c_ws_plugin__s2member_utils_conds::is_multisite_farm() && !is_main_site())
539 return; // We do NOT provide this functionality on Child Blogs of a Blog Farm Network.
540
541 if(!empty($_POST['ws_plugin__s2member_logs_archive_start_fresh']) && is_string($nonce = $_POST['ws_plugin__s2member_logs_archive_start_fresh']) && wp_verify_nonce($nonce, 'ws-plugin--s2member-logs-archive-start-fresh'))
542 {
543 if(is_dir($logs_dir = $GLOBALS['WS_PLUGIN__']['s2member']['c']['logs_dir']))
544 foreach(scandir($logs_dir) as $log_file) // Archive existing log files here.
545 {
546 if(preg_match('/\.log$/', $log_file) && stripos($log_file, '-ARCHIVED-') === FALSE)
547 if(is_file($log_dir_file = $logs_dir.'/'.$log_file) && is_writable($log_dir_file))
548 if(!rename($log_dir_file, preg_replace('/\.log$/i', '', $log_dir_file).'-ARCHIVED-'.date('m-d-Y').'-'.time().'.log'))
549 $error = TRUE;
550 }
551 if(!empty($error))
552 c_ws_plugin__s2member_admin_notices::display_admin_notice('Unknown error when attempting to archive log files. Please check directory permissions.', TRUE);
553 else
554 c_ws_plugin__s2member_admin_notices::display_admin_notice('All log files have been archived successfully.');
555 }
556 }
557
558 /**
559 * Deletes existing log files and starts fresh with new logs.
560 *
561 * @package s2Member\Menu_Pages
562 * @since 120312
563 */
564 public static function delete_logs_start_fresh()
565 {
566 if(!current_user_can('create_users')) return;
567 if(is_multisite() && c_ws_plugin__s2member_utils_conds::is_multisite_farm() && !is_main_site())
568 return; // We do NOT provide this functionality on Child Blogs of a Blog Farm Network.
569
570 if(!empty($_POST['ws_plugin__s2member_logs_delete_start_fresh']) && is_string($nonce = $_POST['ws_plugin__s2member_logs_delete_start_fresh']) && wp_verify_nonce($nonce, 'ws-plugin--s2member-logs-delete-start-fresh'))
571 {
572 if(is_dir($logs_dir = $GLOBALS['WS_PLUGIN__']['s2member']['c']['logs_dir']))
573 foreach(scandir($logs_dir) as $log_file) // Delete existing log files here.
574 {
575 if(preg_match('/\.log$/', $log_file))
576 if(is_file($log_dir_file = $logs_dir.'/'.$log_file) && is_writable($log_dir_file))
577 if(!unlink($log_dir_file)) $error = TRUE;
578 }
579 if(!empty($error))
580 c_ws_plugin__s2member_admin_notices::display_admin_notice('Unknown error when attempting to delete log files. Please check directory permissions.', TRUE);
581 else
582 c_ws_plugin__s2member_admin_notices::display_admin_notice('All log files have been deleted successfully.');
583 }
584 }
585
586 /**
587 * Builds and handles the Getting Started page.
588 *
589 * @package s2Member\Menu_Pages
590 * @since 3.5
591 */
592 public static function start_page()
593 {
594 do_action('ws_plugin__s2member_before_start_page', get_defined_vars());
595
596 include_once dirname(dirname(__FILE__)).'/menu-pages/start.inc.php';
597
598 do_action('ws_plugin__s2member_after_start_page', get_defined_vars());
599 }
600
601 /**
602 * Builds and handles the Getting Help page.
603 *
604 * @package s2Member\Menu_Pages
605 * @since 151218
606 */
607 public static function help_page()
608 {
609 do_action('ws_plugin__s2member_before_help_page', get_defined_vars());
610
611 include_once dirname(dirname(__FILE__)).'/menu-pages/help.inc.php';
612
613 do_action('ws_plugin__s2member_after_help_page', get_defined_vars());
614 }
615
616 /**
617 * Builds and handles the Main Multisite Options page.
618 *
619 * @package s2Member\Menu_Pages
620 * @since 3.5
621 */
622 public static function mms_ops_page()
623 {
624 do_action('ws_plugin__s2member_before_mms_ops_page', get_defined_vars());
625
626 include_once dirname(dirname(__FILE__)).'/menu-pages/mms-ops.inc.php';
627
628 do_action('ws_plugin__s2member_after_mms_ops_page', get_defined_vars());
629 }
630
631 /**
632 * Builds and handles the General Options page.
633 *
634 * @package s2Member\Menu_Pages
635 * @since 3.5
636 */
637 public static function gen_ops_page()
638 {
639 do_action('ws_plugin__s2member_before_gen_ops_page', get_defined_vars());
640
641 c_ws_plugin__s2member_menu_pages::update_all_options();
642
643 include_once dirname(dirname(__FILE__)).'/menu-pages/gen-ops.inc.php';
644
645 do_action('ws_plugin__s2member_after_gen_ops_page', get_defined_vars());
646 }
647
648 /**
649 * Builds and handles the Restriction Options page.
650 *
651 * @package s2Member\Menu_Pages
652 * @since 3.5
653 */
654 public static function res_ops_page()
655 {
656 do_action('ws_plugin__s2member_before_res_ops_page', get_defined_vars());
657
658 c_ws_plugin__s2member_menu_pages::update_all_options();
659
660 include_once dirname(dirname(__FILE__)).'/menu-pages/res-ops.inc.php';
661
662 do_action('ws_plugin__s2member_after_res_ops_page', get_defined_vars());
663 }
664
665 /**
666 * Builds and handles the Paypal Options page.
667 *
668 * @package s2Member\Menu_Pages
669 * @since 3.5
670 */
671 public static function paypal_ops_page()
672 {
673 do_action('ws_plugin__s2member_before_paypal_ops_page', get_defined_vars());
674
675 c_ws_plugin__s2member_menu_pages::update_all_options();
676
677 $logs_dir = $GLOBALS['WS_PLUGIN__']['s2member']['c']['logs_dir'];
678
679 if(!is_dir($logs_dir) && is_writable(dirname(c_ws_plugin__s2member_utils_dirs::strip_dir_app_data($logs_dir))))
680 mkdir($logs_dir, 0777, TRUE).clearstatcache();
681
682 $htaccess = $GLOBALS['WS_PLUGIN__']['s2member']['c']['logs_dir'].'/.htaccess';
683 $htaccess_contents = trim(c_ws_plugin__s2member_utilities::evl(file_get_contents($GLOBALS['WS_PLUGIN__']['s2member']['c']['logs_dir_htaccess'])));
684
685 if(is_dir($logs_dir) && is_writable($logs_dir) && !file_exists($htaccess))
686 file_put_contents($htaccess, $htaccess_contents).clearstatcache();
687
688 if($GLOBALS['WS_PLUGIN__']['s2member']['o']['gateway_debug_logs']) // Logging enabled?
689 {
690 if(!is_dir($logs_dir)) // If the security-enabled logs directory does not exist yet.
691 c_ws_plugin__s2member_admin_notices::display_admin_notice('The security-enabled logs directory (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($logs_dir)).'</code>) does not exist. Please create this directory manually &amp; make it writable (chmod 777).', TRUE);
692
693 else if(!is_writable($logs_dir)) // If the logs directory is not writable yet.
694 c_ws_plugin__s2member_admin_notices::display_admin_notice('Permissions error. The security-enabled logs directory (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($logs_dir)).'</code>) is not writable. Please make this directory writable (chmod 777).', TRUE);
695
696 if(!file_exists($htaccess)) // If the .htaccess file has not been created yet.
697 c_ws_plugin__s2member_admin_notices::display_admin_notice('The .htaccess protection file (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($htaccess)).'</code>) does not exist. Please create this file manually. Inside your .htaccess file, add this:<br /><pre>'.esc_html($htaccess_contents).'</pre>', TRUE);
698
699 else if(!preg_match('/deny from all/i', file_get_contents($htaccess))) // Else if the .htaccess file does not offer the required protection.
700 c_ws_plugin__s2member_admin_notices::display_admin_notice('Unprotected. The .htaccess protection file (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($htaccess)).'</code>) does not contain <code>deny from all</code>. Inside your .htaccess file, add this:<br /><pre>'.esc_html($htaccess_contents).'</pre>', TRUE);
701 }
702 include_once dirname(dirname(__FILE__)).'/menu-pages/paypal-ops.inc.php';
703
704 do_action('ws_plugin__s2member_after_paypal_ops_page', get_defined_vars());
705 }
706
707 /**
708 * Builds and handles the Download Options page.
709 *
710 * @package s2Member\Menu_Pages
711 * @since 3.5
712 */
713 public static function down_ops_page()
714 {
715 do_action('ws_plugin__s2member_before_down_ops_page', get_defined_vars());
716
717 c_ws_plugin__s2member_menu_pages::update_all_options();
718
719 if(!empty($_REQUEST['ws_plugin__s2member_cf_options_reset'])
720 && wp_verify_nonce($_REQUEST['ws_plugin__s2member_cf_options_reset'], 'ws-plugin--s2member-cf-options-reset')
721 )
722 {
723 c_ws_plugin__s2member_files_in::reset_aws_cf_config_values(); // A full CloudFront reset.
724 c_ws_plugin__s2member_admin_notices::display_admin_notice('Amazon CloudFront configuration reset successfully.');
725 }
726 $files_dir = $GLOBALS['WS_PLUGIN__']['s2member']['c']['files_dir'];
727
728 $htaccess = $GLOBALS['WS_PLUGIN__']['s2member']['c']['files_dir'].'/.htaccess';
729 $htaccess_contents = trim(c_ws_plugin__s2member_utilities::evl(file_get_contents($GLOBALS['WS_PLUGIN__']['s2member']['c']['files_dir_htaccess'])));
730
731 $no_gzip_htaccess = ABSPATH.'.htaccess'; // Always located in the absolute root path for WordPress.
732 $no_gzip_htaccess_contents = trim(c_ws_plugin__s2member_utilities::evl(file_get_contents($GLOBALS['WS_PLUGIN__']['s2member']['c']['files_no_gzip_htaccess'])));
733
734 if(!c_ws_plugin__s2member_files::no_gzip_rules_in_root_htaccess()) // If s2Member's GZIP exclusions do NOT yet exist in the root `.htaccess` file.
735 c_ws_plugin__s2member_files::write_no_gzip_into_root_htaccess().clearstatcache(); // Handle the root `.htaccess` file now.
736
737 if(!is_dir($files_dir) && is_writable(dirname(c_ws_plugin__s2member_utils_dirs::strip_dir_app_data($files_dir))))
738 mkdir($files_dir, 0777, TRUE).clearstatcache(); // Create this directory structure now.
739
740 if(is_dir($files_dir) && is_writable($files_dir) && !file_exists($htaccess)) // This file does NOT exist yet?
741 file_put_contents($htaccess, $htaccess_contents).clearstatcache(); // Create the `.htaccess` file now.
742
743 if(!c_ws_plugin__s2member_files::no_gzip_rules_in_root_htaccess()) // If s2Member's GZIP exclusions do NOT yet exist in the root `.htaccess` file.
744 c_ws_plugin__s2member_admin_notices::display_admin_notice('Possible GZIP conflict on server. Unable to write GZIP exclusions into root .htaccess file (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($no_gzip_htaccess)).'</code>). Please read the panel below: <strong>Preventing GZIP Conflicts</strong>, and add this section yourself:<br /><pre>'.esc_html($no_gzip_htaccess_contents).'</pre>', TRUE);
745
746 if(!is_dir($files_dir)) // If the security-enabled files directory does not exist yet.
747 c_ws_plugin__s2member_admin_notices::display_admin_notice('The security-enabled files directory (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($files_dir)).'</code>) does not exist. Please create this directory manually.', TRUE);
748
749 if(!file_exists($htaccess)) // If the `.htaccess` file has not been created yet.
750 c_ws_plugin__s2member_admin_notices::display_admin_notice('The .htaccess protection file (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($htaccess)).'</code>) does not exist. Please create this file manually. Inside your .htaccess file, add this:<br /><pre>'.esc_html($htaccess_contents).'</pre>', TRUE);
751
752 else if(!preg_match('/deny from all/i', file_get_contents($htaccess))) // Else if the `.htaccess` file does not offer the required protection.
753 c_ws_plugin__s2member_admin_notices::display_admin_notice('Unprotected. The .htaccess protection file (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($htaccess)).'</code>) does not contain <code>deny from all</code>. Inside your .htaccess file, add this:<br /><pre>'.esc_html($htaccess_contents).'</pre>', TRUE);
754
755 if(!empty($_POST['ws_plugin__s2member_amazon_cf_files_auto_configure_distros']) && ($nonce = $_POST['ws_plugin__s2member_amazon_cf_files_auto_configure_distros']) && wp_verify_nonce($nonce, 'ws-plugin--s2member-amazon-cf-files-auto-configure-distros'))
756 if(($amazon_cf_auto_configure_distros = c_ws_plugin__s2member_files_in::amazon_cf_auto_configure_distros()) && $amazon_cf_auto_configure_distros['success'])
757 c_ws_plugin__s2member_admin_notices::display_admin_notice('Amazon CloudFront Distributions auto-configured successfully. Please allow 30 minutes for initial propagation. <strong>Tip:</strong> If you try to stream over the RTMP protocol using something like the <code>[s2Stream /]</code> shortcode, and you keep getting an "ID Not Found" error while using JW Player; please note that it can <em>sometimes</em> take a full 24 hours for RTMP (i.e., streaming distributions) to begin working properly. This is because there are a few initialization routines that must complete on the AWS side when you first integrate with CloudFront. Please be patient.'.(($GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_cf_files_distro_downloads_cname']) ? '<br /><em>Downloads Distribution CNAME: <code>'.esc_html($GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_cf_files_distro_downloads_cname']).' &mdash;&raquo; '.esc_html($GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_cf_files_distro_downloads_dname']).'</code></em>' : '').(($GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_cf_files_distro_streaming_cname']) ? '<br /><em>Streaming Distribution CNAME: <code>'.esc_html($GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_cf_files_distro_streaming_cname']).' &mdash;&raquo; '.esc_html($GLOBALS['WS_PLUGIN__']['s2member']['o']['amazon_cf_files_distro_streaming_dname']).'</code></em>' : ''));
758 else // Else there was an error. We need to report this back to the site owner so they can understand what's going on.
759 (c_ws_plugin__s2member_menu_pages::$pre_display_errors['cf_files_auto_configure_distros'] = TRUE).c_ws_plugin__s2member_admin_notices::display_admin_notice('Unable to auto-configure Amazon CloudFront Distributions.<br />Error code: <code>'.esc_html($amazon_cf_auto_configure_distros['code']).'</code>. Error Message: <code>'.esc_html($amazon_cf_auto_configure_distros['message']).'</code>', TRUE);
760
761 if(!empty($_POST['ws_plugin__s2member_amazon_s3_files_auto_configure_acls']) && ($nonce = $_POST['ws_plugin__s2member_amazon_s3_files_auto_configure_acls']) && wp_verify_nonce($nonce, 'ws-plugin--s2member-amazon-s3-files-auto-configure-acls'))
762 if(($amazon_s3_auto_configure_acls = c_ws_plugin__s2member_files_in::amazon_s3_auto_configure_acls()) && $amazon_s3_auto_configure_acls['success'])
763 c_ws_plugin__s2member_admin_notices::display_admin_notice('Amazon S3 ACLs auto-configured successfully.');
764 else // Else there was an error. We need to report this back to the site owner so they can understand what's going on.
765 (c_ws_plugin__s2member_menu_pages::$pre_display_errors['s3_files_auto_configure_acls'] = TRUE).c_ws_plugin__s2member_admin_notices::display_admin_notice('Unable to auto-configure Amazon S3 ACLs.<br />Error code: <code>'.esc_html($amazon_s3_auto_configure_acls['code']).'</code>. Error Message: <code>'.esc_html($amazon_s3_auto_configure_acls['message']).'</code>', TRUE);
766
767 include_once dirname(dirname(__FILE__)).'/menu-pages/down-ops.inc.php';
768
769 do_action('ws_plugin__s2member_after_down_ops_page', get_defined_vars());
770 }
771
772 /**
773 * Builds and handles the API Tracking options page.
774 *
775 * @package s2Member\Menu_Pages
776 * @since 3.5
777 */
778 public static function trk_ops_page()
779 {
780 do_action('ws_plugin__s2member_before_trk_ops_page', get_defined_vars());
781
782 c_ws_plugin__s2member_menu_pages::update_all_options();
783
784 include_once dirname(dirname(__FILE__)).'/menu-pages/trk-ops.inc.php';
785
786 do_action('ws_plugin__s2member_after_trk_ops_page', get_defined_vars());
787 }
788
789 /**
790 * Builds and handles the API List Server options page.
791 *
792 * @package s2Member\Menu_Pages
793 * @since 3.5
794 */
795 public static function els_ops_page()
796 {
797 do_action('ws_plugin__s2member_before_els_ops_page', get_defined_vars());
798
799 c_ws_plugin__s2member_menu_pages::update_all_options();
800
801 include_once dirname(dirname(__FILE__)).'/menu-pages/els-ops.inc.php';
802
803 do_action('ws_plugin__s2member_after_els_ops_page', get_defined_vars());
804 }
805
806 /**
807 * Builds and handles the API Notifications page.
808 *
809 * @package s2Member\Menu_Pages
810 * @since 3.5
811 */
812 public static function api_ops_page()
813 {
814 do_action('ws_plugin__s2member_before_api_ops_page', get_defined_vars());
815
816 c_ws_plugin__s2member_menu_pages::update_all_options();
817
818 include_once dirname(dirname(__FILE__)).'/menu-pages/api-ops.inc.php';
819
820 do_action('ws_plugin__s2member_after_api_ops_page', get_defined_vars());
821 }
822
823 /**
824 * Builds and handles the PayPal Button Generator page.
825 *
826 * @package s2Member\Menu_Pages
827 * @since 3.5
828 */
829 public static function paypal_buttons_page()
830 {
831 do_action('ws_plugin__s2member_before_paypal_buttons_page', get_defined_vars());
832
833 if(!$GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_business'] || !$GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_merchant_id'] || !$GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_api_username'] || !$GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_api_password'] || !$GLOBALS['WS_PLUGIN__']['s2member']['o']['paypal_api_signature'])
834 c_ws_plugin__s2member_admin_notices::display_admin_notice('Please configure <strong>s2Member → PayPal Options</strong> first. Once all of your PayPal Options are configured; including your Email Address, Merchant ID, API Username, Password, and Signature; return to this page &amp; generate your PayPal Button(s).', TRUE);
835
836 include_once dirname(dirname(__FILE__)).'/menu-pages/paypal-buttons.inc.php';
837
838 do_action('ws_plugin__s2member_after_paypal_buttons_page', get_defined_vars());
839 }
840
841 /**
842 * Builds and handles the API Scripting page.
843 *
844 * @package s2Member\Menu_Pages
845 * @since 3.5
846 */
847 public static function scripting_page()
848 {
849 do_action('ws_plugin__s2member_before_scripting_page', get_defined_vars());
850
851 c_ws_plugin__s2member_menu_pages::update_all_options();
852
853 include_once dirname(dirname(__FILE__)).'/menu-pages/scripting.inc.php';
854
855 do_action('ws_plugin__s2member_after_scripting_page', get_defined_vars());
856 }
857
858 /**
859 * Builds and handles the Integrations page.
860 *
861 * @package s2Member\Menu_Pages
862 * @since 3.5
863 */
864 public static function integrations_page()
865 {
866 do_action('ws_plugin__s2member_before_integrations_page', get_defined_vars());
867
868 include_once dirname(dirname(__FILE__)).'/menu-pages/integrations.inc.php';
869
870 do_action('ws_plugin__s2member_after_integrations_page', get_defined_vars());
871 }
872
873 /**
874 * Builds and handles the Logs page.
875 *
876 * @package s2Member\Menu_Pages
877 * @since 120310
878 */
879 public static function logs_page()
880 {
881 do_action('ws_plugin__s2member_before_logs_page', get_defined_vars());
882
883 c_ws_plugin__s2member_menu_pages::update_all_options();
884 c_ws_plugin__s2member_menu_pages::archive_logs_start_fresh();
885 c_ws_plugin__s2member_menu_pages::delete_logs_start_fresh();
886
887 $logs_dir = $GLOBALS['WS_PLUGIN__']['s2member']['c']['logs_dir'];
888
889 if(!is_dir($logs_dir) && is_writable(dirname(c_ws_plugin__s2member_utils_dirs::strip_dir_app_data($logs_dir))))
890 mkdir($logs_dir, 0777, TRUE).clearstatcache();
891
892 $htaccess = $GLOBALS['WS_PLUGIN__']['s2member']['c']['logs_dir'].'/.htaccess';
893 $htaccess_contents = trim(c_ws_plugin__s2member_utilities::evl(file_get_contents($GLOBALS['WS_PLUGIN__']['s2member']['c']['logs_dir_htaccess'])));
894
895 if(is_dir($logs_dir) && is_writable($logs_dir) && !file_exists($htaccess))
896 file_put_contents($htaccess, $htaccess_contents).clearstatcache();
897
898 if(!is_dir($logs_dir)) // If the security-enabled logs directory does not exist yet.
899 c_ws_plugin__s2member_admin_notices::display_admin_notice('The security-enabled logs directory (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($logs_dir)).'</code>) does not exist. Please create this directory manually &amp; make it writable (chmod 777).', TRUE);
900
901 else if(!is_writable($logs_dir)) // If the logs directory is not writable yet.
902 c_ws_plugin__s2member_admin_notices::display_admin_notice('Permissions error. The security-enabled logs directory (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($logs_dir)).'</code>) is not writable. Please make this directory writable (chmod 777).', TRUE);
903
904 if(!file_exists($htaccess)) // If the .htaccess file has not been created yet.
905 c_ws_plugin__s2member_admin_notices::display_admin_notice('The .htaccess protection file (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($htaccess)).'</code>) does not exist. Please create this file manually. Inside your .htaccess file, add this:<br /><pre>'.esc_html($htaccess_contents).'</pre>', TRUE);
906
907 else if(!preg_match('/deny from all/i', file_get_contents($htaccess))) // Else if the .htaccess file does not offer the required protection.
908 c_ws_plugin__s2member_admin_notices::display_admin_notice('Unprotected. The .htaccess protection file (<code>'.esc_html(c_ws_plugin__s2member_utils_dirs::doc_root_path($htaccess)).'</code>) does not contain <code>deny from all</code>. Inside your .htaccess file, add this:<br /><pre>'.esc_html($htaccess_contents).'</pre>', TRUE);
909
910 if(!$GLOBALS['WS_PLUGIN__']['s2member']['o']['gateway_debug_logs']) // Logging disabled?
911 c_ws_plugin__s2member_admin_notices::display_admin_notice('Logging is currently disabled by your configuration.');
912
913 include_once dirname(dirname(__FILE__)).'/menu-pages/logs.inc.php';
914
915 do_action('ws_plugin__s2member_after_logs_page', get_defined_vars());
916 }
917
918 /**
919 * Registers the Get Help panel across admin pages.
920 *
921 * @package s2Member\Menu_Pages
922 * @since 250824
923 *
924 * @attaches-to ``add_action('admin_menu');``
925 */
926 public static function get_help_panel() {
927 $hooks = array(
928 // Framework
929 'ws_plugin__s2member_during_api_ops_page_before_left_sections_form',
930 'ws_plugin__s2member_during_down_ops_page_before_left_sections_form',
931 'ws_plugin__s2member_during_els_ops_page_before_left_sections_form',
932 'ws_plugin__s2member_during_gen_ops_page_before_left_sections_form',
933 'ws_plugin__s2member_during_help_page_before_left_sections',
934 'ws_plugin__s2member_during_integrations_page_before_left_sections',
935 'ws_plugin__s2member_during_logs_page_before_left_sections',
936 'ws_plugin__s2member_during_paypal_buttons_page_before_left_sections',
937 'ws_plugin__s2member_during_paypal_ops_page_before_left_sections_form',
938 'ws_plugin__s2member_during_res_ops_page_before_left_sections_form',
939 'ws_plugin__s2member_during_scripting_page_before_left_sections',
940 'ws_plugin__s2member_during_start_page_before_left_sections',
941 'ws_plugin__s2member_during_trk_ops_page_before_left_sections_form',
942 // Pro
943 'ws_plugin__s2member_during_authnet_forms_page_before_left_sections',
944 'ws_plugin__s2member_during_authnet_ops_page_before_left_sections_form',
945 'ws_plugin__s2member_during_clickbank_buttons_page_before_left_sections',
946 'ws_plugin__s2member_during_clickbank_ops_page_before_left_sections_form',
947 'ws_plugin__s2member_during_coupon_codes_page_before_left_sections_form',
948 'ws_plugin__s2member_during_import_export_page_before_left_sections',
949 'ws_plugin__s2member_during_mms_ops_page_before_left_sections_form',
950 'ws_plugin__s2member_during_other_gateways_page_before_left_sections_form',
951 'ws_plugin__s2member_during_paypal_forms_page_before_left_sections',
952 'ws_plugin__s2member_during_stripe_forms_page_before_left_sections',
953 'ws_plugin__s2member_during_stripe_ops_page_before_left_sections_form',
954 );
955
956 foreach ($hooks as $hook) {
957 add_action($hook, 'c_ws_plugin__s2member_menu_pages::get_help_panel_load', 1, 1);
958 }
959 }
960
961 /**
962 * Renders the Get Help panel (computes $notice/$draft and includes template).
963 *
964 * @package s2Member\Menu_Pages
965 * @since 250824
966 *
967 * @param array $vars Optional vars passed by the hook (unused).
968 */
969 public static function get_help_panel_load($vars = array()) {
970 if (!current_user_can('manage_options')) return;
971
972 // Build notice (if POST).
973 $notice = array('type' => '', 'text' => '');
974 if (!empty($_POST['s2m_help_nonce']) && isset($_POST['s2m_help_submit']) && wp_verify_nonce($_POST['s2m_help_nonce'], 's2member_get_help')) {
975 $result = c_ws_plugin__s2member_menu_pages::get_help_mail();
976 if (!empty($result['ok'])) {
977 $notice = array('type' => 'success', 'text' => __('Thanks — your message was sent. You’ll receive a copy by email.', 's2member'));
978 $_POST = array(); // clear fields after success so defaults repopulate
979 } else {
980 $msg = isset($result['msg']) ? $result['msg'] : __('Sorry, your message could not be sent. Please try again.', 's2member');
981 $notice = array('type' => 'error', 'text' => $msg);
982 }
983 }
984
985 // Build draft (preserve POST on error; else defaults from current user).
986 $current = wp_get_current_user();
987 $draft = array(
988 'name' => is_object($current) ? trim($current->display_name) : '',
989 'email' => is_object($current) ? trim($current->user_email) : '',
990 'subject' => '',
991 'details' => '',
992 'meta' => home_url()."\n"
993 .'PHP v'.PHP_VERSION
994 .', WordPress v'.(isset($GLOBALS['wp_version']) ? $GLOBALS['wp_version'] : '')
995 .', s2Member v'.(defined('WS_PLUGIN__S2MEMBER_VERSION') ? WS_PLUGIN__S2MEMBER_VERSION : '')
996 .(defined('WS_PLUGIN__S2MEMBER_PRO_VERSION') ? ', Pro v'.WS_PLUGIN__S2MEMBER_PRO_VERSION : ''),
997 );
998
999 if (isset($_POST['name'])) $draft['name'] = wp_strip_all_tags(trim(wp_unslash($_POST['name'])));
1000 if (isset($_POST['email'])) $draft['email'] = sanitize_email(trim(wp_unslash($_POST['email'])));
1001 if (isset($_POST['subject'])) $draft['subject'] = sanitize_text_field(trim(wp_unslash($_POST['subject'])));
1002 if (isset($_POST['details'])) $draft['details'] = trim(wp_unslash($_POST['details']));
1003 if (isset($_POST['meta'])) $draft['meta'] = trim(wp_unslash($_POST['meta']));
1004
1005 // Include template (variables $draft and $notice are in scope for the include).
1006 include_once dirname(dirname(__FILE__)).'/menu-pages/get-help-panel.inc.php';
1007 }
1008
1009 /**
1010 * Sends the Get Help email (simple contact form, Bcc user).
1011 *
1012 * @package s2Member\Menu_Pages
1013 * @since 250824
1014 *
1015 * @return array Array like ['ok' => bool, 'msg' => string].
1016 */
1017 public static function get_help_mail() {
1018 if (!current_user_can('manage_options')) {
1019 return array('ok' => false, 'msg' => __('You do not have permission to send support requests from this page.', 's2member'));
1020 }
1021
1022 $name = isset($_POST['name']) ? wp_strip_all_tags(trim(wp_unslash($_POST['name']))) : '';
1023 $email = isset($_POST['email']) ? sanitize_email(trim(wp_unslash($_POST['email']))) : '';
1024 $subject_in = isset($_POST['subject']) ? sanitize_text_field(trim(wp_unslash($_POST['subject']))) : '';
1025 $details = isset($_POST['details']) ? trim(wp_unslash($_POST['details'])) : '';
1026 $meta = isset($_POST['meta']) ? trim(wp_unslash($_POST['meta'])) : '';
1027
1028 $email = str_replace(array("\r", "\n"), '', $email); // header-injection safety
1029 if (empty($email) || !is_email($email)) {
1030 return array('ok' => false, 'msg' => __('Please provide a valid email address.', 's2member'));
1031 }
1032 if ($details === '') {
1033 return array('ok' => false, 'msg' => __('Please describe how I can help.', 's2member'));
1034 }
1035
1036 // Cap length and normalize body fields
1037 $max_len = 100000;
1038 if (strlen($details) > $max_len) $details = substr($details, 0, $max_len);
1039 if (strlen($meta) > $max_len) $meta = substr($meta, 0, $max_len);
1040
1041 $details = str_replace(array("\r\n", "\r"), "\n", $details);
1042 $meta = str_replace(array("\r\n", "\r"), "\n", $meta);
1043 $message = $details."\n\n".$meta;
1044
1045 // Subject with fallback/prefix
1046 $subject = (string) apply_filters('ws_plugin__s2member_get_help_subject', $subject_in);
1047 $subject = str_replace(array("\r\n", "\r", "\n"), ' ', trim($subject));
1048 $subject = ($subject === '') ? __('s2Member help request', 's2member') : 's2Member help: '.$subject;
1049
1050 // Sanitize display names
1051 $name_hdr = str_replace(array("\r\n", "\r", "\n"), ' ', (string) $name);
1052 $name_hdr = str_replace('"', "'", $name_hdr);
1053
1054 $sitename = c_ws_plugin__s2member_utils_urls::parse_url(home_url(), PHP_URL_HOST);
1055 $sitename = is_string($sitename) ? strtolower($sitename) : '';
1056 if (strpos($sitename, 'www.') === 0) $sitename = substr($sitename, 4);
1057
1058 $default_from_email = 'wordpress@'.$sitename;
1059 $default_from_name = ($name_hdr !== '' ? $name_hdr : get_bloginfo('name'));
1060
1061 // Honor WP and s2 filters, then cleanup
1062 $from_email = (string) apply_filters('wp_mail_from', $default_from_email);
1063 $from_name = (string) apply_filters('wp_mail_from_name', $default_from_name);
1064
1065 $from_email = (string) apply_filters('ws_plugin__s2member_get_help_from_email', $from_email);
1066 $from_name = (string) apply_filters('ws_plugin__s2member_get_help_from_name', $from_name);
1067
1068 $from_email = str_replace(array("\r", "\n"), '', $from_email);
1069 $from_name = str_replace(array("\r\n", "\r", "\n"), ' ', $from_name);
1070 $from_name = str_replace('"', "'", $from_name);
1071
1072 // Build headers
1073 $from_line = '"'.$from_name.'" <'.$from_email.'>';
1074 $user_line = ($name_hdr !== '' ? '"'.$name_hdr.'" ' : '').'<'.$email.'>';
1075
1076 $headers = array('Content-Type: text/plain; charset=UTF-8');
1077 $headers[] = 'From: '.$from_line;
1078 $headers[] = 'Reply-To: '.$user_line;
1079 $headers[] = 'Bcc: '.$user_line;
1080 $headers[] = 'X-s2Member-Help: 1';
1081
1082 $headers = apply_filters('ws_plugin__s2member_get_help_headers', $headers, compact('name','email','subject','details','meta'));
1083
1084 // Recipient
1085 $to = (string) apply_filters('ws_plugin__s2member_get_help_mailbox', '[email protected]');
1086 if (empty($to) || !is_email($to)) {
1087 return array('ok' => false, 'msg' => __('Support mailbox is not configured correctly.', 's2member'));
1088 }
1089
1090 $sent = wp_mail($to, $subject, $message, $headers);
1091
1092 if (!$sent) {
1093 if (defined('WP_DEBUG_LOG') && WP_DEBUG_LOG) {
1094 error_log('s2Member Get Help: wp_mail() returned false. To='.$to.' | Subject='.$subject);
1095 }
1096 return array('ok' => false, 'msg' => __('Email could not be sent (mail server issue). Please try again or contact us directly.', 's2member'));
1097 }
1098 return array('ok' => true, 'msg' => __('Thanks — your message was sent. You’ll receive a copy by email.', 's2member'));
1099 }
1100 }
1101 }
1102