Tags Instead of Category

I think your idea is really nice here, but you have a couple of serious flaws in your code.

  • Never use extract(), ever. It is extremely hard to debug and can render unexpected output. It has now been completely removed from core, except in one instance as far as I can pick up from this core trac ticket. The codex has also been updated to reflect this changes as to remove extract() from all functions (Special thanks to the guys responsible for this)

  • Your word in your class name should start with a capital letter. Check the coding standard on naming conventions

  • You are using get_categories() while you need tags. The correct function to use is get_tags()

I have cleaned up your code and corrected the above problems and also rewritten the widget to get it more inline with the Widget API

I have also worked in a custom query to call the posts. You can just change this as needed

Here is the working code. (Caveat: there might be one or two smaller issues that I have missed. It is late)

class Tagged_Posts extends WP_Widget {

    public function __construct() {
        parent::__construct(
            'widget_tagged_posts', 
            _x( 'Tagged Posts Widget', 'Tagged Posts Widget' ), 
            array( 'description' => __( 'Display a list of posts from a selected tag.' ), 
            ) 
        );
        $this->alt_option_name="widget_tagged_posts";

        add_action( 'save_post', array($this, 'flush_widget_cache') );
        add_action( 'deleted_post', array($this, 'flush_widget_cache') );
        add_action( 'switch_theme', array($this, 'flush_widget_cache') );
    }

    public function widget( $args, $instance ) {
        $cache = array();
        if ( ! $this->is_preview() ) {
            $cache = wp_cache_get( 'widget_tag_posts', 'widget' );
        }

        if ( ! is_array( $cache ) ) {
            $cache = array();
        }

        if ( ! isset( $args['widget_id'] ) ) {
            $args['widget_id'] = $this->id;
        }

        if ( isset( $cache[ $args['widget_id'] ] ) ) {
            echo $cache[ $args['widget_id'] ];
            return;
        }

        ob_start();

        $title          = ( ! empty( $instance['title'] ) ) ? $instance['title'] : __( 'Tagged Posts' );
        $title          = apply_filters( 'widget_title', $title, $instance, $this->id_base );
        $number         = ( ! empty( $instance['number'] ) ) ? absint( $instance['number'] ) : 5;
        if ( ! $number ) {
            $number = 5;
        }
        $tags_id        = $instance['tags_id'];
        $thumbnail      = $instance['thumbnail'] ? true : false; 

        $query_args = [
            'posts_per_page'    => $number,
            'tag_id'            => $tags_id,
        ];
        $q = new WP_Query( $query_args );

        if( $q->have_posts() ) {

            echo $args['before_widget'];
            if ( $title ) {
                echo $args['before_title'] . $title . $args['after_title'];
            }               

            while( $q->have_posts() ) {
                $q->the_post(); ?>

                <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>> 

                    <header class="entry-header">
                        <?php the_title( '<h1 class="entry-title"><a href="' . esc_url( get_permalink() ) . '" rel="bookmark">', '</a></h1>' ); ?>
                    </header><!-- .entry-header -->

                    <?php 
                    if ( has_post_thumbnail() && true === $thumbnail ) { ?>

                        <div class="post-thumbnail">

                            <?php the_post_thumbnail(); ?>

                        </div><!--/.post-thumbnail-->

                    <?php   
                    }   
                    ?>          

                    <div class="entry-summary">
                        <?php the_excerpt(); ?>
                    </div><!-- .entry-summary -->

                </article><!-- #post-## -->

                <?php
            }


            wp_reset_postdata();
        }
            echo $args['after_widget']; 

        if ( ! $this->is_preview() ) {
            $cache[ $args['widget_id'] ] = ob_get_flush();
            wp_cache_set( 'widget_tag_posts', $cache, 'widget' );
        } else {
            ob_end_flush();
        }
    }

    public function update( $new_instance, $old_instance ) {
        $instance                   = $old_instance;
        $instance['title']          = strip_tags( $new_instance['title'] );
        $instance['number']         = (int) $new_instance['number'];
        $instance['tags_id']        = (int) $new_instance['tags_id'];
        $instance['thumbnail']      = $new_instance['thumbnail'];
        $this->flush_widget_cache();

        $alloptions = wp_cache_get( 'alloptions', 'options' );
        if ( isset($alloptions['widget_tagged_posts']) )
            delete_option('widget_tagged_posts');

        return $instance;
    }

    public function flush_widget_cache() {
        wp_cache_delete('widget_tag_posts', 'widget');
    }

    public function form( $instance ) {

        $title      = isset( $instance['title'] ) ? esc_attr( $instance['title'] ) : '';
        $number     = isset( $instance['number'] ) ? absint( $instance['number'] ) : 5;
        $tags_id    = isset( $instance['tags_id'] ) ? absint( $instance['tags_id'] ) : 1;
        $thumbnail  = isset( $instance['thumbnail'] ) ? $instance['thumbnail'] : false; 
        ?>

        <p>
            <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:' ); ?></label>
            <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo $title; ?>" />
        </p>

        <p>
            <label for="<?php echo $this->get_field_id( 'number' ); ?>"><?php _e( 'Number of posts to show:' ); ?></label>
            <input id="<?php echo $this->get_field_id( 'number' ); ?>" name="<?php echo $this->get_field_name( 'number' ); ?>" type="text" value="<?php echo $number; ?>" size="3" />
        </p>

        <p>
            <label for="<?php echo $this->get_field_id('tags_id'); ?>"><?php _e( 'Tag Name:' )?></label>
            <select id="<?php echo $this->get_field_id('tags_id'); ?>" name="<?php echo $this->get_field_name('tags_id'); ?>">
                <?php 
                $this->tags = get_tags();
                foreach ( $this->tags as $tag ) {
                    $selected = ( $tag->term_id == esc_attr( $tags_id ) ) ? ' selected = "selected" ' : '';
                    $option = '<option '.$selected .'value="' . $tag->term_id;
                    $option = $option .'">';
                    $option = $option .$tag->name;
                    $option = $option .'</option>';
                    echo $option;
                }
                ?>
            </select>
        </p>

        <p>
            <label for="<?php echo $this->get_field_id('thumbnail'); ?>"><?php _e( 'Hide post thumbnail' ); ?></label>
            <?php $checked = ( $thumbnail ) ? ' checked=\"checked\" ' : ''; ?>
            <input type="checkbox" id="<?php echo $this->get_field_id( 'thumbnail' ); ?>" name="<?php echo $this->get_field_name( 'thumbnail' ); ?>" value="true" <?php echo $checked; ?> />    
        </p>

    <?php
    }

}

add_action( 'widgets_init', function () {
    register_widget( 'Tagged_Posts' );
});

Here is a screenshot of how it looks back end

enter image description here

EDIT

From your comments

I would like the widget you provided to get random PAGES from specific tags INSTEAD OF POSTS. My pages are tagged and I thought I would just be able to edit your code to do just that but I can’t seem to figure out what to replace.

You need to change the query arguments to include the orderby and post_type parameters to get the desidered output

Change the query arguments to the following

$query_args = [
'post_type' => 'page',
'orderby' => 'rand',
'posts_per_page' => $number,
'tag_id' => $tags_id,
];