Synopsis

I'm making a new website for a client of mine, Deborah, director of Dotted & Crossed - a legal writing studio based in The Algarve, Portugal. We want the new website to represent her new approach to legal work which is, in a nutshell, trading legal jargon for talk you and I can understand without having to get a degree in law.

One of her requirements is to have individual pages for each service she offers with a menu on a left-side bar pointing to each service.

I'm using WordPress for this project so this task can be done with plugins, widgets, and custom code which is what I'm choosing. I'm picking the Pages Widget for this as it does 90% of what is required already - displaying links to pages, excluding predefined ones. The remaining 10% is listing in the menu all but the current page, i.e, we're looking at the Legal Translation service, we don't want it to be displayed on the sidebar menu.

Application

The default setup is simple, you give your widget a title, choose how you want it to sort the items on the menu, and most importantly you input all the page IDs you want to exclude but the services (number 7 is Home page).

As you can see, there is no default option to exclude current page ID, that is, the page ID of the page you're looking at in that present moment. To implement this we need to edit WordPress's widget functionality written in PHP code.

A quick search shows you that this can be accomplished by overwriting the widget code in the file functions.php which I happen to be familiar with and use it quite often, to add custom functionality to themes (or child-themes).

First you want to locate your functions.php file in your active theme (parent or child), open it up and get ready for some serious copy-paste action. Next grab the pages widget source code and copy its contents over to functions.php. Then you'll want to declare a global $wp_query; :https://developer.wordpress.org/reference/classes/wp_query/ . I declared it above the $title filtering variable like so (line 14):

 public function widget( $args, $instance ) {
  $default_title = __( 'Pages' );
  $title         = ! empty( $instance['title'] ) ? $instance['title'] : $default_title;

  /**
   * Filters the widget title.
   *
   * @since 2.6.0
   *
   * @param string $title    The widget title. Default 'Pages'.
   * @param array  $instance Array of settings for the current widget.
   * @param mixed  $id_base  The widget ID.
   */
  global $wp_query;
  $title = apply_filters( 'widget_title', $title, $instance, $this->id_base );

After declaring the variable you'll want to make a query for the current page which is a "post" object, and store it's member variable ID on a variable that you can call $self , like:

$self = $wp_query->post->ID;

If you var_dump($self) you can see the current page ID which is an integer, by visiting the website and page where you've enabled the widget, that is after you register the widget.

Finally, you have to join the $self variable to the list of excluded pages. To do that, locate the line that has the $exclude variable and functionality and you'll have to concatenate the contents of $self - an integer number, with the ID's you specify in the first step of this tutorial, this way:

Unmodified $exclude:

$exclude = empty( $instance['exclude'] ) ? '' : $instance['exclude'];

With concatenated $self in front:

$exclude = empty( $instance['exclude'] ) ? '' : $instance['exclude'].",".$self;

Note how we're using the concatenation operator (.), adding a comma (,), and concatenating the $self variable. What will happen is, when you load the page that has the widget on the left side bar listing the pages, it will not only exclude what you specified in the first step: "3,7,37..." but the value of $self. If $self is 50 for the Legal Translation page, then the excluded list will be "3,7,37...50", effectively not listing the current page (service).

The last thing to go on the functions.php file is the widget registration:

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

Full code snippet:

<?php
/**
 * Child Theme Functions File
 */

/**
 * To extend or modify the functions present in the parent theme, both in functions.php or in extras.php
 * files, copy the function to this file and modify it according to your needs.
 *
 * Keep in mind that this file (unlike what happens with the style.css file) will load BEFORE the parent
 * theme functions.php file, which is why any function declared both here and in the original functions.php
 * file will be overriden for whatever you include here.
 */

// Happy editing...
/**
 * Widget API: WP_Widget_Pages class
 *
 * @package WordPress
 * @subpackage Widgets
 * @since 4.4.0
 */

/**
 * Core class used to implement a Pages widget.
 *
 * @since 2.8.0
 *
 * @see WP_Widget
 */
class WP_Widget_Pages_Mod extends WP_Widget {

 /**
  * Sets up a new Pages widget instance.
  *
  * @since 2.8.0
  */
 public function __construct() {
  $widget_ops = array(
   'classname'                   => 'widget_pages',
   'description'                 => __( 'A list of your site&#8217;s Pages.' ),
   'customize_selective_refresh' => true,
  );
  parent::__construct( 'pages', __( 'Pages' ), $widget_ops );
 }

 /**
  * Outputs the content for the current Pages widget instance.
  *
  * @since 2.8.0
  *
  * @param array $args     Display arguments including 'before_title', 'after_title',
  *                        'before_widget', and 'after_widget'.
  * @param array $instance Settings for the current Pages widget instance.
  */
 public function widget( $args, $instance ) {
  $default_title = __( 'Pages' );
  $title         = ! empty( $instance['title'] ) ? $instance['title'] : $default_title;

  /**
   * Filters the widget title.
   *
   * @since 2.6.0
   *
   * @param string $title    The widget title. Default 'Pages'.
   * @param array  $instance Array of settings for the current widget.
   * @param mixed  $id_base  The widget ID.
   */
  global $wp_query;
  $title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
  $self = $wp_query->post->ID;

  $sortby  = empty( $instance['sortby'] ) ? 'menu_order' : $instance['sortby'];
  $exclude = empty( $instance['exclude'] ) ? '' : $instance['exclude'].",".$self;

  if ( 'menu_order' === $sortby ) {
   $sortby = 'menu_order, post_title';
  }

  $out = wp_list_pages(
   /**
    * Filters the arguments for the Pages widget.
    *
    * @since 2.8.0
    * @since 4.9.0 Added the `$instance` parameter.
    *
    * @see wp_list_pages()
    *
    * @param array $args     An array of arguments to retrieve the pages list.
    * @param array $instance Array of settings for the current widget.
    */
   apply_filters(
    'widget_pages_args',
    array(
     'title_li'    => '',
     'echo'        => 0,
     'sort_column' => $sortby,
     'exclude'     => $exclude,
    ),
    $instance
   )
  );

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

   $format = current_theme_supports( 'html5', 'navigation-widgets' ) ? 'html5' : 'xhtml';

   /** This filter is documented in wp-includes/widgets/class-wp-nav-menu-widget.php */
   $format = apply_filters( 'navigation_widgets_format', $format );

   if ( 'html5' === $format ) {
    // The title may be filtered: Strip out HTML and make sure the aria-label is never empty.
    $title      = trim( strip_tags( $title ) );
    $aria_label = $title ? $title : $default_title;
    echo '<nav role="navigation" aria-label="' . esc_attr( $aria_label ) . '">';
   }
   ?>

   <ul>
    <?php echo $out; ?>
   </ul>

   <?php
   if ( 'html5' === $format ) {
    echo '</nav>';
   }

   echo $args['after_widget'];
  }
 }

 /**
  * Handles updating settings for the current Pages widget instance.
  *
  * @since 2.8.0
  *
  * @param array $new_instance New settings for this instance as input by the user via
  *                            WP_Widget::form().
  * @param array $old_instance Old settings for this instance.
  * @return array Updated settings to save.
  */
 public function update( $new_instance, $old_instance ) {
  $instance          = $old_instance;
  $instance['title'] = sanitize_text_field( $new_instance['title'] );
  if ( in_array( $new_instance['sortby'], array( 'post_title', 'menu_order', 'ID' ), true ) ) {
   $instance['sortby'] = $new_instance['sortby'];
  } else {
   $instance['sortby'] = 'menu_order';
  }

  $instance['exclude'] = sanitize_text_field( $new_instance['exclude'] );

  return $instance;
 }

 /**
  * Outputs the settings form for the Pages widget.
  *
  * @since 2.8.0
  *
  * @param array $instance Current settings.
  */
 public function form( $instance ) {
        // Defaults.
  $instance = wp_parse_args(
   (array) $instance,
   array(
    'sortby'  => 'post_title',
    'title'   => '',
    'exclude' => '',
   )
  );
  ?>
  <p>
   <label for="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>"><?php _e( 'Title:' ); ?></label>
   <input class="widefat" id="<?php echo esc_attr( $this->get_field_id( 'title' ) ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'title' ) ); ?>" type="text" value="<?php echo esc_attr( $instance['title'] ); ?>" />
  </p>

  <p>
   <label for="<?php echo esc_attr( $this->get_field_id( 'sortby' ) ); ?>"><?php _e( 'Sort by:' ); ?></label>
   <select name="<?php echo esc_attr( $this->get_field_name( 'sortby' ) ); ?>" id="<?php echo esc_attr( $this->get_field_id( 'sortby' ) ); ?>" class="widefat">
    <option value="post_title"<?php selected( $instance['sortby'], 'post_title' ); ?>><?php _e( 'Page title' ); ?></option>
    <option value="menu_order"<?php selected( $instance['sortby'], 'menu_order' ); ?>><?php _e( 'Page order' ); ?></option>
    <option value="ID"<?php selected( $instance['sortby'], 'ID' ); ?>><?php _e( 'Page ID' ); ?></option>
   </select>
  </p>

  <p>
   <label for="<?php echo esc_attr( $this->get_field_id( 'exclude' ) ); ?>"><?php _e( 'Exclude:' ); ?></label>
   <input type="text" value="<?php echo esc_attr( $instance['exclude'] ); ?>" name="<?php echo esc_attr( $this->get_field_name( 'exclude' ) ); ?>" id="<?php echo esc_attr( $this->get_field_id( 'exclude' ) ); ?>" class="widefat" />
   <br />
   <small><?php _e( 'Page IDs, separated by commas.' ); ?></small>
  </p>
  <?php
 }

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

Hope this serves you well!