Skip to content

Commit

Permalink
Prepare 0.1.4
Browse files Browse the repository at this point in the history
  • Loading branch information
curry684 committed Nov 17, 2023
1 parent ed5c79a commit 0fc48cc
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 6 deletions.
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ This project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

## [0.1.4] - 2023-11-17
### Added
- Implement ResetInterface for proper adaptation to long running servers (#10)
- Implement basic quarantine functionality
- Add PSR-compliant application logging
- Add basic antispam::stats command

### Fixed
- Stealth behavior in embedded forms should now be correct

## [0.1.3] - 2023-11-15
### Added
- All caught spam is now put into a quarantine folder
Expand All @@ -29,7 +39,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## 0.1.0 - 2023-11-10
First public release.

[Unreleased]: https://github.com/omines/antispam-bundle/compare/0.1.3...master
[Unreleased]: https://github.com/omines/antispam-bundle/compare/0.1.4...master
[0.1.4]: https://github.com/omines/antispam-bundle/compare/0.1.3...0.1.4
[0.1.3]: https://github.com/omines/antispam-bundle/compare/0.1.2...0.1.3
[0.1.2]: https://github.com/omines/antispam-bundle/compare/0.1.1...0.1.2
[0.1.1]: https://github.com/omines/antispam-bundle/compare/0.1.0...0.1.1
47 changes: 42 additions & 5 deletions src/Command/StatisticsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@
namespace Omines\AntiSpamBundle\Command;

use Omines\AntiSpamBundle\AntiSpam;
use Omines\AntiSpamBundle\Utility\StringCounter;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableCell;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Yaml\Yaml;
Expand All @@ -31,6 +35,7 @@ public function __construct(private readonly AntiSpam $antiSpam)
protected function configure(): void
{
$this
->addOption('limit', 'l', InputOption::VALUE_OPTIONAL, 'Number of results to show in rankings. Defaults to number of days in quarantine.')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command lists general statistics from the file based anti-spam quarantine.
Expand All @@ -49,27 +54,59 @@ protected function execute(InputInterface $input, OutputInterface $output)
return self::FAILURE;
}

$output->writeln(sprintf('<info>Gathering data from quarantine folder at %s</info>', $config['dir']));
$output->writeln(sprintf('<info>Analyzing data from quarantine folder at %s</info>', $config['dir']));

$limit = $input->getOption('limit');
$limit = (is_string($limit) ? intval($limit) : 0) ?: $config['max_days'] ?: 25;

$finder = (new Finder())
->files()
->name('*.yaml')
->in($config['dir'])
->sortByName()
;

$ips = new StringCounter();
$causes = new StringCounter();
$dates = new StringCounter();
foreach ($finder as $file) {
$items = Yaml::parse($file->getContents());
if (!is_array($items)) {
$output->writeln(sprintf('<error>Quarantine file %s is corrupted and could not be read</error>', $file->getFilename()));
continue;
}
foreach ($items as $item) {
$output->writeln('');
$output->writeln(sprintf('<info>Time:</info> %s', $item['time']));
$output->writeln(sprintf('<info>Message:</info> %s', $item['antispam'][0]['message']));
$output->writeln(sprintf('<info>Cause:</info> %s', $item['antispam'][0]['cause']));
if (array_key_exists('request', $item)) {
$ips->add($item['request']['client_ip']);
}
$dates->add((new \DateTimeImmutable($item['time']))->format('Y-m-d'));
$causes->add($item['antispam'][0]['cause']);
}
}

$table = new Table($output);
$table->setHeaders([
[new TableCell('By date', ['colspan' => 2]), new TableCell('By IP', ['colspan' => 2]), new TableCell('By cause', ['colspan' => 2])],
['Date', '#', 'IP', '#', 'Cause', '#'],
]);

$dates = $dates->getScores();
$ips = $ips->getRanking($limit);
$causes = $causes->getRanking($limit);
$max = max(count($dates), count($ips), count($causes));

for ($i = 0; $i < $max; ++$i) {
@$table->addRow([
$dates[$i][0],
$dates[$i][1],
$ips[$i][0],
$ips[$i][1],
$causes[$i][0],
$causes[$i][1],
]);
}
$table->render();

return self::SUCCESS;
}
}
48 changes: 48 additions & 0 deletions src/Utility/StringCounter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

/*
* Symfony Anti-Spam Bundle
* (c) Omines Internetbureau B.V. - https://omines.nl/
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Omines\AntiSpamBundle\Utility;

class StringCounter
{
/** @var array<string, int> */
private array $scores = [];

public function add(string $string): void
{
array_key_exists($string, $this->scores) ? $this->scores[$string]++ : ($this->scores[$string] = 1);
}

/**
* @return array{string, int}[]
*/
public function getScores(bool $sortByKey = true): array
{
if ($sortByKey) {
ksort($this->scores);
}

return array_map(fn ($k, $v) => [$k, $v], array_keys($this->scores), $this->scores);
}

/**
* @return array{string, int}[]
*/
public function getRanking(int $max = null): array
{
arsort($this->scores, SORT_NUMERIC);

$slice = array_slice($this->scores, 0, $max);

return array_map(fn ($k, $v) => [$k, $v], array_keys($slice), $slice);
}
}
40 changes: 40 additions & 0 deletions tests/Unit/UtilityTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

/*
* Symfony Anti-Spam Bundle
* (c) Omines Internetbureau B.V. - https://omines.nl/
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Tests\Unit;

use Omines\AntiSpamBundle\Utility\StringCounter;
use PHPUnit\Framework\TestCase;

class UtilityTest extends TestCase
{
public function testStringCounter(): void
{
$test = new StringCounter();
$test->add('foo');
$test->add('bar');
$test->add('bar');
$test->add('baz');
$test->add('baz');
$test->add('baz');

$unsorted = $test->getScores(false);
$this->assertSame(['foo', 1], $unsorted[0]);

$sorted = $test->getScores();
$this->assertSame(['bar', 2], $sorted[0]);

$ranking = $test->getRanking(2);
$this->assertCount(2, $ranking);
$this->assertSame(['baz', 3], $ranking[0]);
}
}

0 comments on commit 0fc48cc

Please sign in to comment.