Make WordPress Core

Opened 3 years ago

Last modified 3 years ago

#53625 reopened defect (bug)

The 'explode' function does not work on widget block editor.

Reported by: subrataemfluence's profile subrataemfluence Owned by:
Milestone: Priority: normal
Severity: major Version: 5.8
Component: Widgets Keywords: has-screenshots 2nd-opinion reporter-feedback
Focuses: administration Cc:

Description

The explode() function does not seem to be working on the new Widget block editor. I am testing it on the RC-2 version.

I have created a simple widget that accepts Title, Description, and multiple selections from a Checkbox list items (Screenshots attached)

On selecting checkboxes, the values form a comma-separated string (e.g. 123456, 9822310, 457790 etc.) and stored in a textbox.

Inside the update() function, I am exploding this string and storing the array as $instance['groups'] element, which in turn gets saved in the database (wp_options table).

The functionality works as expected if I have the widgets-block-editor support disabled. But when enabled, the explode() function is not working and nothing is in $instance['groups].

<?php
public function update( $new_instance, $old_instance ) {
   $instance = $old_instance;
                
   $instance['title']  = strip_tags( $new_instance['title'] );
   $instance['about']  = strip_tags( $new_instance['about'] );
                
   $groups             = $new_instance['groups'];

   // the following line is not working and creating an empty array
   // when widgets-block-editor support is enabled.
   $instance['groups'] = array_pop ( explode( ',', $groups ) );

                
   return $instance;
}

Database:

a:2:{i:2;a:3:{s:5:"title";s:15:"Product Channel";s:5:"about";s:41:"When you subscribe to our Product Channel";s:6:"groups";a:3:{i:0;s:6:"237500";i:1;s:6:"457844";}}s:12:"_multiwidget";i:1;}

However, the values get stored with block editor support enabled as a comma-separated string without the explode() function, i.e.

<?php
public function update( $new_instance, $old_instance ) {
   $instance = $old_instance;
                
   $instance['title']  = strip_tags( $new_instance['title'] );
   $instance['about']  = strip_tags( $new_instance['about'] );
   $instance['groups'] = $new_instance['groups'];

   return $instance;
}

Attachments (3)

classic-widget-page.png (29.5 KB) - added by subrataemfluence 3 years ago.
The Widget in classic mode with block editor support disabled. The comma-separated IDs are rightly converted into an array and saved in the database
widget-block-editor.png (27.8 KB) - added by subrataemfluence 3 years ago.
The Widget with block editor support enabled. The explode() function does not work. Getting an empty array
Zrzut ekranu 2021-07-9 o 17.50.42.png (1.6 MB) - added by zieladam 3 years ago.

Download all attachments as: .zip

Change History (17)

@subrataemfluence
3 years ago

The Widget in classic mode with block editor support disabled. The comma-separated IDs are rightly converted into an array and saved in the database

@subrataemfluence
3 years ago

The Widget with block editor support enabled. The explode() function does not work. Getting an empty array

#1 @subrataemfluence
3 years ago

Record in the database with widgets-block-editor support disabled:

a:2:{i:2;a:3:{s:5:"title";s:15:"Product Channel";s:5:"about";s:41:"When you subscribe to our Product Channel";
s:6:"groups";a:3:{i:0;s:6:"237500";i:1;s:6:"457844";}
}s:12:"_multiwidget";i:1;}

And this is what I get with widgets-block-editor support enabled:

a:2:{i:2;a:3:{s:5:"title";s:15:"Product Channel";s:5:"about";s:41:"When you subscribe to our Product Channel";
s:6:"groups";
s:0:"";}s:12:"_multiwidget";i:1;}

Please notice, there is nothing under groups in the second record.

This ticket was mentioned in Slack in #core by subratasarkar. View the logs.


3 years ago

#3 follow-up: @andraganescu
3 years ago

  • Keywords reporter-feedback added

HI @subrataemfluence thanks for posting the problem, it's very helpful. Reading the ticket, I believe it's unlikely that explode is the problem. Most likely something about $groups = $new_instance['groups']; could be different with the block editor enabled. I'd like to ask you for three things, please:

1) Can you add verbatim what you send to register_widget for this widget?

