'; /** * Which face this install shows: `hub`, `member` or `unpaired`. A * multisite is always the hub side; a single site is whichever role it * took, or neither. * * @param Os $os Host. * @return string */ function mode( Os $os ) { if ( $os->env->is_network() ) { return 'hub'; } if ( \openstation_network_is_member() ) { return 'member'; } if ( \openstation_network_is_hub() ) { return 'hub'; } return 'unpaired'; } /** * The gate: whoever manages this install's network or, on a single * site, the site. * * @param Os $os Host. * @return bool */ function can_use( Os $os ) { return $os->env->is_network() ? $os->auth->can( 'manage_network' ) : $os->auth->can( 'manage_options' ); } /** * Record an outcome on the state: an error, or a notice. * * @param State $state State. * @param mixed $result A WP_Error, or anything else for success. * @param string $notice Notice on success. */ function outcome( State $state, $result, $notice ) { if ( is_wp_error( $result ) ) { $state->set( 'error', $result->get_error_message() )->set( 'notice', '' ); return; } $state->set( 'error', '' )->set( 'notice', $notice ); } /** * A status badge for a member. * * @param string $status `paired`, `unreachable` or `key-changed`. * @return string Markup. */ function status_badge( $status ) { switch ( $status ) { case 'unreachable': return tag( 'os-badge', array( 'tone' => 'warning' ), esc( __( 'Unreachable', 'desktop-mode' ) ) ); case 'key-changed': return tag( 'os-badge', array( 'tone' => 'danger' ), esc( __( 'Key changed', 'desktop-mode' ) ) ); default: return tag( 'os-badge', array( 'tone' => 'success' ), esc( __( 'Paired', 'desktop-mode' ) ) ); } } /** * Which switcher entry this shell is (`network`, a blog id, `hub` or * `member:`), so that row offers no Open: the user is already here. * * @return string */ function current_instance() { $block = \openstation_multisite_payload(); return is_array( $block ) && isset( $block['current'] ) ? (string) $block['current'] : ''; } /** * One site row. * * @param array $site `id`, `name`, `url`, `shellUrl`, `kind`, `status`, `error`. * @param bool $can_remove Whether a Remove button is offered. * @param string $current The switcher entry this shell is; see `current_instance()`. */ function site_row( array $site, $can_remove, $current ) { $is_member = 'member' === $site['kind']; ?>
  • 'neutral' ), esc( __( 'This network', 'desktop-mode' ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built by tag(); see above. } if ( $site['id'] !== $current ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built by tag(); see above. echo tag( 'os-button', array( 'variant' => 'ghost', 'os-action' => 'open', 'os-arg-id' => $site['id'], 'title' => __( 'Switch to this site', 'desktop-mode' ), ), esc( __( 'Open', 'desktop-mode' ) ) ); } if ( $is_member && $can_remove ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Built by tag(); see above. echo tag( 'os-button', array( 'variant' => 'ghost', 'os-action' => 'remove', 'os-arg-id' => substr( (string) $site['id'], strlen( 'member:' ) ), /* translators: %s: site name. */ 'os-confirm' => sprintf( __( 'Remove %s from the network? Its switcher will stop listing this network the next time it syncs.', 'desktop-mode' ), $site['name'] ), 'os-confirm-label' => __( 'Remove', 'desktop-mode' ), ), esc( __( 'Remove', 'desktop-mode' ) ) ); } ?>
  • get( 'error' ) ) { echo '' . esc( (string) $state->get( 'error' ) ) . ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- esc() escapes. } if ( '' !== (string) $state->get( 'notice' ) ) { echo '' . esc( (string) $state->get( 'notice' ) ) . ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- esc() escapes. } } /** * The hub's face: every site, and the door. * * @param State $state State. * @param Os $os Host. */ function hub_view( State $state, Os $os ) { $identity = \openstation_network_identity(); $sites = array_merge( \openstation_network_local_entries(), \openstation_network_member_entries() ); $members = \openstation_network_members(); $current = current_instance(); ?>

    env->is_network() ? __( 'The sites of this WordPress network, and the OpenStation installs that joined it from elsewhere. Every one of them shows the same site switcher.', 'desktop-mode' ) : __( 'This site is the hub of an OpenStation network: every install listed here shows the same site switcher.', 'desktop-mode' ) ); ?>

      $site['id'], 'name' => $site['name'], 'url' => $site['url'], 'kind' => $site['kind'], 'status' => $site['status'], 'error' => $member ? $member['error'] : '', ), true, $current ); } ?>

    0 ) : ?> env->format_datetime( $hub['fetched'] ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- esc() escapes. ?>

      $site['id'], 'name' => $site['name'], 'url' => $site['url'], 'kind' => $site['kind'], 'status' => 'paired', 'error' => '', ), false, $current ); } ?>

    auth->user_id() ); if ( array() === $links ) { return; } ?>

      $link ) : ?>
    • 'ghost', 'os-action' => 'unlink', 'os-arg-key' => $key, ), esc( __( 'Unlink', 'desktop-mode' ) ) ); ?>
    '; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- esc() escapes. notices( $state ); if ( 'hub' === $mode ) { hub_view( $state, $os ); } elseif ( 'member' === $mode ) { member_view( $state, $os ); } else { unpaired_view( $state, $os ); } links_section( $os ); echo ''; } return App::define( APP_ID ) ->title( __( 'Network', 'desktop-mode' ) ) ->icon( ICON ) ->size( 760, 560 ) ->min_size( 520, 400 ) // Every shell of the network offers it: managing the network is the // super admin's job wherever they stand, and each row is a way to // another site. ->admin( 'any' ) // A desktop icon rather than a dock tile, like WP Explorer: the icon // has its own row in Preferences > Navigation. ->placement( 'none' ) ->desktop_icon() ->can( __NAMESPACE__ . '\\can_use' ) ->state( array( 'url' => '', 'notice' => '', 'error' => '', ) ) ->action( 'dismiss', static function ( State $state ) { $state->set( 'error', '' )->set( 'notice', '' ); } ) ->action( 'add', static function ( State $state, Os $os ) { $member = \openstation_network_add_member( (string) $state->get( 'url' ) ); outcome( $state, $member, is_wp_error( $member ) ? '' /* translators: %s: site name. */ : sprintf( __( '%s is in the network. It is in the site switcher now, and on that site once it joins from its Network window.', 'desktop-mode' ), $member['name'] ) ); if ( ! is_wp_error( $member ) ) { $state->set( 'url', '' ); // The switcher's rows come from the menu payload; a // refresh is how they follow the registry without a // reload. Same after every action below that changes them. $os->refresh_menu(); } } ) ->action( 'remove', static function ( State $state, Os $os, array $args ) { $id = isset( $args['id'] ) ? sanitize_key( (string) $args['id'] ) : ''; $removed = \openstation_network_remove_member( $id ); outcome( $state, $removed ? true : new \WP_Error( 'openstation_network_unknown', __( 'That site is not in the network.', 'desktop-mode' ) ), __( 'Removed from the network.', 'desktop-mode' ) ); if ( $removed ) { $os->refresh_menu(); } } ) ->action( 'open', static function ( State $state, Os $os, array $args ) { $id = isset( $args['id'] ) && is_scalar( $args['id'] ) ? sanitize_text_field( (string) $args['id'] ) : ''; if ( '' !== $id ) { // The shell takes it from here: the same hop a pick in // the switcher takes, slide and login token included. $os->effects->add( 'hop', array( 'site' => $id ) ); } } ) ->action( 'unlink', static function ( State $state, Os $os, array $args ) { $key = isset( $args['key'] ) && is_scalar( $args['key'] ) ? sanitize_text_field( (string) $args['key'] ) : ''; outcome( $state, \openstation_network_unlink( (int) $os->auth->user_id(), $key ) ? true : new \WP_Error( 'openstation_network_unknown', __( 'That account is not linked.', 'desktop-mode' ) ), __( 'Unlinked. A switch from that account lands on the login screen now.', 'desktop-mode' ) ); } ) ->action( 'check', static function ( State $state ) { \openstation_network_check_members(); outcome( $state, true, __( 'Every site was checked.', 'desktop-mode' ) ); } ) ->action( 'join', static function ( State $state, Os $os ) { $hub = \openstation_network_join( (string) $state->get( 'url' ) ); outcome( $state, $hub, is_wp_error( $hub ) ? '' : ( '' === $hub['error'] /* translators: %s: network name. */ ? sprintf( __( 'This site belongs to %s. The site switcher shows the network now.', 'desktop-mode' ), $hub['name'] ) /* translators: %s: network name. */ : sprintf( __( 'Pinned %s. It has not added this site yet; sync once it has.', 'desktop-mode' ), $hub['name'] ) ) ); if ( ! is_wp_error( $hub ) ) { $state->set( 'url', '' ); $os->refresh_menu(); } } ) ->action( 'leave', static function ( State $state, Os $os ) { \openstation_network_leave(); outcome( $state, true, __( 'Left the network.', 'desktop-mode' ) ); $os->refresh_menu(); } ) ->action( 'sync', static function ( State $state, Os $os ) { $list = \openstation_network_refresh_list(); outcome( $state, $list, __( 'Site list synced.', 'desktop-mode' ) ); if ( ! is_wp_error( $list ) ) { $os->refresh_menu(); } } ) ->view( __NAMESPACE__ . '\\render' );