Since the security release SA-CORE-2019-003 (https://www.drupal.org/node/3034490) serialized objects stored in product 'options' are no longer able to be unserialized correctly and product pages can no longer be viewed as a PHP error is thrown:
Notice: Drupal\shopify\Form\ShopifyVariantOptionsForm::buildForm(): The script tried to execute a method or access a property of an incomplete object. Please ensure that the class definition "stdClass" of the object you are trying to operate on was loaded _before_ unserialize() gets called or provide a __autoload() function to load the class definition in Drupal\shopify\Form\ShopifyVariantOptionsForm->buildForm()

The line from the security release affecting this issue on Drupal 8.5 is https://github.com/drupal/core/commit/59396df3e7d8c304b2de91a1b34835a874...

According to http://php.net/manual/en/function.unserialize.php the allowed_classes basically instantiates all classes as __PHP_Incomplete_Class

This affects sites running Drupal 8.6 too as the same line is in that release too.

I assume the object needs to be stored in that options product property so there's no way around that, and the security patch still needs to be applied, so the only way around this issue that I can see at the moment is to try and turn the object into a 'complete' one.

Not sure whether to mark this as Critical or Major as technically product pages are no longer able to be accessed and pages are broken.

Comments

oldspot created an issue. See original summary.

oldspot’s picture

I managed to quickly patch the site by adding this code in the "src/Form/ShopifyVariantOptionsForm.php" file:

  /**
   * Fix Incomplete Object PHP error caused by SA-CORE-2019-003 security release which
   * uses unserialize($values, ['allowed_classes' => FALSE]);
   * @param $object
   * @return mixed
   */
  private function fixIncompleteObject($object) {
    if (!is_object($object) && gettype ($object) == 'object') {
      // Serialize then unserialize the object back without the allowed_classes FALSE option.
      return ($object = unserialize(serialize($object)));
    }
    return $object;
  }

And calling that function from the buildForm function:

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state, ShopifyProduct $product = NULL) {
    // Disable caching of this form.
    $form['#cache']['max-age'] = 0;
    $options = $product->options->get(0)->toArray();
    foreach ($options as &$option) {
      $option = $this->fixIncompleteObject($option);
    }
    ...

I'll try and create a patch from it (unless someone else has a better fix for it) once I fix a checkout issue that got introduced as well last night :(

oldspot’s picture

StatusFileSize
new1.42 KB

Created a quick patch from above code. Seems to work fine on our site and it brought back the product pages.

xpersonas’s picture

I can confirm this issue after upgrading. I'm using alpha3 too. alpha 4 breaks my site.

I was just about to post the exact same patch when I found yours. I can confirm that it fixes the error.

bdawg8569’s picture

I had this same issue on drupal 8.6 and after the move to 8.6.10. I fixed it on my local copy and thought it was all good, only to find that it didn't work on my production server when I pushed it up. After closer inspection, this fix will NOT work if you are using PHP 7.2.

The conditions that are being used in the patch to determine when to serialize/unserialize are using is_object() and there was a change to how this works in php 7.2

"is_object() now returns TRUE for unserialized objects without a class definition (class of __PHP_Incomplete_Class). Previously FALSE was returned."

To fix this, I simply changed the condition for the fixIncompleteObject function:

/**
   * Fix Incomplete Object PHP error caused by SA-CORE-2019-003 security release which
   * uses unserialize($values, ['allowed_classes' => FALSE]); and thus turns the unserialized
   * object into a "__PHP_Incomplete_Class" object.
   * @param $object
   * @return mixed
   */
  private function fixIncompleteObject($object) {
    if(get_class($object) === "__PHP_Incomplete_Class") {
      return ($object = unserialize(serialize($object)));
    }
    return $object;
  }
Asacolips’s picture

Status: Active » Needs review
StatusFileSize
new1.67 KB

Rerolled the patch against 8.x-1.x-dev and implemented the change in #5. In addition, the patch from #3 introduced a new $option variable that was passed by reference and conflicts with a different $option variable later in the code. I renamed that to $option_obj to restore functionality for multi-variant product forms.

xpersonas’s picture

StatusFileSize
new27.61 KB

I'm not sure what has changed, but I was having the same issues described earlier, but recently I found that my variant (options) select list was not working on my products. Debugging, I noticed I was no longer getting the incomplete object. So it seemed I didn't need this patch anymore.

Now it seemed like for what ever reason, I couldn't access the options of the product entity. So I added a getOptions() method to the entity, and I'm calling that now in my ShopifyVariantOptionsForm.php. Seems to work.

It's still related to this issue enough that I wanted to put the patch here in case it helps any one else.

xpersonas’s picture

Apologies, disregard #7. Patched the wrong version. I'm still using the alpha3 version.

abaier’s picture

Today I did an upgrade of Drupal core from 8.5.3 to 8.7.3 and noticed that my product pages were also missing their options fields. I am using shopify 8.x-1.0-alpha3+16-dev.

Patch #7 did not work for me, so I tried #8, which eliminated the warnings, but brought back only ONE options field per product. Sometimes we have multiple, i.e. size and color.

I then changed the follwing in the patch, which worked out so far:

$options[0] = $this->values["options"]["x-default"][0];
changed to
$options = $this->values["options"]["x-default"];

I am no coder actually, so please correct me, if this could cause 'damage' ;)