2) Could you var_dump, or inspect in xcode, $groups = $new_instance['groups']; with and without the block editor enabled? What is the value of $new_instancegroups? in these cases?

3) When is the comma-separated string formed? Clientside or after save?

Alternatively you could attach the entire widget code so we could test without the back and forth.

Thank you!

#4 in reply to: ↑ 3 @subrataemfluence
3 years ago

Hi Andrei thanks very much for looking into this! Yes, you are right, explode() might not be the only one. Because I faced the issue with this, I mentioned it.

3) The comma-separated string is formed clientside when I select/unselect the checkboxes.

I will work on 1 and 2 tomorrow and add as much information I can collect.

Replying to andraganescu:

HI @subrataemfluence thanks for posting the problem, it's very helpful. Reading the ticket, I believe it's unlikely that explode is the problem. Most likely something about $groups = $new_instance['groups']; could be different with the block editor enabled. I'd like to ask you for three things, please:

1) Can you add verbatim what you send to register_widget for this widget?

2) Could you var_dump, or inspect in xcode, $groups = $new_instance['groups']; with and without the block editor enabled? What is the value of $new_instancegroups? in these cases?

3) When is the comma-separated string formed? Clientside or after save?

Alternatively you could attach the entire widget code so we could test without the back and forth.

Thank you!

Last edited 3 years ago by subrataemfluence (previous) (diff)

#5 @subrataemfluence
3 years ago

Hi, @andraganescu here is the codebase. Please let me know if I need to provide anything else.

The widget is built as a separate file (widget.php) and has been required in a file called setup.php. This file also enqueues the JavaScript that I am using. The file structure is like this:

[ A ]
File:
wp-content/plugins/platform-subscribers/index.php

Content:

<?php
require_once 'setup.php';

[ B ]
File:
wp-content/plugins/platform-subscribers/setup.php

Content:

<?php
require_once 'widget.php';

function ps_load_widget() {
   $platform_subscriber = new PlatformSubscriber();
   register_widget( $platform_subscriber );
}

add_action( 'widgets_init', 'ps_load_widget' );

function enqueue_plugin_scripts() {
   wp_enqueue_script(
      'ps-settings',
      plugins_url( '/ps-settings.js', __FILE__ ),
      array( 'jquery' ),
      '1.0',
      false
   );
}

add_action( 'admin_enqueue_scripts', 'enqueue_plugin_scripts' );

[ C ]
File:
wp-content/plugins/platform-subscribers/widget.php

Content:

<?php
class PlatformSubscriber extends WP_Widget {
   public function __construct() {
      $id_base         = 'platform_subscriber';
      $widget_options  = array(
         'classname'   => 'platform_subscriber',
         'description' => 'Creates an email signup form for your visitors.'
      );
      $control_options = array(
         'width' => 400,
         'id_base' => $id_base
      );
                
      parent::__construct(
         $id_base,
         __( 'Platform Subscriber', 'ps_platform' ),
         $widget_options,
         $control_options
      );
   }
        
   /**
      * @param array $instance
      *
      * @return string|void
      * Widget backend.
   */
   public function form( $instance ) {
      $this->create_backend_form( $instance );
   }
        
   /**
      * @param array $new_instance
      * @param array $old_instance
      *
      * @return array
      *
      * Updating widget replacing old instance with new.
   */
   public function update( $new_instance, $old_instance ) {
      $instance = $old_instance;
                
      $instance['title']  = strip_tags( $new_instance['title'] );
      $instance['about']  = strip_tags( $new_instance['about'] );
                
      $groups             = $new_instance['groups'];
      $instance['groups'] = array_pop ( explode( ',', $groups ) );
                
      return $instance;
   }
        
