Quantcast
Channel: WordPress.org Forums » All Topics
Viewing all articles
Browse latest Browse all 59525

Major flaws breaking JSON data, suggested fix.

$
0
0

Replies: 0

Hello!

This is a great plugin, however I’ve unfortunately found some concerning major issues when updating from version 3.3.7 to 3.3.9.

When updating posts with JSON stored in the post_content column escaping stashes are removed. For example I am using a plugin called TablePress which stores content that may look like this:

[["test cell 1","test cell 2"],["test cell 3","<a href=\"/uploads/2020/02/example.png\">Link</a>"]]

After doing a replace and update all links the data looks like this:

[["test cell 1","test cell 2"],["test cell 3","<a href="/uploads/2020/02/new-example.png">Link</a>"]]

Where the back slashes for the href quotes are removed.

The problem lies in classes/replacer.php.

First the function isJSON (line 567) will ALWAYS return false. This is what it looks like:

private function isJSON($content)
  {
    if (is_array($content) || is_object($content))
      return false; // can never be.

    $json = json_decode($content);
    $json = is_string($content) && json_decode($content);

    return $json && $content != $json;
 }

So the $json variable is being overwritten as a boolean and will always be not equal to $content. I think it should look like this:

  private function isJSON($content)
  {
    if (is_array($content) || is_object($content))
      return false; // can never be.

    $json = json_decode($content);
    $isjson = is_string($content) && json_decode($content);

    return $isjson && $content != $json;
  }

Next in the replaceContent function (line 503) at the bottom there is a check that does $content = json_encode($content);. However since every if statement before it has a return it will never reach that part of the function if the content is JSON.

I think this:

private function replaceContent($content, $search, $replace)
  {
    //$is_serial = false;
    $content = maybe_unserialize($content);
    $isJson = $this->isJSON($content);

    if ($isJson)
    {
      $content = json_decode($content);
    }

    if (is_string($content))  // let's check the normal one first.
    {
      return str_replace($search, $replace, $content);
    }
    elseif (is_wp_error($content)) // seen this.
    {
       return $content;  // do nothing.
    }
    elseif (is_array($content) ) // array metadata and such.
    {
      foreach($content as $index => $value)
      {
        $content[$index] = $this->replaceContent($value, $search, $replace); //str_replace($value, $search, $replace);
      }
      return $content;
    }
    elseif(is_object($content)) // metadata objects, they exist.
    {
      foreach($content as $key => $value)
      {
        $content->{$key} = $this->replaceContent($value, $search, $replace); //str_replace($value, $search, $replace);
      }
      return $content;
    }

    if ($isJson) // convert back to JSON, if this was JSON. Different than serialize which does WP automatically.
    {
      Log::addDebug('Value was found to be JSON, encoding');
      $content = json_encode($content);
    }

    return $content;
}

Should look like this:

private function replaceContent($content, $search, $replace)
  {
    //$is_serial = false;
    $content = maybe_unserialize($content);
    $isJson = $this->isJSON($content);

    if ($isJson)
    {
      $content = json_decode($content);
    }

    if (is_string($content))  // let's check the normal one first.
    {
      $content = str_replace($search, $replace, $content);
    }
    elseif (is_wp_error($content)) // seen this.
    {
      // do nothing.
    }
    elseif (is_array($content) ) // array metadata and such.
    {
      foreach($content as $index => $value)
      {
        $content[$index] = $this->replaceContent($value, $search, $replace); //str_replace($value, $search, $replace);
      }
    }
    elseif(is_object($content)) // metadata objects, they exist.
    {
      foreach($content as $key => $value)
      {
        $content->{$key} = $this->replaceContent($value, $search, $replace); //str_replace($value, $search, $replace);
      }
    }

    if ($isJson) // convert back to JSON, if this was JSON. Different than serialize which does WP automatically.
    {
      Log::addDebug('Value was found to be JSON, encoding');
      $content = json_encode($content);
      $content = wp_slash($content);
    }

    return $content;
}

Finally you removed your custom insert SQL around line 474 and replaced $wpdb->query(...) with wp_update_post(...). It seems that the wp_update_post function uses stripslashes or something of that sort. This brings us back to our main problem of slashes being removed from our JSON content.

To fix this I added this as a workaround on line 541 in the replaceContent function after the json_encode is called:

$content = wp_slash($content);

Note this also happens for other WordPress functions such as update_metadata() if you happen to be using those anywhere – didn’t have time to check myself. (See this WordPress Stackexchange question as reference: WordPress is stripping escape backslashes from JSON strings in post_meta)

Anyway I am not sure if this is the correct solution or how it may affect other use cases. I doubt I am anywhere as familiar with the plugin as you the Authors are but it seems to have resolved the issue for me.

I hope this helps and that there is a patch in the next update. Again thanks for the awesome work you’ve put into this plugin 🙂

  • This topic was modified 29 minutes ago by patternmb. Reason: Used wrong function "wp_update_meta". Changed to "wp_update_post"

Viewing all articles
Browse latest Browse all 59525

Trending Articles