diff --git a/Config/module.xml b/Config/module.xml
index cc24390..6b4e697 100644
--- a/Config/module.xml
+++ b/Config/module.xml
@@ -20,7 +20,7 @@
en_US
fr_FR
- 3.0.2
+ 3.0.3
Vincent Lopes-Vicente
diff --git a/ShortCode.php b/ShortCode.php
index 2709323..ff97986 100644
--- a/ShortCode.php
+++ b/ShortCode.php
@@ -13,6 +13,7 @@
namespace ShortCode;
use Propel\Runtime\Connection\ConnectionInterface;
+use Propel\Runtime\Propel;
use Propel\Runtime\Exception\PropelException;
use ShortCode\Model\ShortCodeQuery;
use Thelia\Core\Install\Database;
@@ -23,14 +24,20 @@ class ShortCode extends BaseModule
/** @var string */
const DOMAIN_NAME = 'shortcode';
+ /**
+ * Create the tables on a fresh install.
+ *
+ * The install script starts with DROP TABLE, so it only runs when the tables are missing.
+ * The database is asked rather than an "is_initialized" flag in the module configuration:
+ * a shop whose tables were created another way, or whose configuration was lost, would
+ * otherwise have every short code wiped by a mere reactivation.
+ */
public function preActivation(?ConnectionInterface $con = null): bool
{
- if (!$this->getConfigValue('is_initialized', false)) {
- $database = new Database($con);
+ $con ??= Propel::getWriteConnection('TheliaMain');
- $database->insertSql(null, array(__DIR__ . '/Config/TheliaMain.sql'));
-
- $this->setConfigValue('is_initialized', true);
+ if (!$this->hasTable($con, 'short_code')) {
+ (new Database($con))->insertSql(null, [__DIR__.'/Config/TheliaMain.sql']);
}
return true;
@@ -86,4 +93,12 @@ public static function deactivateShortCode($shortCodeName)
->save();
}
}
+
+ private function hasTable(ConnectionInterface $con, string $table): bool
+ {
+ $statement = $con->prepare('SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :table');
+ $statement->execute(['table' => $table]);
+
+ return (int) $statement->fetchColumn() > 0;
+ }
}
diff --git a/Tests/Integration/ActivationTest.php b/Tests/Integration/ActivationTest.php
new file mode 100644
index 0000000..0de337b
--- /dev/null
+++ b/Tests/Integration/ActivationTest.php
@@ -0,0 +1,127 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace ShortCode\Tests\Integration;
+
+use ShortCode\ShortCode;
+use PHPUnit\Framework\Attributes\Test;
+use Propel\Runtime\Connection\ConnectionInterface;
+use Propel\Runtime\Propel;
+use Thelia\Core\Install\Database;
+use Thelia\Model\ModuleConfigQuery;
+use Thelia\Model\ModuleQuery;
+use Thelia\Test\IntegrationTestCase;
+
+/**
+ * Activating the module creates its tables on a fresh install, and never touches the
+ * short codes of a shop that already has them.
+ *
+ * The install script starts with DROP TABLE, which commits implicitly: no test transaction,
+ * the tables are restored and the test row removed by hand.
+ */
+final class ActivationTest extends IntegrationTestCase
+{
+ private const INSTALL_SCRIPT = __DIR__.'/../../Config/TheliaMain.sql';
+
+ protected bool $useTransaction = false;
+
+ private ConnectionInterface $con;
+
+ private ?int $rowId = null;
+
+ protected function setUp(): void
+ {
+ parent::setUp();
+
+ $this->con = Propel::getWriteConnection('TheliaMain');
+ }
+
+ protected function tearDown(): void
+ {
+ if (!$this->hasTable('short_code')) {
+ (new Database($this->con))->insertSql(null, [self::INSTALL_SCRIPT]);
+ }
+
+ if (null !== $this->rowId) {
+ $this->con->prepare('DELETE FROM `short_code` WHERE `id` = :id')->execute(['id' => $this->rowId]);
+ }
+
+ parent::tearDown();
+ }
+
+ #[Test]
+ public function reactivatingTheModuleKeepsTheExistingRowsEvenWithoutItsInstallFlag(): void
+ {
+ $this->con->exec("INSERT INTO `short_code` (`tag`, `event`, `active`) VALUES ('activation_test', 'activation_test', 1)");
+ $this->rowId = (int) $this->con->lastInsertId();
+
+ // A shop whose tables were created without going through the activation: by a schema
+ // command, or with its module configuration lost.
+ $this->forgetInstallFlag();
+
+ $this->activate();
+
+ self::assertSame(1, (int) $this->con->query('SELECT COUNT(*) FROM `short_code` WHERE `id` = '.$this->rowId)->fetchColumn());
+ }
+
+ #[Test]
+ public function activatingTheModuleOnAFreshInstallCreatesItsTables(): void
+ {
+ $tables = $this->installScriptTables();
+
+ $this->forgetInstallFlag();
+ $this->con->exec('SET FOREIGN_KEY_CHECKS = 0');
+ foreach ($tables as $table) {
+ $this->con->exec('DROP TABLE IF EXISTS `'.$table.'`');
+ }
+ $this->con->exec('SET FOREIGN_KEY_CHECKS = 1');
+
+ $this->activate();
+
+ foreach ($tables as $table) {
+ self::assertTrue($this->hasTable($table), $table.' is created.');
+ }
+ }
+
+ private function activate(): void
+ {
+ $module = new ShortCode();
+ self::assertTrue($module->preActivation($this->con));
+ $module->postActivation($this->con);
+ }
+
+ private function forgetInstallFlag(): void
+ {
+ $moduleId = (int) ModuleQuery::create()->findOneByCode('ShortCode')?->getId();
+ ModuleConfigQuery::create()->filterByModuleId($moduleId)->filterByName('is_initialized')->delete();
+ }
+
+ /**
+ * @return list
+ */
+ private function installScriptTables(): array
+ {
+ preg_match_all('/^CREATE TABLE `(\w+)`/m', (string) file_get_contents(self::INSTALL_SCRIPT), $matches);
+
+ return $matches[1];
+ }
+
+ private function hasTable(string $table): bool
+ {
+ $statement = $this->con->prepare('SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :table');
+ $statement->execute(['table' => $table]);
+
+ return (int) $statement->fetchColumn() > 0;
+ }
+}