   /**
      * @param array $args
      * @param array $instance
      *
      * Create the widget front-end.
      * Will add the actual functionality later
   */
   public function widget( $args, $instance ) {
      $title = apply_filters( 'widget_title', $instance['title'] );
                
      echo $args['before_widget'];
      if( ! empty( $title ) ) {
         echo $args['before_title'] . $title . $args['after_title'];
      }
                
      echo __( 'Hello World!', 'ps_platform' );
      echo $args['after_widget'];
   }
        
        
   /**
      * @param $instance
      * Creates the backend form
   */
   protected function create_backend_form( $instance ) {
      if( ! isset( $instance['title'] ) ) {
         $title = __( 'New Platform Title', 'ps_platform' );
      } else {
         $title = $instance['title'];
      }
                
      if( ! isset( $instance['about'] ) ) {
         $about = '';
      } else {
         $about = esc_html( $instance['about'] );
      }
                
      if( ! isset( $instance['groups'] ) ) {
         $selectedGroups = '';
      } else {
         $selectedGroups =  $instance['groups'];
      }
      ?>
                
      <p>
         <label for="<?= $this->get_field_id( 'title' ); ?>">
            <?php _e( 'Title ' ) ?>&nbsp;*
         </label>
         <input
            class="widefat"
            type="text"
            required
            id="<?= $this->get_field_id( 'title' ); ?>"
            name="<?= $this->get_field_name( 'title' ) ?>"
            value="<?= esc_attr( $title ) ?>"
         />
      </p>
      <p>
         <label for="<?= $this->get_field_id( 'about' ) ?>">
            <?php _e( 'Description' ) ?>
         </label>
         <textarea
            class="widefat"
            name="<?= $this->get_field_name( 'about' ) ?>"
            id="<?= $this->get_field_id( 'about' ) ?>"
            cols="30" rows="3" maxlength="150" required
            placeholder="Help text for visitor"><?= $about ?></textarea>
         </p>
         <div>
            <p>
               <input
                  type="checkbox"
                  id="<?= $this->get_field_id('online-conference-ticket') ?>"
                  name="<?= $this->get_field_name('online-conference-ticket') ?>"
                  value="396060"
                  onclick="updateSelection( this, <?= $this->number ?> );"
               />
               <label
                  id="label-<?= $this->get_field_id('online-conference-ticket') ?>"
                  for="<?= $this->get_field_id('online-conference-ticket') ?>"
                  onclick="updateSelection( this, <?= $this->number ?> );">
                  Online Conference Ticket
               </label>
            </p>
            <p>
               <input
                  type="checkbox"
                  id="<?= $this->get_field_id('marketing-platform-demo') ?>"
                  name="<?= $this->get_field_name('marketing-platform-demo') ?>"
                  value="237500"
                  onclick="updateSelection( this, <?= $this->number ?> );"
               />
               <label
                  id="label-<?= $this->get_field_id('marketing-platform-demo') ?>"
                  for="<?= $this->get_field_id('marketing-platform-demo') ?>"
                  onclick="updateSelection( this, <?= $this->number ?> );">
                  Marketing Platform Demo
               </label>
            </p>
            <p>
               <input
                  type="checkbox"
                  id="<?= $this->get_field_id('beer-online-conference') ?>"
                  name="<?= $this->get_field_name('beer-online-conference') ?>"
                  value="457844"
                  onclick="updateSelection( this, <?= $this->number ?> );"
               />
               <label
                  id="label-<?= $this->get_field_id('beer-online-conference') ?>"
                  for="<?= $this->get_field_id('beer-online-conference') ?>"
                  onclick="updateSelection( this, <?= $this->number ?> );">
                  Beer Online Conference
               </label>
            </p>
         </div>
         <p>
            <label for="<?= $this->get_field_id( 'groups' ) ?>">
               <?php _e( 'Selected groups' ) ?>&nbsp;*
            </label>
            <input
               class="widefat"
               type="text"
               required
               id="<?= $this->get_field_id( 'groups' ); ?>"
               name="<?= $this->get_field_name( 'groups' ) ?>"
               value="<?= esc_attr( $selectedGroups ) ?>"
            />
         </p>
                
         <?php
   }
}

[ D ]
File:
wp-content/plugins/platform-subscribers/ps-settings.js

Content:

function updateSelection( obj, widget_number ) {
    var selectedGroupsBoxId = 'widget-platform_subscriber-' + widget_number + '-groups';
    var parentDiv = jQuery(obj).closest( 'div' );
    var commaGroups = '';

    jQuery( parentDiv ).find( 'input:checked' ).each( function() {
        commaGroups = commaGroups + jQuery(this).attr('value') + ',';
    } );

    jQuery( '#' + selectedGroupsBoxId ).val( commaGroups );
}

