Skip to content

Commit

Permalink
feat: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
dvdmlln committed Jul 17, 2024
0 parents commit a4ad6ff
Show file tree
Hide file tree
Showing 18 changed files with 5,829 additions and 0 deletions.
46 changes: 46 additions & 0 deletions .build/phpcs.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="PHP_CodeSniffer">
<config name="installed_paths" value="../../slevomat/coding-standard" />
<rule ref="Generic.Arrays.DisallowLongArraySyntax">
<type>error</type>
</rule>
<rule ref="PSR12">
<type>error</type>
</rule>
<rule ref="Generic.Files.LineLength">
<type>warning</type>
</rule>
<rule ref="SlevomatCodingStandard.Arrays.SingleLineArrayWhitespace">
<type>error</type>
</rule>
<rule ref="SlevomatCodingStandard.Arrays.MultiLineArrayEndBracketPlacement">
<type>error</type>
</rule>
<rule ref="SlevomatCodingStandard.Arrays.TrailingArrayComma">
<type>error</type>
</rule>
<rule ref="SlevomatCodingStandard.Classes.ClassStructure">
<type>warning</type>
</rule>
<rule ref="SlevomatCodingStandard.ControlStructures.LanguageConstructWithParentheses">
<type>error</type>
</rule>
<rule ref="SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses">
<type>error</type>
</rule>
<rule ref="SlevomatCodingStandard.Namespaces.UnusedUses">
<type>error</type>
</rule>
<rule ref="SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly">
<type>error</type>
</rule>
<rule ref="SlevomatCodingStandard.Whitespaces.DuplicateSpaces">
<type>error</type>
<properties>
<property name="ignoreSpacesInComment" value="true" />
</properties>
</rule>
<rule ref="Squiz.Strings.DoubleQuoteUsage">
<type>error</type>
</rule>
</ruleset>
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
root = true

[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
21 changes: 21 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: CI

on:
pull_request:

jobs:
commitlint:
runs-on: ubuntu-latest
steps:
- uses: remindgmbh/[email protected]
phpcs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: 8.2
extensions: intl
tools: composer:v2
- run: composer install
- run: composer run-script phpcs
14 changes: 14 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: Release

on:
push:
branches:
- "main"

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: remindgmbh/[email protected]
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
21 changes: 21 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
### IDE ###
nbproject/
.idea/

### Composer ###
composer.phar

### Node ###
node_modules/

### General ###
*.log
cache.properties

### CI ###
.build/bin/
.build/vendor/
.build/web/
.build/logs/
.build/coverage/
.build/pdepend/
8 changes: 8 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"recommendations": [
"benjaminkott.typo3-typoscript",
"devsense.phptools-vscode",
"obliviousharmony.vscode-php-codesniffer"
],
"unwantedRecommendations": []
}
8 changes: 8 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"phpCodeSniffer.exec.linux": "./.build/bin/phpcs",
"phpCodeSniffer.standard": "Custom",
"phpCodeSniffer.standardCustom": "./.build/phpcs.xml",
"phpCodeSniffer.exclude": [
"**/vendor/{,!(remind)/**}"
]
}
84 changes: 84 additions & 0 deletions Classes/Command/DeleteBackupCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

declare(strict_types=1);

namespace Remind\Backup\Command;

use Remind\Backup\Utility\FileNameUtility;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;

final class DeleteBackupCommand extends Command
{
private const INPUT_DIR = 'dir';
private const INPUT_FILE = 'file';
private const INPUT_KEEP_COUNT = 'keep-count';
private array $extensionConfiguration;

public function __construct(
ExtensionConfiguration $extensionConfiguration
) {
$this->extensionConfiguration = $extensionConfiguration->get('rmnd_backup');
parent::__construct();
}

protected function configure(): void
{
$this
->addOption(
self::INPUT_DIR,
'd',
InputOption::VALUE_OPTIONAL,
'',
$this->extensionConfiguration['defaultDir'],
)
->addOption(
self::INPUT_FILE,
'f',
InputOption::VALUE_OPTIONAL,
'',
$this->extensionConfiguration['defaultFile'],
)
->addOption(
self::INPUT_KEEP_COUNT,
null,
InputOption::VALUE_OPTIONAL,
'Number of database backups to keep',
$this->extensionConfiguration['delete']['keepCount']
);
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
if (!(bool) $this->extensionConfiguration['delete']['enable']) {
$output->writeln(
'Backup deletion has to be enabled with [\'EXTENSIONS\'][\'rmnd_backup\'][\'delete\'][\'enable\'] = 1'
);
return Command::SUCCESS;
}

$dir = $input->getOption(self::INPUT_DIR);
$file = $input->getOption(self::INPUT_FILE);

if (!is_dir($dir)) {
$output->writeln(sprintf('Directory \'%s\' does not exist.', $dir));
return Command::FAILURE;
}

$files = array_values(array_diff(scandir($dir, SCANDIR_SORT_ASCENDING), ['.', '..']));
$matches = array_filter($files, function (string $fileToCheck) use ($file) {
return preg_match(FileNameUtility::getRegexPattern($file), $fileToCheck);
});

$filesToBeDeleted = array_slice($matches, 0, -$input->getOption(self::INPUT_KEEP_COUNT));

foreach ($filesToBeDeleted as $file) {
unlink(FileNameUtility::buildPath($dir, $file));
}

return Command::SUCCESS;
}
}
156 changes: 156 additions & 0 deletions Classes/Command/ExportCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
<?php

