Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config/pdk.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@
return plugin_basename(Pdk::getAppInfo()->path);
}),

'migrationDirectory' => factory(function (): string {
return rtrim(Pdk::getAppInfo()->path, '/') . '/src/Migration';
}),

'urlDocumentation' => value('https://developer.myparcel.nl/nl/documentatie/10.woocommerce.html'),
'urlReleaseNotes' => value('https://github.com/myparcelnl/woocommerce/releases'),

Expand Down
110 changes: 110 additions & 0 deletions src/Migration/2026_08_04_101714_restore_v6_options.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

declare(strict_types=1);

use MyParcelNL\Pdk\App\Installer\Migration\AbstractTimestampedMigration;
use MyParcelNL\Pdk\Facade\Pdk;
use MyParcelNL\Pdk\Settings\Contract\PdkSettingsRepositoryInterface;
use MyParcelNL\Pdk\Storage\Contract\StorageInterface;

return new class extends AbstractTimestampedMigration {
private const CURRENT_PREFIX = '_myparcelcom_';
private const LEGACY_PREFIX = '_myparcelnl_';

public function up(): void
{
$legacyVersion = get_option(self::LEGACY_PREFIX . 'installed_version', null);

if (! $this->isV6OrLater($legacyVersion)) {
return;
}

/** @var PdkSettingsRepositoryInterface $settingsRepository */
$settingsRepository = Pdk::get(PdkSettingsRepositoryInterface::class);
/** @var StorageInterface $storage */
$storage = Pdk::get(StorageInterface::class);

$storage->delete('settings_all');

foreach ($this->getLegacyOptionNames() as $legacyName) {
$this->restoreOption($legacyName, $settingsRepository, $storage);
}
}

/**
* @return string[]
*/
private function getLegacyOptionNames(): array
{
global $wpdb;

$query = $wpdb->prepare(
"SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s",
$wpdb->esc_like(self::LEGACY_PREFIX) . '%'
);

if (! is_string($query)) {
throw new \RuntimeException('Could not prepare legacy option query.');
}

$optionNames = $wpdb->get_col($query);

if (! empty($wpdb->last_error)) {
throw new \RuntimeException(sprintf('Could not read legacy options: %s', $wpdb->last_error));
}

if (! is_array($optionNames)) {
throw new \RuntimeException('Could not read legacy options.');
}

$optionNames = array_values(array_filter($optionNames, 'is_string'));
$versionKey = self::LEGACY_PREFIX . 'installed_version';

usort($optionNames, static function (string $left, string $right) use ($versionKey): int {
return ((int) ($left === $versionKey)) <=> ((int) ($right === $versionKey));
});

return $optionNames;
}

private function restoreOption(
string $legacyName,
PdkSettingsRepositoryInterface $settingsRepository,
StorageInterface $storage
): void {
$missing = new \stdClass();
$legacyValue = get_option($legacyName, $missing);

if ($legacyValue === $missing) {
return;
}

$currentName = self::CURRENT_PREFIX . substr($legacyName, strlen(self::LEGACY_PREFIX));

if (get_option($currentName, $missing) === $missing) {
$settingsRepository->store($currentName, $legacyValue);

if (get_option($currentName, $missing) === $missing) {
throw new \RuntimeException(sprintf('Could not restore option "%s".', $currentName));
}
}

$storage->delete('settings_' . $legacyName);

if (! delete_option($legacyName) && get_option($legacyName, $missing) !== $missing) {
throw new \RuntimeException(sprintf('Could not remove legacy option "%s".', $legacyName));
}
}

/**
* @param mixed $version
*
* @return bool
*/
private function isV6OrLater($version): bool
{
return is_string($version)
&& preg_match('/^(\d+)(?:\.|$)/', $version, $matches)
&& (int) $matches[1] >= 6;
}
};
17 changes: 0 additions & 17 deletions src/Service/WpInstallerService.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,6 @@ protected function getInstalledVersion(): ?string
return parent::getInstalledVersion() ?: $this->getLegacyInstalledVersion();
}

/**
* Override because a null version will re-trigger a migration or overwrite all options with defaults
* when deactivating and re-activating the plugin in WordPress.
*
* @param null|string $version
*
* @return void
*/
protected function updateInstalledVersion(?string $version): void
{
if (! $version) {
return;
}

parent::updateInstalledVersion($version);
}

/**
* This is not in the PDK config or the bootstrapper because it's legacy stuff.
*
Expand Down
55 changes: 55 additions & 0 deletions tests/Mock/MockWpdb.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,21 @@ final class MockWpdb
*/
public $prefix = 'wp_';

/**
* @var string
*/
public $options = 'wp_options';

/**
* @var string
*/
public $last_error = '';

/**
* @var null|string
*/
public $preparedOptionPattern;

/**
* @var array
*/
Expand Down Expand Up @@ -46,6 +61,46 @@ public function get_results(string $query): array
return [];
}

/**
* @param string $text
*
* @return string
*/
public function esc_like(string $text): string
{
return addcslashes($text, '_%\\');
}

/**
* @param string $query
* @param string $value
*
* @return string
*/
public function prepare(string $query, string $value): string
{
$this->preparedOptionPattern = $value;

return $query;
}

/**
* @param string $query
*
* @return string[]
*/
public function get_col(string $query): array
{
$prefix = rtrim(str_replace('\\_', '_', $this->preparedOptionPattern ?? ''), '%');

return array_values(array_filter(
array_keys(WordPressOptions::$options),
static function (string $optionName) use ($prefix): bool {
return 0 === strpos($optionName, $prefix);
}
));
}

/**
* @param string $query
*
Expand Down
15 changes: 14 additions & 1 deletion tests/Mock/WordPressOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ final class WordPressOptions
*/
public static function getOption(string $name, $default = false)
{
return self::$options[$name] ?? $default;
return array_key_exists($name, self::$options)
? self::$options[$name]
: $default;
}

/**
Expand All @@ -35,6 +37,17 @@ public static function updateOption($option, $value, $autoload = null): void
self::$options[$option] = $value;
}

public static function deleteOption(string $option): bool
{
if (! array_key_exists($option, self::$options)) {
return false;
}

unset(self::$options[$option]);

return true;
}

public static function reset(): void
{
self::$options = [];
Expand Down
8 changes: 6 additions & 2 deletions tests/Unit/EntryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,14 @@
->toBeString();
});

it('runs uninstall on deactivate', function () {
it('keeps settings when deactivated', function () {
WordPressOptions::updateOption('_myparcelcom_carrier', ['POSTNL' => ['enabled' => true]]);

MockWpActions::execute('deactivate_woocommerce-myparcel');

expect(MockWpActions::get('deactivate_woocommerce-myparcel'))->toBe([]);
expect(MockWpActions::toArray())->not->toHaveKey('deactivate_woocommerce-myparcel')
->and(WordPressOptions::getOption('_myparcelcom_carrier'))
->toBe(['POSTNL' => ['enabled' => true]]);
});

it('adds necessary hooks on plugin init', function () {
Expand Down
Loading
Loading