*/ private array $cache = array(); /** * Plain-language location of a source. * * @param Source $source Source. */ public function label( Source $source ): string { $key = $source->type . ':' . $source->ref; if ( ! isset( $this->cache[ $key ] ) ) { $this->cache[ $key ] = $this->build( $source ); } return $this->cache[ $key ]; } /** * Manual steps for a source type. * * @param string $type Source type. */ public static function manual_steps( string $type ): string { switch ( $type ) { case Source::POST_CONTENT: return __( 'Open the page or post in the editor, find the link or image, and correct or remove it.', 'forensix-digital' ); case Source::ELEMENTOR: return __( 'Open the page or template with Elementor, select the element named in the location, and correct the link or image in its settings.', 'forensix-digital' ); case Source::MENU_ITEM: return __( 'Go to Appearance → Menus (or the Site Editor for block themes), open the menu, and correct or remove the item.', 'forensix-digital' ); case Source::SEO_FIELD: return __( 'Open the page or post in the editor and change it in the SEO settings of your SEO plugin. Without an SEO plugin, titles and descriptions are set in the "SEO title and description" box below the editor.', 'forensix-digital' ); case Source::SITE_FILE: return __( 'robots.txt is a file in the site folder, or a virtual file that WordPress or your SEO plugin creates when none exists. The sitemap comes from WordPress or your SEO plugin. Change them in the SEO plugin settings, or in the file itself if it exists in the site folder.', 'forensix-digital' ); case Source::MEDIA: return __( 'Open Media → Library, select the file named in the location and change it there. Alternative text is edited in the details of the file or in the block editor.', 'forensix-digital' ); case Source::WIDGET: return __( 'Go to Appearance → Widgets, open the widget named in the location, and correct the link or image.', 'forensix-digital' ); case Source::POST_META: return __( 'This address is stored in a custom field of the page or post named in the location. Edit it there, or in the settings of the plugin that owns the field.', 'forensix-digital' ); case Source::OPTION: return __( 'This address is stored in a site setting. Look for it in the settings of WordPress, your theme or the plugin named after the setting, and change it to the current domain.', 'forensix-digital' ); default: return __( 'This comes from your theme, a plugin or a template outside the content. Check the theme settings, the Site Editor or the plugin that adds this part of the page.', 'forensix-digital' ); } } /** * Builds a label. * * @param Source $source Source. */ private function build( Source $source ): string { switch ( $source->type ) { case Source::POST_CONTENT: return $this->post_label( (int) $source->ref ); case Source::ELEMENTOR: [ $post, $element ] = array_pad( explode( ':', $source->ref, 2 ), 2, '' ); if ( '' === $element ) { /* translators: %s: page or template. */ return sprintf( __( 'Elementor content of %s', 'forensix-digital' ), $this->post_label( (int) $post ) ); } /* translators: 1: Elementor element id, 2: page or template. */ return sprintf( __( 'Elementor element %1$s in %2$s', 'forensix-digital' ), $element, $this->post_label( (int) $post ) ); case Source::POST_META: [ $post, $key ] = array_pad( explode( ':', $source->ref, 2 ), 2, '' ); /* translators: 1: custom field name, 2: page or post. */ return sprintf( __( 'Custom field "%1$s" of %2$s', 'forensix-digital' ), $key, $this->post_label( (int) $post ) ); case Source::MEDIA: /* translators: %s: media file. */ return sprintf( __( 'Media file "%s"', 'forensix-digital' ), $this->media_title( (int) $source->ref ) ); case Source::SEO_FIELD: [ $post, $field ] = array_pad( explode( ':', $source->ref, 2 ), 2, '' ); $names = array( 'title' => __( 'SEO title', 'forensix-digital' ), 'description' => __( 'SEO description', 'forensix-digital' ), 'robots' => __( 'Indexing setting', 'forensix-digital' ), 'canonical' => __( 'Canonical address', 'forensix-digital' ), ); /* translators: 1: name of the SEO field, e.g. "SEO title", 2: page or post. */ return sprintf( __( '%1$s of %2$s', 'forensix-digital' ), $names[ $field ] ?? $field, $this->post_label( (int) $post ) ); case Source::SITE_FILE: if ( 'robots.txt' === $source->ref ) { return __( 'The file robots.txt', 'forensix-digital' ); } return 'llms.txt' === $source->ref ? __( 'The file llms.txt', 'forensix-digital' ) : __( 'The XML sitemap', 'forensix-digital' ); case Source::OPTION: /* translators: %s: setting name. */ return sprintf( __( 'Site setting "%s"', 'forensix-digital' ), $source->ref ); case Source::MENU_ITEM: return $this->menu_label( (int) $source->ref ); case Source::WIDGET: return $this->widget_label( $source->ref ); default: return __( 'Theme template or plugin output', 'forensix-digital' ); } } /** * "Page "About us"" style label. * * @param int $post_id Post id. */ private function post_label( int $post_id ): string { $post = $post_id > 0 ? get_post( $post_id ) : null; if ( ! $post instanceof \WP_Post ) { /* translators: %d: post id. */ return sprintf( __( 'Content #%d (no longer exists)', 'forensix-digital' ), $post_id ); } $type = get_post_type_object( $post->post_type ); $kind = null !== $type ? $type->labels->singular_name : $post->post_type; $title = '' !== $post->post_title ? $post->post_title : __( '(no title)', 'forensix-digital' ); /* translators: 1: post type, e.g. "Page", 2: title. */ return sprintf( __( '%1$s "%2$s"', 'forensix-digital' ), $kind, $title ); } /** * Title of a media file, or its file name. * * @param int $attachment_id Attachment id. */ private function media_title( int $attachment_id ): string { $post = $attachment_id > 0 ? get_post( $attachment_id ) : null; if ( ! $post instanceof \WP_Post ) { /* translators: %d: attachment id. */ return sprintf( __( '#%d (no longer exists)', 'forensix-digital' ), $attachment_id ); } return '' !== $post->post_title ? $post->post_title : wp_basename( (string) get_attached_file( $attachment_id ) ); } /** * "Menu "Footer" → item "Contact"". * * @param int $item_id Menu item post id. */ private function menu_label( int $item_id ): string { $item = get_post( $item_id ); $terms = wp_get_object_terms( $item_id, 'nav_menu' ); $menu = is_array( $terms ) && isset( $terms[0] ) && $terms[0] instanceof \WP_Term ? $terms[0]->name : __( 'Unknown menu', 'forensix-digital' ); $title = ''; if ( $item instanceof \WP_Post ) { $setup = wp_setup_nav_menu_item( $item ); $title = isset( $setup->title ) ? (string) $setup->title : $item->post_title; } /* translators: 1: menu name, 2: menu item title. */ return sprintf( __( 'Menu "%1$s" → item "%2$s"', 'forensix-digital' ), $menu, $title ); } /** * "Widget "Text" in "Footer"". * * @param string $widget_id Widget id, e.g. "text-2". */ private function widget_label( string $widget_id ): string { global $wp_registered_widgets, $wp_registered_sidebars; $name = isset( $wp_registered_widgets[ $widget_id ]['name'] ) ? (string) $wp_registered_widgets[ $widget_id ]['name'] : $widget_id; $sidebar = ''; foreach ( (array) get_option( 'sidebars_widgets', array() ) as $sidebar_id => $widgets ) { if ( is_array( $widgets ) && in_array( $widget_id, $widgets, true ) ) { $sidebar = isset( $wp_registered_sidebars[ $sidebar_id ]['name'] ) ? (string) $wp_registered_sidebars[ $sidebar_id ]['name'] : (string) $sidebar_id; break; } } return '' === $sidebar /* translators: %s: widget name. */ ? sprintf( __( 'Widget "%s"', 'forensix-digital' ), $name ) /* translators: 1: widget name, 2: widget area. */ : sprintf( __( 'Widget "%1$s" in "%2$s"', 'forensix-digital' ), $name, $sidebar ); } }