change table structure by migrations
This commit is contained in:
parent
ed06705c40
commit
43e8291bf9
219
Setup/Actions.php
Normal file
219
Setup/Actions.php
Normal file
@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* https://www.d3data.de
|
||||
*
|
||||
* @copyright (C) D3 Data Development (Inh. Thomas Dartsch)
|
||||
* @author D3 Data Development - Daniel Seifert <info@shopmodule.com>
|
||||
* @link https://www.oxidmodule.com
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Setup;
|
||||
|
||||
use Doctrine\DBAL\Driver\Exception as DoctrineDriverException;
|
||||
use Exception;
|
||||
use OxidEsales\DoctrineMigrationWrapper\MigrationsBuilder;
|
||||
use OxidEsales\Eshop\Application\Controller\FrontendController;
|
||||
use OxidEsales\Eshop\Core\DbMetaDataHandler;
|
||||
use OxidEsales\Eshop\Core\Registry;
|
||||
use OxidEsales\Eshop\Core\SeoEncoder;
|
||||
use OxidEsales\Eshop\Core\Utils;
|
||||
use OxidEsales\Eshop\Core\UtilsView;
|
||||
use OxidEsales\EshopCommunity\Internal\Container\ContainerFactory;
|
||||
use OxidEsales\EshopCommunity\Internal\Framework\Module\Configuration\Bridge\ShopConfigurationDaoBridgeInterface;
|
||||
use OxidEsales\EshopCommunity\Internal\Framework\Module\Configuration\DataObject\ModuleConfiguration;
|
||||
use OxidEsales\EshopCommunity\Internal\Framework\Module\Configuration\Exception\ModuleConfigurationNotFoundException;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class Actions
|
||||
{
|
||||
public array $seo_de = [
|
||||
'2-faktor-authentisierung/'
|
||||
];
|
||||
public array $seo_en = [
|
||||
'en/2-factor-authentication/'
|
||||
];
|
||||
public array $stdClassName = [
|
||||
'd3_account_totp'
|
||||
];
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function runModuleMigrations(): void
|
||||
{
|
||||
/** @var MigrationsBuilder $migrationsBuilder */
|
||||
$migrationsBuilder = oxNew(MigrationsBuilder::class);
|
||||
$migrations = $migrationsBuilder->build();
|
||||
$migrations->execute('migrations:migrate', 'd3totp');
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate views for changed tables
|
||||
* @throws Exception
|
||||
*/
|
||||
public function regenerateViews(): void
|
||||
{
|
||||
$oDbMetaDataHandler = oxNew(DbMetaDataHandler::class);
|
||||
$oDbMetaDataHandler->updateViews();
|
||||
}
|
||||
|
||||
/**
|
||||
* clear cache
|
||||
* @throws Exception
|
||||
*/
|
||||
public function clearCache(): void
|
||||
{
|
||||
try {
|
||||
$oUtils = oxNew(Utils::class);
|
||||
$oUtils->resetTemplateCache($this->getModuleTemplates());
|
||||
$oUtils->resetLanguageCache();
|
||||
} catch (ContainerExceptionInterface|NotFoundExceptionInterface|ModuleConfigurationNotFoundException $e) {
|
||||
oxNew(LoggerInterface::class)->error($e->getMessage(), [$this]);
|
||||
oxNew(UtilsView::class)->addErrorToDisplay($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
* @throws ModuleConfigurationNotFoundException
|
||||
*/
|
||||
protected function getModuleTemplates(): array
|
||||
{
|
||||
$container = $this->getDIContainer();
|
||||
$shopConfiguration = $container->get(ShopConfigurationDaoBridgeInterface::class)->get();
|
||||
$moduleConfiguration = $shopConfiguration->getModuleConfiguration('d3totp');
|
||||
|
||||
return array_unique(array_merge(
|
||||
$this->getModuleTemplatesFromTemplates($moduleConfiguration),
|
||||
$this->getModuleTemplatesFromBlocks($moduleConfiguration)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ModuleConfiguration $moduleConfiguration
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getModuleTemplatesFromTemplates(ModuleConfiguration $moduleConfiguration): array
|
||||
{
|
||||
/** @var $template ModuleConfiguration\Template */
|
||||
return array_map(
|
||||
function ($template) {
|
||||
return $template->getTemplateKey();
|
||||
},
|
||||
$moduleConfiguration->getTemplates()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ModuleConfiguration $moduleConfiguration
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getModuleTemplatesFromBlocks(ModuleConfiguration $moduleConfiguration): array
|
||||
{
|
||||
/** @var $templateBlock ModuleConfiguration\TemplateBlock */
|
||||
return array_map(
|
||||
function ($templateBlock) {
|
||||
return basename($templateBlock->getShopTemplatePath());
|
||||
},
|
||||
$moduleConfiguration->getTemplateBlocks()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function seoUrl()
|
||||
{
|
||||
try {
|
||||
if (!$this->hasSeoUrls()) {
|
||||
$this->createSeoUrls();
|
||||
}
|
||||
} catch (Exception|NotFoundExceptionInterface|DoctrineDriverException|ContainerExceptionInterface $e) {
|
||||
Registry::getLogger()->error($e->getMessage(), [$this]);
|
||||
Registry::getUtilsView()->addErrorToDisplay('error wile creating SEO URLs: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
public function hasSeoUrls(): bool
|
||||
{
|
||||
foreach ($this->stdClassName as $item) {
|
||||
foreach ([0, 1] as $lang) {
|
||||
if (false === $this->hasSeoUrl($item, $lang)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function hasSeoUrl($item, $langId): bool
|
||||
{
|
||||
$seoEncoder = oxNew(SeoEncoder::class);
|
||||
$seoUrl = $seoEncoder->getStaticUrl(
|
||||
oxNew(FrontendController::class)->getViewConfig()->getSelfLink() .
|
||||
"cl=" . $item,
|
||||
$langId
|
||||
);
|
||||
|
||||
return (bool)strlen($seoUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function createSeoUrls()
|
||||
{
|
||||
foreach (array_keys($this->stdClassName) as $id) {
|
||||
$seoEncoder = oxNew(SeoEncoder::class);
|
||||
$objectid = md5(strtolower(Registry::getConfig()->getShopId() . $this->seo_de[$id]));
|
||||
if (!$this->hasSeoUrl($this->stdClassName[$id], 0)) {
|
||||
$seoEncoder->addSeoEntry(
|
||||
$objectid,
|
||||
Registry::getConfig()->getShopId(),
|
||||
0,
|
||||
'index.php?cl=' . $this->stdClassName[$id],
|
||||
$this->seo_de[$id],
|
||||
'static',
|
||||
false
|
||||
);
|
||||
}
|
||||
if (!$this->hasSeoUrl($this->stdClassName[$id], 0)) {
|
||||
$seoEncoder->addSeoEntry(
|
||||
$objectid,
|
||||
Registry::getConfig()->getShopId(),
|
||||
1,
|
||||
'index.php?cl=' . $this->stdClassName[$id],
|
||||
$this->seo_en[$id],
|
||||
'static',
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ContainerInterface|null
|
||||
*/
|
||||
protected function getDIContainer(): ?ContainerInterface
|
||||
{
|
||||
return ContainerFactory::getInstance()->getContainer();
|
||||
}
|
||||
}
|
100
Setup/Events.php
100
Setup/Events.php
@ -15,7 +15,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Setup;
|
||||
|
||||
use OxidEsales\Eshop\Core\DatabaseProvider;
|
||||
use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException;
|
||||
use OxidEsales\Eshop\Core\Exception\DatabaseErrorException;
|
||||
|
||||
@ -29,10 +28,11 @@ class Events
|
||||
*/
|
||||
public static function onActivate(): void
|
||||
{
|
||||
self::addTotpTable();
|
||||
self::addTotpBackupCodesTable();
|
||||
self::addSeoItem1();
|
||||
self::addSeoItem2();
|
||||
$actions = oxNew(Actions::class);
|
||||
$actions->runModuleMigrations();
|
||||
$actions->regenerateViews();
|
||||
$actions->clearCache();
|
||||
$actions->seoUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -41,95 +41,5 @@ class Events
|
||||
public static function onDeactivate()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws DatabaseErrorException
|
||||
*/
|
||||
public static function addTotpTable(): void
|
||||
{
|
||||
$query = "CREATE TABLE IF NOT EXISTS `d3totp` (
|
||||
`OXID` CHAR(32) NOT NULL ,
|
||||
`OXUSERID` CHAR(32) NOT NULL ,
|
||||
`USETOTP` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`SEED` VARCHAR(256) NOT NULL ,
|
||||
`OXTIMESTAMP` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Timestamp',
|
||||
PRIMARY KEY (`OXID`) ,
|
||||
UNIQUE KEY `OXUSERID` (`OXUSERID`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='totp setting';";
|
||||
|
||||
DatabaseProvider::getDb()->execute($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws DatabaseErrorException
|
||||
*/
|
||||
public static function addTotpBackupCodesTable(): void
|
||||
{
|
||||
$query = "CREATE TABLE IF NOT EXISTS `d3totp_backupcodes` (
|
||||
`OXID` CHAR(32) NOT NULL ,
|
||||
`OXUSERID` CHAR(32) NOT NULL COMMENT 'user id',
|
||||
`BACKUPCODE` VARCHAR(64) NOT NULL COMMENT 'BackupCode',
|
||||
`OXTIMESTAMP` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Timestamp',
|
||||
PRIMARY KEY (`OXID`) ,
|
||||
KEY `OXUSERID` (`OXUSERID`) ,
|
||||
KEY `BACKUPCODE` (`BACKUPCODE`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci COMMENT='totp backup codes';";
|
||||
|
||||
DatabaseProvider::getDb()->execute($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws DatabaseErrorException
|
||||
*/
|
||||
public static function addSeoItem1(): void
|
||||
{
|
||||
if (!DatabaseProvider::getDb()->getOne('SELECT 1 FROM oxseo WHERE oxident = "76282e134ad4e40a3578e121a6cb1f6a"')) {
|
||||
$query = "INSERT INTO `oxseo`
|
||||
(
|
||||
`OXOBJECTID`, `OXIDENT`, `OXSHOPID`,
|
||||
`OXLANG`, `OXSTDURL`, `OXSEOURL`,
|
||||
`OXTYPE`, `OXFIXED`, `OXEXPIRED`,
|
||||
`OXPARAMS`, `OXTIMESTAMP`
|
||||
) VALUES (
|
||||
'39f744f17e974988e515558698a29df4', '76282e134ad4e40a3578e121a6cb1f6a', 1,
|
||||
1, 'index.php?cl=d3_account_totp', 'en/2-factor-authintication/',
|
||||
'static', 0, 0,
|
||||
'', NOW()
|
||||
);";
|
||||
|
||||
DatabaseProvider::getDb()->execute($query);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws DatabaseConnectionException
|
||||
* @throws DatabaseErrorException
|
||||
*/
|
||||
public static function addSeoItem2(): void
|
||||
{
|
||||
if (!DatabaseProvider::getDb()->getOne('SELECT 1 FROM oxseo WHERE oxident = "c1f8b5506e2b5d6ac184dcc5ebdfb591"')) {
|
||||
$query = "INSERT INTO `oxseo`
|
||||
(
|
||||
`OXOBJECTID`, `OXIDENT`, `OXSHOPID`,
|
||||
`OXLANG`, `OXSTDURL`, `OXSEOURL`,
|
||||
`OXTYPE`, `OXFIXED`, `OXEXPIRED`,
|
||||
`OXPARAMS`, `OXTIMESTAMP`
|
||||
) VALUES (
|
||||
'39f744f17e974988e515558698a29df4', 'c1f8b5506e2b5d6ac184dcc5ebdfb591', 1,
|
||||
0, 'index.php?cl=d3_account_totp', '2-faktor-authentisierung/',
|
||||
'static', 0, 0,
|
||||
'', NOW()
|
||||
);";
|
||||
|
||||
DatabaseProvider::getDb()->execute($query);
|
||||
}
|
||||
}
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
@ -42,7 +42,7 @@ use OxidEsales\Eshop\Application\Model as OxidModel;
|
||||
$sMetadataVersion = '2.1';
|
||||
|
||||
$sModuleId = 'd3totp';
|
||||
$logo = '<img src="https://logos.oxidmodule.com/d3logo.svg" alt="(D3)" style="height:1em;width:1em">';
|
||||
$logo = '(D3)';
|
||||
|
||||
/**
|
||||
* Module information
|
||||
|
38
migrations/README.md
Normal file
38
migrations/README.md
Normal file
@ -0,0 +1,38 @@
|
||||
# Arbeiten mit [Doctrine Migrations](https://www.doctrine-project.org/projects/doctrine-migrations/en/3.6/reference/introduction.html)
|
||||
|
||||
Migrations bilden die Veränderung der Datenbankstruktur in programmierter Form ab. Jede Strukturänderung wird in einer einzelnen (jeweils neuen) Migrationsdatei abgelegt, die Teil des Moduls ist.
|
||||
|
||||
Passe die `migrations.yml` an Dein Modul an.
|
||||
|
||||
## Erstellen eines Skeletons für die erste oder zusätzliche Migrationen
|
||||
|
||||
```
|
||||
./vendor/bin/oe-eshop-doctrine_migration migrations:generate d3moduleid
|
||||
```
|
||||
|
||||
Arbeite die angelegte Datei entsprechend Deinen Anforderungen um.
|
||||
|
||||
## Ausführen der noch nicht ausgeführten Migrations
|
||||
|
||||
Doctrine überwacht selbst, welche Migrationen schon ausgeführt wurden und verhindert damit mehrfache Ausführungen der selben Migration.
|
||||
|
||||
Im OXID-Shop werden Migrations mit folgendem Befehl ausgeführt:
|
||||
|
||||
```
|
||||
./vendor/bin/oe-eshop-db_migrate migrations:migrate
|
||||
```
|
||||
|
||||
Als Argument kann noch die Suite mitgegeben werden, wenn nur bestimmte Migrations ausgeführt werden sollen. Mögliche Angaben sind:
|
||||
|
||||
- CE - für alle CE-Migrations
|
||||
- PE - für alle PE-Migrations
|
||||
- EE - für alle EE-Migrations
|
||||
- PR - für alle Projekt-Migrations
|
||||
- ModuleId - für alle Migrations des jeweiligen Moduls
|
||||
- ohne Angabe - werden die Migrations aller Suiten nacheinander ausgeführt
|
||||
|
||||
## Abweichungen zwischen Doctrine Migrations und dem OXID Migration Wrapper
|
||||
|
||||
In den originalen Doctrine Migrations können keine Suiten angegeben werden. Dafür gibt es die Möglichkeit, die Richtung (up / down) und eine Zielversion anzugeben.
|
||||
|
||||
Bei OXID können Migrations ausschließlich aufwärts (up) und immer fix bis zur aktuellsten Version ausgeführt werden.
|
147
migrations/data/Version20240905232017.php
Normal file
147
migrations/data/Version20240905232017.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace D3\Totp\Migrations;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Types\BooleanType;
|
||||
use Doctrine\DBAL\Types\DateTimeType;
|
||||
use Doctrine\DBAL\Types\StringType;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20240905232017 extends AbstractMigration
|
||||
{
|
||||
public function getDescription() : string
|
||||
{
|
||||
return 'Extend Database by missing OTP tables and missing columns.';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function up(Schema $schema) : void
|
||||
{
|
||||
$this->connection->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string');
|
||||
|
||||
$this->addTotpTable($schema);
|
||||
$this->addTotpBackupCodesTable($schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @return void
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function addTotpTable(Schema $schema): void
|
||||
{
|
||||
$table = !$schema->hasTable('d3totp') ?
|
||||
$schema->createTable('d3totp')->setComment('totp setting') :
|
||||
$schema->getTable('d3totp');
|
||||
|
||||
// OXID
|
||||
if (!$table->hasColumn('OXID')) {
|
||||
$table->addColumn('OXID', (new StringType())->getName())
|
||||
->setLength(32)
|
||||
->setFixed(true)
|
||||
->setNotnull(true);
|
||||
}
|
||||
|
||||
// OXUSERID
|
||||
if (!$table->hasColumn('OXUSERID')) {
|
||||
$table->addColumn('OXUSERID', (new StringType())->getName())
|
||||
->setLength(32)
|
||||
->setFixed(true)
|
||||
->setNotnull(true);
|
||||
}
|
||||
|
||||
// useTotp
|
||||
if (!$table->hasColumn('USETOTP')) {
|
||||
$table->addColumn('USETOTP', (new BooleanType())->getName())
|
||||
->setLength(1)
|
||||
->setDefault(0)
|
||||
->setNotnull(true);
|
||||
}
|
||||
|
||||
// Seed
|
||||
if (!$table->hasColumn('SEED')) {
|
||||
$table->addColumn('SEED', (new StringType())->getName())
|
||||
->setLength(256)
|
||||
->setNotnull(true);
|
||||
}
|
||||
|
||||
// oxtimestamp
|
||||
if (!$table->hasColumn('OXTIMESTAMP')) {
|
||||
$table->addColumn('OXTIMESTAMP', (new DateTimeType())->getName())
|
||||
->setNotnull(true)
|
||||
->setDefault('CURRENT_TIMESTAMP');
|
||||
}
|
||||
|
||||
$table->hasPrimaryKey() ?:$table->setPrimaryKey(['oxid']);
|
||||
|
||||
if($table->hasIndex('OXUSERID') === false){
|
||||
$table->addUniqueIndex(['OXUSERID'], 'OXUSERID');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @return void
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function addTotpBackupCodesTable(Schema $schema): void
|
||||
{
|
||||
$table = !$schema->hasTable('d3totp_backupcodes') ?
|
||||
$schema->createTable('d3totp_backupcodes')->setComment('totp backup codes') :
|
||||
$schema->getTable('d3totp_backupcodes');
|
||||
|
||||
// OXID
|
||||
if (!$table->hasColumn('OXID')) {
|
||||
$table->addColumn('OXID', (new StringType())->getName())
|
||||
->setLength(32)
|
||||
->setFixed(true)
|
||||
->setNotnull(true);
|
||||
}
|
||||
|
||||
// OXUSERID
|
||||
if (!$table->hasColumn('OXUSERID')) {
|
||||
$table->addColumn('OXUSERID', (new StringType())->getName())
|
||||
->setLength(32)
|
||||
->setFixed(true)
|
||||
->setNotnull(true)
|
||||
->setComment('User ID');
|
||||
}
|
||||
|
||||
// useTotp
|
||||
if (!$table->hasColumn('BACKUPCODE')) {
|
||||
$table->addColumn('BACKUPCODE', (new StringType())->getName())
|
||||
->setFixed(false)
|
||||
->setLength(64)
|
||||
->setNotnull(true);
|
||||
}
|
||||
|
||||
// oxtimestamp
|
||||
if (!$table->hasColumn('OXTIMESTAMP')) {
|
||||
$table->addColumn('OXTIMESTAMP', (new DateTimeType())->getName())
|
||||
->setNotnull(true)
|
||||
->setDefault('CURRENT_TIMESTAMP');
|
||||
}
|
||||
|
||||
$table->hasPrimaryKey() ?:$table->setPrimaryKey(['oxid']);
|
||||
|
||||
if($table->hasIndex('OXUSERID') === false){
|
||||
$table->addIndex(['OXUSERID'], 'OXUSERID');
|
||||
}
|
||||
|
||||
if($table->hasIndex('BACKUPCODE') === false){
|
||||
$table->addIndex(['BACKUPCODE'], 'BACKUPCODE');
|
||||
}
|
||||
}
|
||||
|
||||
public function down(Schema $schema) : void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
}
|
||||
}
|
4
migrations/migrations.yml
Normal file
4
migrations/migrations.yml
Normal file
@ -0,0 +1,4 @@
|
||||
name: D3 Twofactor Onetime Password
|
||||
migrations_namespace: D3\Totp\Migrations
|
||||
table_name: d3migrations_totp
|
||||
migrations_directory: data
|
Loading…
Reference in New Issue
Block a user