start_target() ); exit; } /** * Where the connect button leads: /connect, or back to the screen with a message on non-https sites. * * @return string */ public function start_target(): string { if ( Connection::is_connected() ) { Notice::add( 'error', __( 'This site is already connected. Disconnect first.', '85pl-android-sms-api' ) ); return Menu::tab_url( 'overview' ); } $problem = Connection::connect_problem(); if ( null !== $problem ) { Notice::add( 'error', self::problem_message( $problem ) ); return Menu::tab_url( 'overview' ); } return Connection::connect_url(); } /** * Message for a Connection::connect_problem() value. * * @param string $problem 'insecure' or 'address'. * @return string */ public static function problem_message( string $problem ): string { if ( 'insecure' === $problem ) { return __( 'Your site must use https to connect to smsportal.app.', '85pl-android-sms-api' ); } return __( 'The WordPress Address and Site Address have different domains or an unusual form, so this site cannot be connected. Check Settings > General.', '85pl-android-sms-api' ); } /** * Disconnect button (admin-post). * * @return void */ public function handle_disconnect() { if ( ! current_user_can( Menu::CAPABILITY ) ) { Guard::deny(); } check_admin_referer( self::DISCONNECT_ACTION ); $this->disconnect_and_notify(); wp_safe_redirect( Menu::tab_url( 'overview' ) ); exit; } /** * Disconnect and tell the admin whether smsportal confirmed it. * * @return void */ public function disconnect_and_notify() { if ( Connection::home_changed() ) { Connection::disconnect( 'local' ); Notice::add( 'success', __( 'Disconnected only on this site. Its address changed, so the original site keeps its connection.', '85pl-android-sms-api' ) ); return; } if ( Connection::disconnect( 'user' ) ) { Notice::add( 'success', __( 'Disconnected from smsportal.app.', '85pl-android-sms-api' ) ); return; } Notice::add( 'warning', __( 'Disconnected on this site, but smsportal.app did not answer. Remove the connection in the smsportal.app dashboard under Integrations.', '85pl-android-sms-api' ) ); } /** * "This is the right site: keep the connection" (admin-post). * * @return void */ public function handle_keep_home() { if ( ! current_user_can( Menu::CAPABILITY ) ) { Guard::deny(); } check_admin_referer( self::KEEP_HOME_ACTION ); $this->process_keep_home(); wp_safe_redirect( Menu::tab_url( 'overview' ) ); exit; } /** * The site moved for good: the connection takes the new address and sending resumes. * * @return void */ public function process_keep_home() { Connection::keep_home(); Notice::add( 'success', __( 'Connection kept for this address. SMS are sent again.', '85pl-android-sms-api' ) ); } /** * "Connect again" on a copy of the site (admin-post). * * @return void */ public function handle_reconnect() { if ( ! current_user_can( Menu::CAPABILITY ) ) { Guard::deny(); } check_admin_referer( self::RECONNECT_ACTION ); $this->process_reconnect(); wp_safe_redirect( Menu::tab_url( 'overview' ) ); exit; } /** * Drop the copied connection on this site only (the original keeps it), so this site can connect on its own. * * @return void */ public function process_reconnect() { Connection::disconnect( 'local' ); Notice::add( 'info', __( 'The copied connection was removed from this site only. Click "Connect to smsportal.app" to connect this site on its own.', '85pl-android-sms-api' ) ); } /** * Return from /connect lands on admin.php?page=smsportal&code=…&state=… (or &error=…). * * @return void */ public function handle_return() { if ( ! current_user_can( Menu::CAPABILITY ) ) { return; } /* * The browser comes back from the sign-in on smsportal.app (an OAuth redirect), which cannot carry a * WordPress nonce. Its CSRF protection is the one-time "state" token created for this user when they * clicked Connect (itself a nonce-checked form): Connection::handle_return() compares it with * hash_equals() and does nothing unless it matches. */ $query = array(); foreach ( array( 'state', 'code', 'error' ) as $key ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- see above: the state token is checked instead. $value = isset( $_GET[ $key ] ) ? sanitize_text_field( wp_unslash( $_GET[ $key ] ) ) : ''; if ( '' !== $value ) { $query[ $key ] = $value; } } $target = $this->process_return( $query ); if ( null !== $target ) { wp_safe_redirect( $target ); exit; } } /** * Process return parameters. * * @param array $query Unslashed query. * @return string|null Redirect target, or null when this is not a return from smsportal. */ public function process_return( array $query ): ?string { if ( ! isset( $query['state'] ) || ( ! isset( $query['code'] ) && ! isset( $query['error'] ) ) ) { return null; } $params = array(); foreach ( array( 'state', 'code', 'error' ) as $key ) { if ( isset( $query[ $key ] ) && is_string( $query[ $key ] ) ) { $params[ $key ] = $query[ $key ]; } } $result = Connection::handle_return( $params ); if ( is_wp_error( $result ) ) { $type = 'smsportal_cancelled' === $result->get_error_code() ? 'warning' : 'error'; Notice::add( $type, $result->get_error_message() ); } else { Notice::add( 'success', __( 'Connected to smsportal.app.', '85pl-android-sms-api' ) ); } return Menu::tab_url( 'overview' ); } }