Before, I got the following warnings:

Warning: unserialize() expects parameter 1 to be string, object given in Drupal\Core\Field\Plugin\Field\FieldType\MapItem->setValue() (line 68 of core/lib/Drupal/Core/Field/Plugin/Field/FieldType/MapItem.php).
Drupal\Core\Field\Plugin\Field\FieldType\MapItem->setValue(Object, ) (Line: 200)
Drupal\Core\TypedData\TypedDataManager->getPropertyInstance(Object, 0, Object) (Line: 82)
Drupal\Core\Field\FieldTypePluginManager->createFieldItem(Object, 0, Object) (Line: 41)
Drupal\Core\Field\FieldItemList->createItem(0, Object) (Line: 66)
Drupal\Core\TypedData\Plugin\DataType\ItemList->setValue(Array, ) (Line: 107)
Drupal\Core\Field\FieldItemList->setValue(Array, ) (Line: 200)
Drupal\Core\TypedData\TypedDataManager->getPropertyInstance(Object, 'options', Array) (Line: 74)
Drupal\Core\Field\FieldTypePluginManager->createFieldItemList(Object, 'options', Array) (Line: 604)
Drupal\Core\Entity\ContentEntityBase->getTranslatedField('options', 'x-default') (Line: 1058)
Drupal\Core\Entity\ContentEntityBase->__get('options') (Line: 35)
Drupal\shopify\Form\ShopifyVariantOptionsForm->buildForm(Array, Object, Object)
call_user_func_array(Array, Array) (Line: 519)
Drupal\Core\Form\FormBuilder->retrieveForm('shopify_variant_options_form', Object) (Line: 276)
Drupal\Core\Form\FormBuilder->buildForm('shopify_variant_options_form', Object) (Line: 217)
Drupal\Core\Form\FormBuilder->getForm('Drupal\shopify\Form\ShopifyVariantOptionsForm', Object) (Line: 65)
Drupal\shopify\ShopifyProductViewBuilder->alterBuild(Array, Object, Object, 'full') (Line: 295)
Drupal\Core\Entity\EntityViewBuilder->buildMultiple(Array) (Line: 242)
Drupal\Core\Entity\EntityViewBuilder->build(Array)
call_user_func(Array, Array) (Line: 378)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 195)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 226)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 582)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 227)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 117)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 111)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch('kernel.view', Object) (Line: 156)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 68)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 57)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 52)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 693)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)

