connection = $connection; } public function register(): void { add_action( 'admin_post_mowl_connect', array( $this, 'start' ) ); add_action( 'admin_post_mowl_disconnect', array( $this, 'disconnect' ) ); add_action( 'admin_post_mowl_test_connection', array( $this, 'test' ) ); add_action( 'admin_init', array( $this, 'maybe_handle_callback' ) ); } public static function settings_url( array $args = array() ): string { return add_query_arg( array_merge( array( 'page' => self::PAGE ), $args ), admin_url( 'admin.php' ) ); } /** * A Mowl a kódot csak a site_url (home_url()) hostjára küldi vissza, ezért * a return_url (az admin) is ott kell fusson. Ha az admin más domainen * nyílt meg (pl. home www.mowl.email, admin www.mowl.hu), innen nem * indítható csatlakozás. * * @return string|null a home_url() hostja, ha eltér az adminétól */ public static function foreign_home_host(): ?string { $home = strtolower( (string) wp_parse_url( home_url(), PHP_URL_HOST ) ); $admin = strtolower( (string) wp_parse_url( self::settings_url(), PHP_URL_HOST ) ); return '' !== $home && $home !== $admin ? $home : null; } /** A beállítás-oldal címe a home_url() domainjén (séma, host, port). */ public static function settings_url_on_home(): string { $home = wp_parse_url( home_url() ); $origin = ( $home['scheme'] ?? 'https' ) . '://' . ( $home['host'] ?? '' ) . ( isset( $home['port'] ) ? ':' . $home['port'] : '' ); return (string) preg_replace( '#^https?://[^/]+#i', $origin, self::settings_url() ); } public function authorize_url( string $state ): string { // add_query_arg() nem URL-kódolja az értékeket, tehát a beágyazott // URL-t (return_url) és a szabad szöveget (site_name) kézzel kell. return add_query_arg( array( 'platform' => 'wordpress', 'site_url' => rawurlencode( home_url() ), 'site_name' => rawurlencode( get_bloginfo( 'name' ) ), 'return_url' => rawurlencode( self::settings_url( array( 'mowl_callback' => '1' ) ) ), 'state' => rawurlencode( $state ), ), $this->connection->client()->base_url() . '/integrations/authorize' ); } public function start(): void { Guard::admin_action( 'mowl_connect' ); $home_host = self::foreign_home_host(); if ( null !== $home_host ) { Notices::flash( 'error', sprintf( /* translators: %s: the site's home domain, e.g. www.example.com */ __( 'Connecting only works from the admin on your site’s own domain. Open the admin at %s and try again.', 'mowl-email' ), esc_html( $home_host ) ) ); Redirect::to( self::settings_url() ); } $state = wp_generate_password( 32, false, false ); set_transient( $this->state_key(), $state, self::STATE_TTL ); Redirect::external( $this->authorize_url( $state ) ); } /** admin_init: csak a saját oldalunkon, csak callback-paraméterrel. */ public function maybe_handle_callback(): void { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- a state a CSRF-védelem, lásd lent. if ( ! isset( $_GET['page'], $_GET['mowl_callback'] ) || self::PAGE !== sanitize_key( wp_unslash( $_GET['page'] ) ) || '1' !== sanitize_key( wp_unslash( $_GET['mowl_callback'] ) ) ) { return; } if ( ! current_user_can( 'manage_options' ) ) { return; } $state = isset( $_GET['state'] ) ? sanitize_text_field( wp_unslash( $_GET['state'] ) ) : ''; $code = isset( $_GET['code'] ) ? sanitize_text_field( wp_unslash( $_GET['code'] ) ) : ''; $error = isset( $_GET['error'] ) ? sanitize_key( wp_unslash( $_GET['error'] ) ) : ''; // phpcs:enable $expected = get_transient( $this->state_key() ); delete_transient( $this->state_key() ); if ( ! is_string( $expected ) || '' === $expected || ! hash_equals( $expected, $state ) ) { Notices::flash( 'error', __( 'The connection attempt could not be verified (state mismatch). Please try again.', 'mowl-email' ) ); Redirect::to( self::settings_url() ); } if ( '' !== $error ) { Notices::flash( 'warning', __( 'The connection was cancelled in Mowl.', 'mowl-email' ) ); Redirect::to( self::settings_url() ); } try { $data = $this->connection->client()->connect( $code, home_url() ); } catch ( ApiException $e ) { Log::add( 'error', 'Connect code exchange failed', array( 'status' => $e->getStatus(), 'message' => $e->getMessage(), ) ); Notices::flash( 'error', __( 'Mowl rejected the connection code. Please start the connection again.', 'mowl-email' ) ); Redirect::to( self::settings_url() ); } if ( empty( $data['token'] ) || ! is_string( $data['token'] ) || empty( $data['integration_id'] ) || ! is_numeric( $data['integration_id'] ) || (int) $data['integration_id'] <= 0 ) { Log::add( 'error', 'Connect response malformed', array( 'keys' => array_keys( $data ) ) ); Notices::flash( 'error', __( 'Mowl returned an unexpected response. Please try again.', 'mowl-email' ) ); Redirect::to( self::settings_url() ); } $this->connection->save( (string) $data['token'], (int) $data['integration_id'], (array) ( $data['workspace'] ?? array() ) ); Log::add( 'info', 'Connected to Mowl', array( 'workspace' => $data['workspace']['name'] ?? '' ) ); Notices::flash( 'success', sprintf( /* translators: %s: workspace name */ __( 'Connected to the Mowl workspace “%s”.', 'mowl-email' ), esc_html( (string) ( $data['workspace']['name'] ?? '' ) ) ) ); Redirect::to( self::settings_url() ); } public function disconnect(): void { Guard::admin_action( 'mowl_disconnect' ); $token = $this->connection->token(); if ( null !== $token ) { try { $this->connection->client()->disconnect( $token ); } catch ( ApiException $e ) { // Best effort: a helyi állapot mindenképp törlődik; a Mowl oldalon kézzel is leválasztható. Log::add( 'warning', 'Remote disconnect failed', array( 'status' => $e->getStatus() ) ); } } $this->connection->clear(); Notices::flash( 'success', __( 'Disconnected from Mowl.', 'mowl-email' ) ); Redirect::to( self::settings_url() ); } public function test(): void { Guard::admin_action( 'mowl_test_connection' ); $health = $this->connection->health( true ); if ( null === $health ) { Notices::flash( 'error', $this->connection->is_broken() ? __( 'Mowl no longer accepts this connection. Please connect again.', 'mowl-email' ) : __( 'Could not reach Mowl. Check the log tab for details.', 'mowl-email' ) ); } else { Notices::flash( 'success', __( 'Connection OK.', 'mowl-email' ) ); } Redirect::to( self::settings_url() ); } private function state_key(): string { return 'mowl_connect_state_' . get_current_user_id(); } }