declare(strict_types=1);

namespace Remind\Backup\Command;

use Remind\Backup\Service\DatabaseService;
use Remind\Backup\Utility\FileNameUtility;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Process;
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;

final class ExportCommand extends Command
{
private const INPUT_DIR = 'dir';
private const INPUT_FILE = 'file';
private const INPUT_NO_DATA = 'no-data';
private const INPUT_INCLUDE_CACHE_DATA = 'include-cache-data';
private const INPUT_INCLUDE_DEFAULT_NO_DATA = 'include-default-no-data';
private const INPUT_OMIT_TIMESTAMP = 'omit-timestamp';
private const DEFAULT_NO_DATA = [
'be_sessions',
'fe_sessions',
'fe_users',
'sys_history',
'sys_http_report',
'sys_lockedrecords',
'sys_log',
];

private array $extensionConfiguration;

public function __construct(
private readonly DatabaseService $databaseService,
ExtensionConfiguration $extensionConfiguration,
) {
$this->extensionConfiguration = $extensionConfiguration->get('rmnd_backup');
parent::__construct();
}

protected function configure(): void
{
$this
->addOption(
self::INPUT_DIR,
'd',
InputOption::VALUE_OPTIONAL,
'',
$this->extensionConfiguration['defaultDir'],
)
->addOption(
self::INPUT_FILE,
'f',
InputOption::VALUE_OPTIONAL,
'',
$this->extensionConfiguration['defaultFile'],
)
->addOption(
self::INPUT_NO_DATA,
null,
InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
'Table with data excluded',
[],
)
->addOption(
self::INPUT_INCLUDE_CACHE_DATA,
null,
InputOption::VALUE_NONE,
'Include cache tables data'
)
->addOption(
self::INPUT_INCLUDE_DEFAULT_NO_DATA,
null,
InputOption::VALUE_NONE,
sprintf('Include data for tables %s', implode(', ', self::DEFAULT_NO_DATA)),
)
->addOption(
self::INPUT_OMIT_TIMESTAMP,
null,
InputOption::VALUE_NONE,
'Omit timestamp in filename'
);
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
if (!(bool) $this->extensionConfiguration['export']['enable']) {
$output->writeln(
'Database export has to be enabled with [\'EXTENSIONS\'][\'rmnd_backup\'][\'export\'][\'enable\'] = 1'
);
return Command::SUCCESS;
}

$dir = $input->getOption(self::INPUT_DIR);

$path = FileNameUtility::buildPath(
$dir,
$input->getOption(self::INPUT_FILE),
!$input->getOption(self::INPUT_OMIT_TIMESTAMP),
true,
);

if (file_exists($path)) {
$output->writeln(sprintf('File \'%s\' already exists', $path));
return Command::FAILURE;
}

if (!file_exists($dir)) {
mkdir($dir, 0755, true);
}

$tables = $this->databaseService->getTableNames();

$cacheTables = array_filter($tables, function (string $table) {
return str_starts_with($table, 'cache_');
});

$ignoreTables = $input->getOption(self::INPUT_NO_DATA);

if (!$input->getOption(self::INPUT_INCLUDE_CACHE_DATA)) {
array_push($ignoreTables, ...$cacheTables);
}

if (!$input->getOption(self::INPUT_INCLUDE_DEFAULT_NO_DATA)) {
array_push($ignoreTables, ...self::DEFAULT_NO_DATA);
}

$ignoreTableArgs = array_map(function (string $table) {
return sprintf('--ignore-table=%s.%s', $this->databaseService->getDbName(), $table);
}, $ignoreTables);

$processes = [
$this->databaseService->mysqldump(['--single-transaction', '--no-data']),
$this->databaseService->mysqldump(['--single-transaction', '--no-create-info', ...$ignoreTableArgs]),
];

foreach ($processes as $process) {
$exitCode = $process->run(function ($type, $buffer) use ($output, $path) {
if ($type === Process::ERR) {
$output->writeln($buffer);
} else {
file_put_contents($path, $buffer, FILE_APPEND | LOCK_EX);
}
});

if ($exitCode !== 0) {
return Command::FAILURE;
}
}

return Command::SUCCESS;
}
}
Loading

0 comments on commit a4ad6ff

Please sign in to comment.