Warning: Invalid argument supplied for foreach() in Drupal\shopify\Form\ShopifyVariantOptionsForm->buildForm() (line 57 of modules/shopify/src/Form/ShopifyVariantOptionsForm.php).
Drupal\shopify\Form\ShopifyVariantOptionsForm->buildForm(Array, Object, Object)
call_user_func_array(Array, Array) (Line: 519)
Drupal\Core\Form\FormBuilder->retrieveForm('shopify_variant_options_form', Object) (Line: 276)
Drupal\Core\Form\FormBuilder->buildForm('shopify_variant_options_form', Object) (Line: 217)
Drupal\Core\Form\FormBuilder->getForm('Drupal\shopify\Form\ShopifyVariantOptionsForm', Object) (Line: 65)
Drupal\shopify\ShopifyProductViewBuilder->alterBuild(Array, Object, Object, 'full') (Line: 295)
Drupal\Core\Entity\EntityViewBuilder->buildMultiple(Array) (Line: 242)
Drupal\Core\Entity\EntityViewBuilder->build(Array)
call_user_func(Array, Array) (Line: 378)
Drupal\Core\Render\Renderer->doRender(Array, ) (Line: 195)
Drupal\Core\Render\Renderer->render(Array, ) (Line: 226)
Drupal\Core\Render\MainContent\HtmlRenderer->Drupal\Core\Render\MainContent\{closure}() (Line: 582)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 227)
Drupal\Core\Render\MainContent\HtmlRenderer->prepare(Array, Object, Object) (Line: 117)
Drupal\Core\Render\MainContent\HtmlRenderer->renderResponse(Array, Object, Object) (Line: 90)
Drupal\Core\EventSubscriber\MainContentViewSubscriber->onViewRenderArray(Object, 'kernel.view', Object)
call_user_func(Array, Object, 'kernel.view', Object) (Line: 111)
Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch('kernel.view', Object) (Line: 156)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 68)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 57)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 52)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 693)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
gustavowal’s picture

We've had an issue where a product with no options was giving a fatal error when accessing the page.
This was due to shopify/src/Form/ShopifyVariantOptionsForm.php L35 $options = $product->getOptions(); getting product options not as an multidimensional array. When '#title' => t($option->name), was called, core/lib/Drupal/Core/StringTranslation/TranslatableMarkup.php L130 if (!is_string($string)) was getting TRUE cause $string was null and was throwing an exception.

Managed to temporarily fix this by applying https://www.drupal.org/files/issues/2019-04-01/2719553-26-null_for_strin... but that would show the Product Option Selector even when the product didn't had any.

I got this fixed more correctly by checking for an array before returning on function getOptions().

abaier’s picture

Thanks for patch #10! Works as expected.

rho_’s picture

Patch #10 worked for me as well.

bobbygryzynger’s picture

With the patch in #10 $product->getOptions() returns a serialized array which causes the options rendering to fail because it passes a non-string for translation which causes the error (which seems like it should be the expected behavior).

Here, it looks like the options should be unserialized at some point before processing them.

bobbygryzynger’s picture

Status: Needs review » Needs work
lobodakyrylo’s picture

This patch for dev version of the module

joe huggans’s picture

I am also seeing this error when syncing products, I seem to have patched this by adding the following to the shopify_sync_products() function in shopify.module file after the line

if ($entity instanceof ShopifyProduct) {

foreach ($product->options as $key => $option) {
    if ($option instanceof stdClass) {
      $product->options[$key] = (array) $product->options[$key];
   }
}
bobbygryzynger’s picture

Status: Needs work » Needs review
StatusFileSize
new1.43 KB
new1.24 KB

I'm still seeing the issue with serialized options mentioned in #13. The attached patch attempts to unserialize the options if they're not already an array.

bobbygryzynger’s picture

Version: 8.x-1.0-alpha3 » 8.x-1.x-dev
Issue tags: +Needs update path
StatusFileSize
new3.38 KB

The attached patch stores all options as a serialized array rather than a PHP object. This will need a post_update operation to resync all products since the product option storage will need to be altered.

bobbygryzynger’s picture

Status: Needs review » Needs work

Setting to "needs work" to deal with update path.

bobbygryzynger’s picture

Saving credit.

bobbygryzynger’s picture

Status: Needs work » Needs review
StatusFileSize
new5.2 KB
new1.76 KB

Adds the post_update hook to resync all of a site's products.

  • bobbygryzynger authored cbd9e38 on 8.x-1.x
    Issue #3034741 by bobbygryzynger, xpersonas, oldspot, Asacolips,...
bobbygryzynger’s picture

Status: Needs review » Fixed
Issue tags: -Needs update path

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.