#6 @zieladam
3 years ago

I don't think this is actually breaking anything, but it's worth pointing out that here:

<?php
$instance['groups'] = array_pop ( explode( ',', $groups ) );

A warning is logged:

Only variables can be passed by reference

Because array_pop expects to receive a variable, not a value.

#7 @zieladam
3 years ago

  • Milestone Awaiting Review deleted
  • Resolution set to invalid
  • Status changed from new to closed

The problem is a bug in the PlatformSubscriber widget. See the debugger screenshot above.

  1. The backend receives a value like "396060," and calls array_pop( explode( ",", $groups ) )
  2. explode() returns ["396060", ""]
  3. array_pop() pops ""
  4. An empty string of length 0 is stored in the database

I'll close this ticket because it does not seem to be a problem with the widgets editor. Feel free to bring up anything I may have missed.

#8 @subrataemfluence
3 years ago

  • Resolution invalid deleted
  • Status changed from closed to reopened
  • Summary changed from The 'explode' function does not work on widget block editor. to The 'explode' function or something else does not work on widget block editor.

Hi @zieladam thanks for looking into the array_pop() thing. It was a mistake which now I have corrected and no more empty string.

But I am almost certain that either the explode() functionality is not working or there is something else that is preventing it from working properly in widget block mode. Because everything works as expected when the block support is removed from the theme.

So I am reopening the ticket with more test cases and their results.

Here are my test cases:

<?php
function update( $old_instance, $new_instance ) {
   $instance = $old_instance;

   $instance['title']  = strip_tags($new_instance['title']);
   $instance['about']  = strip_tags($new_instance['about']);
   $instance['groups'] = $new_instance['groups'];

   return $instance;
}

In the above code, I am simply assigning the coma-separated value (100,200,300...) to $instance['groups'] and this saves fine both with and without the widgets-block-editor theme support. This also proves that the comma-separated values generated at the client-side are properly read.

Then I incorporated the explode() function:

<?php
function update( $old_instance, $new_instance ) {
   $instance = $old_instance;

   $instance['title']  = strip_tags($new_instance['title']);
   $instance['about']  = strip_tags($new_instance['about']);

   $instance['groups_string'] = $new_instance['groups'];
   $instance['groups_object'] = explode( ',', $new_instance['groups'] );

   return $instance;
}

The above snippet works perfectly fine when widgets-block-editor support is turned off. Here is the record that is saved in the database:

a: 2: {
   i: 2;a: 4: {
     s: 5: "title";s: 18: "New Platform Title";
     s: 5: "about";s: 0: "";
     s: 13: "groups_str";s: 20: "396060,237500,457844";
     s: 13: "groups";
            a: 3: {
	      i: 0;s: 6: "396060";
              i: 1;s: 6: "237500";
              i: 2;s: 6: "457844";
	    }
   }
   s: 12: "_multiwidget";i: 1;
}

But, whenever I turn it on, either the explode function is not working or something else happening in the background prevents the groups from being saved. The same code above ends up with the following:

a: 2: {
   i: 2;a: 4: {
     s: 5: "title";s: 18: "New Platform Title";
     s: 5: "about";s: 0: "";
     s: 13: "groups_str";s: 20: "396060,237500,457844";
     s: 13: "groups";N;
   }
   s: 12: "_multiwidget";i: 1;
}

There is another strange behavior that I have experienced with the following snippet:

<?php
function update( $old_instance, $new_instance ) {
   $instance = $old_instance;

   $instance['title']  = strip_tags($new_instance['title']);
   $instance['about']  = strip_tags($new_instance['about']);

   $instance['groups_str'] = $new_instance['groups'];
   $instance['groups'] = explode( ',', $new_instance['groups'] );

   return $instance;
}

With the theme support enabled, the output becomes:

a: 2: {
   i: 2;a: 4: {
      s: 5: "title";
      s: 18: "New Platform Title";
      s: 5: "about";s: 0: "";
      s: 10: "groups_str";a: 2: {
         i: 0;s: 6: "396060";
         i: 1;s: 6: "237500";
      }
      s: 6: "groups";N;
   }
   s: 12: "_multiwidget";i: 1;
}

