change table structure by migrations

Cette révision appartient à :
Daniel Seifert 2024-09-05 23:50:54 +02:00
Parent ed06705c40
révision 43e8291bf9
Signé par: DanielS
ID de la clé GPG: 6A513E13AEE66170
6 fichiers modifiés avec 414 ajouts et 96 suppressions

219
Setup/Actions.php Fichier normal
Voir le fichier

@ -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();
}
}

Voir le fichier

@ -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

Voir le fichier

@ -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 Fichier normal
Voir le fichier

@ -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.

Voir le fichier

@ -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 Fichier normal
Voir le fichier

@ -0,0 +1,4 @@
name: D3 Twofactor Onetime Password
migrations_namespace: D3\Totp\Migrations
table_name: d3migrations_totp
migrations_directory: data