/**
  *	_migration_step
  *
  * @access protected
  * @param int $num_items
  * @throws EE_Error
  * @return int number of items ACTUALLY migrated
  */
 protected function _migration_step($num_items = 1)
 {
     $templates_relative_path = 'modules/gateways/Invoice/lib/templates/';
     $overridden_invoice_body = EEH_Template::locate_template($templates_relative_path . 'invoice_body.template.php', NULL, FALSE, FALSE, TRUE);
     $overridden_receipt_body = EEH_Template::locate_template($templates_relative_path . 'receipt_body.template.php', NULL, FALSE, FALSE, TRUE);
     if ($overridden_invoice_body || $overridden_receipt_body) {
         EE_Error::add_persistent_admin_notice('invoice_overriding_templates', sprintf(__('Note: in this version of Event Espresso, PDF and HTML Invoices and Receipts are now Messages and can be changed just like any other messages; however we noticed you had previously overriden the old default Invoice/Receipt templates. Because of this, your old Invoice/Receipt templates will continue to be used INSTEAD of the new Invoice/Receipt message equivalents (but this will be removed in an upcoming version). We recommend deleting your old Invoice/Receipt templates and using the new messages system. Then modify the new Invoice and Receipt messages\'s content in Messages -> Invoice and Messages -> Receipt.')), TRUE);
     }
     //regardless of whether it worked or not, we ought to continue the migration
     $this->set_completed();
     return 1;
 }
 /**
  * @access private
  * @return EE_Cron_Tasks
  */
 private function __construct()
 {
     do_action('AHEE_log', __CLASS__, __FUNCTION__);
     // verify that WP Cron is enabled
     if (defined('DISABLE_WP_CRON') && DISABLE_WP_CRON && is_admin()) {
         EE_Error::add_persistent_admin_notice('wp_cron_disabled', sprintf(__('Event Espresso has detected that wp_cron is disabled. It\'s important that wp_cron is enabled because Event Espresso depends on wp_cron for critical scheduled tasks.%1$sSince it\'s possible that your site may have an alternative task scheduler set up, we strongly suggest that you inform the website host, or developer that set up the site, about this issue.', 'event_espresso'), '<br />'));
     }
     // UPDATE TRANSACTION WITH PAYMENT
     add_action('AHEE__EE_Cron_Tasks__update_transaction_with_payment_2', array('EE_Cron_Tasks', 'setup_update_for_transaction_with_payment'), 10, 2);
     // FINALIZE ABANDONED TRANSACTIONS
     add_action('AHEE__EE_Cron_Tasks__finalize_abandoned_transactions', array('EE_Cron_Tasks', 'check_for_abandoned_transactions'), 10, 1);
     // CLEAN OUT JUNK TRANSACTIONS AND RELATED DATA
     add_action('AHEE__EE_Cron_Tasks__clean_up_junk_transactions', array('EE_Cron_Tasks', 'clean_out_junk_transactions'));
     // logging
     add_action('AHEE__EE_System__load_core_configuration__complete', array('EE_Cron_Tasks', 'log_scheduled_ee_crons'));
     EE_Registry::instance()->load_lib('Messages_Scheduler');
 }
 /**
  * 	used by EE and EE addons during plugin activation
  *
  * 	@access public
  * 	@static
  * @param string $table_name without the $wpdb->prefix
  * @param string $sql SQL for creating the table (contents between brackets in an SQL create table query)
  * @param string $engine like 'ENGINE=MyISAM' or 'ENGINE=InnoDB'
  * @param boolean $drop_pre_existing_table set to TRUE when you want to make SURE the table is completely empty
  * and new once this function is done (ie, you really do want to CREATE a table, and
  * expect it to be empty once you're done)
  * leave as FALSE when you just want to verify the table exists and matches this definition (and if it
  * HAS data in it you want to leave it be)
  * 	@return void
  * @throws EE_Error if there are database errors
  */
 public static function create_table($table_name, $sql, $engine = 'ENGINE=MyISAM ', $drop_pre_existing_table = false)
 {
     if (apply_filters('FHEE__EEH_Activation__create_table__short_circuit', FALSE, $table_name, $sql)) {
         return;
     }
     do_action('AHEE_log', __FILE__, __FUNCTION__, '');
     if (!function_exists('dbDelta')) {
         require_once ABSPATH . 'wp-admin/includes/upgrade.php';
     }
     /** @var WPDB $wpdb */
     global $wpdb;
     $wp_table_name = EEH_Activation::ensure_table_name_has_prefix($table_name);
     // do we need to first delete an existing version of this table ?
     if ($drop_pre_existing_table && EEH_Activation::table_exists($wp_table_name)) {
         // ok, delete the table... but ONLY if it's empty
         $deleted_safely = EEH_Activation::delete_db_table_if_empty($wp_table_name);
         // table is NOT empty, are you SURE you want to delete this table ???
         if (!$deleted_safely && defined('EE_DROP_BAD_TABLES') && EE_DROP_BAD_TABLES) {
             EEH_Activation::delete_unused_db_table($wp_table_name);
         } else {
             if (!$deleted_safely) {
                 // so we should be more cautious rather than just dropping tables so easily
                 EE_Error::add_persistent_admin_notice('bad_table_' . $wp_table_name . '_detected', sprintf(__('Database table %1$s exists when it shouldn\'t, and may contain erroneous data. If you have previously restored your database from a backup that didn\'t remove the old tables, then we recommend adding %2$s to your %3$s file then restore to that backup again. This will clear out the invalid data from %1$s. Afterwards you should undo that change from your %3$s file. %4$sIf you cannot edit %3$s, you should remove the data from %1$s manually then restore to the backup again.', 'event_espresso'), $wp_table_name, "<pre>define( 'EE_DROP_BAD_TABLES', TRUE );</pre>", '<b>wp-config.php</b>', '<br/>'), TRUE);
             }
         }
     }
     // does $sql contain valid column information? ( LPT: https://regex101.com/ is great for working out regex patterns )
     if (preg_match('((((.*?))(,\\s))+)', $sql, $valid_column_data)) {
         $SQL = "CREATE TABLE {$wp_table_name} ( {$sql} ) {$engine} DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;";
         //get $wpdb to echo errors, but buffer them. This way at least WE know an error
         //happened. And then we can choose to tell the end user
         $old_show_errors_policy = $wpdb->show_errors(TRUE);
         $old_error_suppression_policy = $wpdb->suppress_errors(FALSE);
         ob_start();
         dbDelta($SQL);
         $output = ob_get_contents();
         ob_end_clean();
         $wpdb->show_errors($old_show_errors_policy);
         $wpdb->suppress_errors($old_error_suppression_policy);
         if (!empty($output)) {
             throw new EE_Error($output);
         }
     } else {
         throw new EE_Error(sprintf(__('The following table creation SQL does not contain valid information about the table columns: %1$s %2$s', 'event_espresso'), '<br />', $sql));
     }
 }
 /**
  * 	_display_minimum_recommended_php_version_notice
  *
  * 	@access private
  * 	@return void
  */
 private function _display_minimum_recommended_php_version_notice()
 {
     EE_Error::add_persistent_admin_notice('php_version_' . str_replace('.', '-', EE_MIN_PHP_VER_RECOMMENDED) . '_recommended', sprintf(__('Event Espresso recommends PHP version %1$s or greater for optimal performance. You are currently running version %2$s.%3$sIn order to update your version of PHP, you will need to contact your current hosting provider.%3$sFor information on stable PHP versions, please go to %4$s.', 'event_espresso'), EE_MIN_PHP_VER_RECOMMENDED, PHP_VERSION, '<br/>', '<a href="http://php.net/downloads.php">http://php.net/downloads.php</a>'));
 }
 /**
  *		generate_state_abbreviation
  *
  * 		@access		public
  *		@return 		boolean
  */
 public static function save_new_state_to_db($props_n_values = array())
 {
     //		printr( $props_n_values, '$props_n_values  <br /><span style="font-size:10px;font-weight:normal;">' . __FILE__ . '<br />line no: ' . __LINE__ . '</span>', 'auto' );
     if ($existing_state = EEM_State::instance()->get_all(array($props_n_values, 'limit' => 1))) {
         return array_pop($existing_state);
     }
     $new_state = EE_State::new_instance($props_n_values);
     if ($new_state instanceof EE_State) {
         // if not non-ajax admin
         $new_state_key = $new_state->country_iso() . '-' . $new_state->abbrev();
         $new_state_notice = sprintf(__('A new State named "%s (%s)" was dynamically added from an Event Espresso form for the Country of "%s".<br/>To verify, edit, and/or delete this new State, please go to the %s and update the States / Provinces section.<br/>Check "Yes" to have this new State added to dropdown select lists in forms.', 'event_espresso'), '<b>' . $new_state->name() . '</b>', '<b>' . $new_state->abbrev() . '</b>', '<b>' . $new_state->country()->name() . '</b>', '<a href="' . add_query_arg(array('page' => 'espresso_general_settings', 'action' => 'country_settings', 'country' => $new_state->country_iso()), admin_url('admin.php')) . '">' . __('Event Espresso - General Settings > Countries Tab', 'event_espresso') . '</a>');
         EE_Error::add_persistent_admin_notice($new_state_key, $new_state_notice);
         $new_state->save();
         EEM_State::instance()->reset_cached_states();
         return $new_state;
     }
 }
 /**
  *	_migration_step
  *
  * @access protected
  * @param int $num_items
  * @throws EE_Error
  * @return int number of items ACTUALLY migrated
  */
 protected function _migration_step($num_items = 1)
 {
     // if this isn't set then something is really wrong
     if (!EE_Config::instance()->gateway instanceof EE_Gateway_Config) {
         throw new EE_Error(__('It appears the Event Espresso Core Configuration is not setup correctly.', 'event_espresso'));
     }
     $invoice_settings = isset(EE_Config::instance()->gateway->payment_settings['Invoice']) ? EE_Config::instance()->gateway->payment_settings['Invoice'] : NULL;
     if (!$invoice_settings) {
         $this->add_error(__('Could not migrate EE4.4 invoice settings to EE4.5 because they didnt exist', 'event_espresso'));
     } else {
         $invoice_settings['template_payment_instructions'] = $invoice_settings['pdf_instructions'];
         $invoice_settings['template_invoice_payee_name'] = $invoice_settings['payable_to'];
         $invoice_settings['template_invoice_address'] = $invoice_settings['payment_address'];
         $invoice_settings['template_invoice_email'] = '';
         $invoice_settings['template_invoice_tax_number'] = '';
         unset($invoice_settings['pdf_instructions']);
         unset($invoice_settings['payable_to']);
         unset($invoice_settings['payment_address']);
         EE_Config::instance()->gateway->payment_settings['Invoice'] = $invoice_settings;
         EE_Config::instance()->update_espresso_config(false, false);
         //@todo: check 'invoice_css' too because we can't easily affect that so we might need to set a persistent notice
         //(why is it tough to change? because we want to update the receipt and invoice message template, but
         //message templates are only initialized AFTER migrations and those two are new in 4.5. So if we wanted to
         //update them from a DMS, we'd need to have the DMS create the message templates which is quite a lot of code;
         //also we don't want to build a dependency on the messages code because it is likely to change soon
         if (!in_array($invoice_settings['invoice_css'], array('', 'simple.css'))) {
             EE_Error::add_persistent_admin_notice('invoice_css_not_updated', sprintf(__('You had previously set your Invoice Payment Method\'s stylesheet to be %1$s, but that setting has moved. PDF and HTML Invoices and Receipts are now Messages, which means you can easily modify them from your Wordpress Dashboard instead of using filters or uploading template files. Please visit Messages -> Receipt and Messages -> Invoice to change their stylesheets.', 'event_espresso'), $invoice_settings['invoice_css']), FALSE);
         }
         EE_Registry::instance()->load_helper('Template');
         $templates_relative_path = 'modules/gateways/Invoice/lib/templates/';
         $overridden_invoice_body = EEH_Template::locate_template($templates_relative_path . 'invoice_body.template.php', NULL, FALSE, FALSE, TRUE);
         $overridden_receipt_body = EEH_Template::locate_template($templates_relative_path . 'receipt_body.template.php', NULL, FALSE, FALSE, TRUE);
         if ($overridden_invoice_body || $overridden_receipt_body) {
             EE_Error::add_persistent_admin_notice('invoice_overriding_templates', sprintf(__('Note: in this version of Event Espresso, PDF and HTML Invoices and Receipts are now Messages and can be changed just like any other messages; however we noticed you had previously overriden the old default Invoice/Receipt templates. Because of this, your old Invoice/Receipt templates will continue to be used INSTEAD of the new Invoice/Receipt message equivalents. We recommend deleting your old Invoice/Receipt templates and modifying the new Invoice and Receipt messages\'s content in Messages -> Invoice and Messages -> Receipt.')), TRUE);
         }
     }
     //regardless of whether it worked or not, we ought to continue the migration
     $this->set_completed();
     return 1;
 }
 /**
  * _prep_pages
  * sets the _prepped_menu_maps property
  *
  * @access private
  * @throws EE_Error
  * @return void
  */
 private function _prep_pages()
 {
     $pages_array = array();
     //rearrange _admin_menu_groups to be indexed by group slug.
     $menu_groups = $this->_rearrange_menu_groups();
     foreach ($this->_installed_pages as $page) {
         if ($page instanceof EE_Admin_page_Init) {
             $page_map = $page->get_menu_map();
             //if we've got an array then the menu map is in the old format so let's throw a persistent notice that the admin system isn't setup correctly for this item.
             if (is_array($page_map) || empty($page_map)) {
                 EE_Error::add_persistent_admin_notice('menu_map_warning_' . str_replace(' ', '_', $page->label) . '_' . EVENT_ESPRESSO_VERSION, sprintf(__('The admin page for %s was not correctly setup because it is using an older method for integrating with Event Espresso Core.  This means that full functionality for this component is not available.  This error message usually appears with an Add-on that is out of date.  Make sure you update all your Event Espresso 4 add-ons to the latest version to ensure they have necessary compatibility updates in place.', 'event_espresso'), $page->label));
                 continue;
             }
             //if page map is NOT a EE_Admin_Page_Menu_Map object then throw error.
             if (!$page_map instanceof EE_Admin_Page_Menu_Map) {
                 throw new EE_Error(sprintf(__('The menu map for %s must be an EE_Admin_Page_Menu_Map object.  Instead it is %s.  Please double check that the menu map has been configured correctly.', 'event_espresso'), $page->label, $page_map));
             }
             //use the maintenance_mode_parent property and maintenance mode status to determine if this page even gets added to array.
             if (empty($page_map->maintenance_mode_parent) && EE_Maintenance_Mode::instance()->level() == EE_Maintenance_Mode::level_2_complete_maintenance) {
                 continue;
             }
             //assign to group (remember $page_map has the admin page stored in it).
             $pages_array[$page_map->menu_group][] = $page_map;
         }
     }
     if (empty($pages_array)) {
         throw new EE_Error(__('Something went wrong when prepping the admin pages', 'event_espresso'));
     }
     //let's sort the groups, make sure it's a valid group, add header (if to show).
     foreach ($pages_array as $group => $menu_maps) {
         //valid_group?
         if (!array_key_exists($group, $menu_groups)) {
             continue;
         }
         //sort pages.
         usort($menu_maps, array($this, '_sort_menu_maps'));
         //prepend header
         array_unshift($menu_maps, $menu_groups[$group]);
         //reset $pages_array with prepped data
         $pages_array[$group] = $menu_maps;
     }
     //now let's setup the _prepped_menu_maps property
     foreach ($menu_groups as $group => $group_objs) {
         if (isset($pages_array[$group])) {
             $this->_prepped_menu_maps = array_merge($this->_prepped_menu_maps, $pages_array[$group]);
         }
     }
     /**/
 }
 /**
  * Overrides parent to not only turn wpdb results into EE_Payment_Method objects,
  * but also verifies the payment method type of each is a usable object. If not,
  * deactivate it, sets a notification, and deactivates it
  * @param array $rows
  * @return EE_Payment_Method[]
  */
 protected function _create_objects($rows = array())
 {
     EE_Registry::instance()->load_lib('Payment_Method_Manager');
     $payment_methods = parent::_create_objects($rows);
     /* @var $payment_methods EE_Payment_Method[] */
     $usable_payment_methods = array();
     foreach ($payment_methods as $key => $payment_method) {
         if (EE_Payment_Method_Manager::instance()->payment_method_type_exists($payment_method->type())) {
             $usable_payment_methods[$key] = $payment_method;
             //some payment methods enqueue their scripts in EE_PMT_*::__construct
             //which is kinda a no-no (just because it's being constructed doesn't mean we need to enqueue
             //its scripts). but for backwards-compat we should continue to do that
             $payment_method->type_obj();
         } elseif ($payment_method->active()) {
             //only deactivate and notify the admin if the payment is active somewhere
             $payment_method->deactivate();
             $payment_method->save();
             EE_Error::add_persistent_admin_notice('auto-deactivated-' . $payment_method->type(), sprintf(__('The payment method %1$s was automatically deactivated because it appears its associated Event Espresso Addon was recently deactivated.%2$sIt can be reactivated on the %3$sPlugins admin page%4$s, then you can reactivate the payment method.', 'event_espresso'), $payment_method->admin_name(), '<br />', '<a href="' . admin_url('plugins.php') . '">', '</a>'), true);
         }
     }
     return $usable_payment_methods;
 }
 /**
  * 	used by EE and EE addons during plugin activation
  *
  * 	@access public
  * 	@static
  * @param string $table_name without the $wpdb->prefix
  * @param string $sql SQL for creating the table (contents between brackets in an SQL create table query)
  * @param string $engine like 'ENGINE=MyISAM' or 'ENGINE=InnoDB'
  * @param boolean $drop_table_if_pre_existed set to TRUE when you want to make SURE the table is completely empty
  * and new once this function is done (ie, you really do want to CREATE a table, and
  * expect it to be empty once you're done)
  * leave as FALSE when you just want to verify the table exists and matches this definition (and if it
  * HAS data in it you want to leave it be)
  * 	@return void
  * @throws EE_Error if there are database errors
  */
 public static function create_table($table_name, $sql, $engine = 'ENGINE=MyISAM ', $drop_table_if_pre_existed = false)
 {
     //		echo "create table $table_name ". ($drop_table_if_pre_existed? 'but first nuke preexisting one' : 'or update it if it exists') . "<br>";//die;
     if (apply_filters('FHEE__EEH_Activation__create_table__short_circuit', FALSE, $table_name, $sql)) {
         return;
     }
     do_action('AHEE_log', __FILE__, __FUNCTION__, '');
     if (!function_exists('dbDelta')) {
         require_once ABSPATH . 'wp-admin/includes/upgrade.php';
     }
     global $wpdb;
     $wp_table_name = $wpdb->prefix . $table_name;
     //		if(in_array(EE_System::instance()->detect_req_type(),array(EE_System::req_type_new_activation,  EE_System::req_t) )
     if ($drop_table_if_pre_existed && EEH_Activation::table_exists($wp_table_name)) {
         if (defined('EE_DROP_BAD_TABLES') && EE_DROP_BAD_TABLES) {
             $wpdb->query("DROP TABLE IF EXISTS {$wp_table_name} ");
         } else {
             //so we should be more cautious rather than just dropping tables so easily
             EE_Error::add_persistent_admin_notice('bad_table_' . $wp_table_name . '_detected', sprintf(__('Database table %1$s existed when it shouldn\'t, and probably contained erroneous data. You probably restored to a backup that didn\'t remove old tables didn\'t you? We recommend adding %2$s to your %3$s file then restore to that backup again. This will clear out the invalid data from %1$s. Afterwards you should undo that change from your %3$s file. %4$sIf you cannot edit %3$s, you should remove the data from %1$s manually then restore to the backup again.', 'event_espresso'), $wp_table_name, "<pre>define( 'EE_DROP_BAD_TABLES', TRUE );</pre>", '<b>wp-config.php</b>', '<br/>'), TRUE);
         }
     }
     $SQL = "CREATE TABLE {$wp_table_name} ( {$sql} ) {$engine} DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;";
     //get $wpdb to echo errors, but buffer them. This way at least WE know an error
     //happened. And then we can choose to tell the end user
     $old_show_errors_policy = $wpdb->show_errors(TRUE);
     $old_error_supression_policy = $wpdb->suppress_errors(FALSE);
     ob_start();
     dbDelta($SQL);
     $output = ob_get_contents();
     ob_end_clean();
     $wpdb->show_errors($old_show_errors_policy);
     $wpdb->suppress_errors($old_error_supression_policy);
     if (!empty($output)) {
         throw new EE_Error($output);
     }
 }
 /**
  * verify_default_pages_exist
  *
  * 	@access public
  * 	@static
  * 	@return void
  */
 public static function verify_default_pages_exist()
 {
     $critical_page_problem = FALSE;
     $critical_pages = array(array('id' => 'reg_page_id', 'name' => __('Registration Checkout', 'event_espresso'), 'post' => NULL, 'code' => 'ESPRESSO_CHECKOUT'), array('id' => 'txn_page_id', 'name' => __('Transactions', 'event_espresso'), 'post' => NULL, 'code' => 'ESPRESSO_TXN_PAGE'), array('id' => 'thank_you_page_id', 'name' => __('Thank You', 'event_espresso'), 'post' => NULL, 'code' => 'ESPRESSO_THANK_YOU'), array('id' => 'cancel_page_id', 'name' => __('Registration Cancelled', 'event_espresso'), 'post' => NULL, 'code' => 'ESPRESSO_CANCELLED'));
     foreach ($critical_pages as $critical_page) {
         // is critical page ID set in config ?
         if (EE_Registry::instance()->CFG->core->{$critical_page}['id'] !== FALSE) {
             // attempt to find post by ID
             $critical_page['post'] = get_post(EE_Registry::instance()->CFG->core->{$critical_page}['id']);
         }
         // no dice?
         if ($critical_page['post'] == NULL) {
             // attempt to find post by title
             $critical_page['post'] = self::get_page_by_ee_shortcode($critical_page['code']);
             // still nothing?
             if ($critical_page['post'] == NULL) {
                 $critical_page = EEH_Activation::create_critical_page($critical_page);
                 // REALLY? Still nothing ??!?!?
                 if ($critical_page['post'] == NULL) {
                     $msg = __('The Event Espresso critical page configuration settings could not be updated.', 'event_espresso');
                     EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
                     break;
                 }
             }
         }
         // track post_shortcodes
         if ($critical_page['post']) {
             EEH_Activation::_track_critical_page_post_shortcodes($critical_page);
         }
         // check that Post ID matches critical page ID in config
         if (isset($critical_page['post']->ID) && $critical_page['post']->ID != EE_Registry::instance()->CFG->core->{$critical_page}['id']) {
             //update Config with post ID
             EE_Registry::instance()->CFG->core->{$critical_page}['id'] = $critical_page['post']->ID;
             if (!EE_Config::instance()->update_espresso_config(FALSE, FALSE)) {
                 $msg = __('The Event Espresso critical page configuration settings could not be updated.', 'event_espresso');
                 EE_Error::add_error($msg, __FILE__, __FUNCTION__, __LINE__);
             }
         }
         $critical_page_problem = !isset($critical_page['post']->post_status) || $critical_page['post']->post_status != 'publish' || strpos($critical_page['post']->post_content, $critical_page['code']) === FALSE ? TRUE : $critical_page_problem;
     }
     if ($critical_page_problem) {
         $msg = sprintf(__('A potential issue has been detected with one or more of your Event Espresso pages. Go to %s to view your Event Espresso pages.', 'event_espresso'), '<a href="' . admin_url('admin.php?page=espresso_general_settings&action=critical_pages') . '">' . __('Event Espresso Critical Pages Settings', 'event_espresso') . '</a>');
         EE_Error::add_persistent_admin_notice('critical_page_problem', $msg);
     }
     if (EE_Error::has_notices()) {
         EE_Error::get_notices(FALSE, TRUE, TRUE);
     }
 }
 /**
  * If the user appears to be using WP API basic auth, tell them (via a persistent
  * admin notice and an email) that we're going to remove it soon, so they should
  * replace it with application passwords.
  */
 public static function maybe_notify_of_basic_auth_removal()
 {
     if (!isset($_SERVER['PHP_AUTH_USER']) && !isset($_SERVER['HTTP_AUTHORIZATION'])) {
         //sure it's a WP API request, but they aren't using basic auth, so don't bother them
         return;
     }
     //ok they're using the WP API with Basic Auth
     $message = sprintf(__('We noticed you\'re using the WP API, which is used by the Event Espresso 4 mobile apps. Because of security and compatibility concerns, we will soon be removing our default authentication mechanism, WP API Basic Auth, from Event Espresso. It is recommended you instead install the %1$sWP Application Passwords plugin%2$s and use it with the EE4 Mobile apps. See %3$sour mobile app documentation%2$s for more information. %4$sIf you have installed the WP API Basic Auth plugin separately, or are not using the Event Espresso 4 mobile apps, you can disregard this message.%4$sThe Event Espresso Team', 'event_espresso'), '<a href="https://wordpress.org/plugins/application-passwords/">', '</a>', '<a href="https://eventespresso.com/wiki/ee4-event-apps/#authentication">', '<br/>');
     EE_Error::add_persistent_admin_notice('using_basic_auth', $message);
     if (!get_option('ee_notified_admin_on_basic_auth_removal', false)) {
         add_option('ee_notified_admin_on_basic_auth_removal', true);
         //piggy back off EE_Error::set_content_type, which sets the content type to HTML
         add_filter('wp_mail_content_type', array('EE_Error', 'set_content_type'));
         //and send the message to the site admin too
         wp_mail(get_option('admin_email'), __('Notice of Removal of WP API Basic Auth From Event Espresso 4', 'event_espresso'), $message);
         remove_filter('wp_mail_content_type', array('EE_Error', 'set_content_type'));
     }
 }