The interesting thing in the above output is the value of groups_str. In the code, I have $instance['groups_str'] = $new_instance['groups']; meaning I am expecting the value to be saved as a string, i.e. 396060,237500, but surprisingly it is saving as an array. And the next line of code, which is supposed to save the array is actually saving nothing "groups";N;

And this also proves that the value generated by JavaScript is properly read even with the theme support enabled. Just to confirm, that the last snippet of code is working exactly the way I want when theme support is removed.

The question is why the same code is not working in the same way! At least I cannot see any logical issue in widget.php.

#9 @subrataemfluence
3 years ago

  • Summary changed from The 'explode' function or something else does not work on widget block editor. to The 'explode' function does not work on widget block editor.

#10 @subrataemfluence
3 years ago

  • Keywords 2nd-opinion added; reporter-feedback removed

#11 @zieladam
3 years ago

  • Resolution set to invalid
  • Status changed from reopened to closed

@subrataemfluence The signature of update in WordPress core is as follows:

function update( $new_instance, $old_instance );

However, in your sample, I see it's

function update( $old_instance, $new_instance ) 

Notice how the arguments are reversed. So when you do $instance['groups'] = explode( ',', $new_instance['groups'] ); the second argument comes from the old widget instance and is already an array.

When I switch the arguments order, it works like a charm.

I did not get to the bottom of the discrepancy between the old editor and the new editor because that seems to be a problem only when the signature is wrong, but I'm happy to keep looking if something still isn't right. I am going to close this one again just to keep things tidy, but don't take it as me pushing for closure. Absolutely feel free to reopen if there are still some problems left to explore here.

#12 @zieladam
3 years ago

  • Keywords reporter-feedback added

#13 @subrataemfluence
3 years ago

Thanks for your detailed reply @zieladam . But IMO there is something wrong.

I am trying to shorten down the area of interest in the code and only with editor support enabled. Please look at the following code snippet:

<?php
public function update( $new_instance, $old_instance ) {
   $instance = $old_instance;
   $instance['title']  = strip_tags($new_instance['title']);
   $instance['about']  = strip_tags($new_instance['about']);

   // Please see the following lines...
   $instance['groups_str'] = $new_instance['groups'];
   $instance['groups'] = explode( ',', $new_instance['groups'] );
}

The above code creates the following record in the database:

a:2:{i:2;a:4:{s:5:"title";s:18:"New Platform Title";s:5:"about";s:0:"";s:10:"groups_str";a:2:{i:0;s:6:"237500";i:1;s:6:"457844";}s:6:"groups";N;}s:12:"_multiwidget";i:1;}

Please note, the record saved has an array with the groups ID, i.e.
"groups_str";a:2:{i:0;s:6:"237500";i:1;s:6:"457844";}

But the most noticeable thing here is $instance['groups_str'] is assigned the comma separated string value, not an array, but still it is saving the values as an array in the database!! While, I am expecting the $instance['groups'] to have the array saved against it, but all I get is an 'N'!

Moving forward, if I take out one line ($instance['groups'] = explode( ',', $new_instance['groups'] );) from the above snippet to make the function to read:

<?php
public function update( $new_instance, $old_instance ) {
   $instance = $old_instance;
   $instance['title']  = strip_tags($new_instance['title']);
   $instance['about']  = strip_tags($new_instance['about']);

   // Please see the following lines...
   $instance['groups_str'] = $new_instance['groups'];
}

the record saved in the database is:

a:2:{i:2;a:3:{s:5:"title";s:18:"New Platform Title";s:5:"about";s:2:"wd";s:10:"groups_str";N;}s:12:"_multiwidget";i:1;}

Please note. I did not change anything in the following line:
$instance['groups_str'] = $new_instance['groups'];.

I believe there are issues and could be inspected more. At least this is what I am experiencing so far.

My development environment:

WordPress version: Beta RC-4
Theme: 2021 (up to date)
Plugins: Widget plugin I am building

One more thing. The order of the arguments for update method was written wrong in my previous message, but they were correct in the code. Sorry for this confusion.

#14 @subrataemfluence
3 years ago

  • Resolution invalid deleted
  • Status changed from closed to reopened
Note: See TracTickets for help on using tickets.