Skip to content

Commit 0fc48cc

Browse files
committed
Prepare 0.1.4
1 parent ed5c79a commit 0fc48cc

File tree

4 files changed

+142
-6
lines changed

4 files changed

+142
-6
lines changed

CHANGELOG.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ This project adheres to [Semantic Versioning](http://semver.org/).
44

55
## [Unreleased]
66

7+
## [0.1.4] - 2023-11-17
8+
### Added
9+
- Implement ResetInterface for proper adaptation to long running servers (#10)
10+
- Implement basic quarantine functionality
11+
- Add PSR-compliant application logging
12+
- Add basic antispam::stats command
13+
14+
### Fixed
15+
- Stealth behavior in embedded forms should now be correct
16+
717
## [0.1.3] - 2023-11-15
818
### Added
919
- All caught spam is now put into a quarantine folder
@@ -29,7 +39,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
2939
## 0.1.0 - 2023-11-10
3040
First public release.
3141

32-
[Unreleased]: https://github.com/omines/antispam-bundle/compare/0.1.3...master
42+
[Unreleased]: https://github.com/omines/antispam-bundle/compare/0.1.4...master
43+
[0.1.4]: https://github.com/omines/antispam-bundle/compare/0.1.3...0.1.4
3344
[0.1.3]: https://github.com/omines/antispam-bundle/compare/0.1.2...0.1.3
3445
[0.1.2]: https://github.com/omines/antispam-bundle/compare/0.1.1...0.1.2
3546
[0.1.1]: https://github.com/omines/antispam-bundle/compare/0.1.0...0.1.1

src/Command/StatisticsCommand.php

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@
1313
namespace Omines\AntiSpamBundle\Command;
1414

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

52-
$output->writeln(sprintf('<info>Gathering data from quarantine folder at %s</info>', $config['dir']));
57+
$output->writeln(sprintf('<info>Analyzing data from quarantine folder at %s</info>', $config['dir']));
58+
59+
$limit = $input->getOption('limit');
60+
$limit = (is_string($limit) ? intval($limit) : 0) ?: $config['max_days'] ?: 25;
5361

5462
$finder = (new Finder())
5563
->files()
5664
->name('*.yaml')
5765
->in($config['dir'])
66+
->sortByName()
5867
;
68+
69+
$ips = new StringCounter();
70+
$causes = new StringCounter();
71+
$dates = new StringCounter();
5972
foreach ($finder as $file) {
6073
$items = Yaml::parse($file->getContents());
6174
if (!is_array($items)) {
6275
$output->writeln(sprintf('<error>Quarantine file %s is corrupted and could not be read</error>', $file->getFilename()));
6376
continue;
6477
}
6578
foreach ($items as $item) {
66-
$output->writeln('');
67-
$output->writeln(sprintf('<info>Time:</info> %s', $item['time']));
68-
$output->writeln(sprintf('<info>Message:</info> %s', $item['antispam'][0]['message']));
69-
$output->writeln(sprintf('<info>Cause:</info> %s', $item['antispam'][0]['cause']));
79+
if (array_key_exists('request', $item)) {
80+
$ips->add($item['request']['client_ip']);
81+
}
82+
$dates->add((new \DateTimeImmutable($item['time']))->format('Y-m-d'));
83+
$causes->add($item['antispam'][0]['cause']);
7084
}
7185
}
7286

87+
$table = new Table($output);
88+
$table->setHeaders([
89+
[new TableCell('By date', ['colspan' => 2]), new TableCell('By IP', ['colspan' => 2]), new TableCell('By cause', ['colspan' => 2])],
90+
['Date', '#', 'IP', '#', 'Cause', '#'],
91+
]);
92+
93+
$dates = $dates->getScores();
94+
$ips = $ips->getRanking($limit);
95+
$causes = $causes->getRanking($limit);
96+
$max = max(count($dates), count($ips), count($causes));
97+
98+
for ($i = 0; $i < $max; ++$i) {
99+
@$table->addRow([
100+
$dates[$i][0],
101+
$dates[$i][1],
102+
$ips[$i][0],
103+
$ips[$i][1],
104+
$causes[$i][0],
105+
$causes[$i][1],
106+
]);
107+
}
108+
$table->render();
109+
73110
return self::SUCCESS;
74111
}
75112
}

src/Utility/StringCounter.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
/*
4+
* Symfony Anti-Spam Bundle
5+
* (c) Omines Internetbureau B.V. - https://omines.nl/
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
11+
declare(strict_types=1);
12+
13+
namespace Omines\AntiSpamBundle\Utility;
14+
15+
class StringCounter
16+
{
17+
/** @var array<string, int> */
18+
private array $scores = [];
19+
20+
public function add(string $string): void
21+
{
22+
array_key_exists($string, $this->scores) ? $this->scores[$string]++ : ($this->scores[$string] = 1);
23+
}
24+
25+
/**
26+
* @return array{string, int}[]
27+
*/
28+
public function getScores(bool $sortByKey = true): array
29+
{
30+
if ($sortByKey) {
31+
ksort($this->scores);
32+
}
33+
34+
return array_map(fn ($k, $v) => [$k, $v], array_keys($this->scores), $this->scores);
35+
}
36+
37+
/**
38+
* @return array{string, int}[]
39+
*/
40+
public function getRanking(int $max = null): array
41+
{
42+
arsort($this->scores, SORT_NUMERIC);
43+
44+
$slice = array_slice($this->scores, 0, $max);
45+
46+
return array_map(fn ($k, $v) => [$k, $v], array_keys($slice), $slice);
47+
}
48+
}

tests/Unit/UtilityTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
/*
4+
* Symfony Anti-Spam Bundle
5+
* (c) Omines Internetbureau B.V. - https://omines.nl/
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
11+
declare(strict_types=1);
12+
13+
namespace Tests\Unit;
14+
15+
use Omines\AntiSpamBundle\Utility\StringCounter;
16+
use PHPUnit\Framework\TestCase;
17+
18+
class UtilityTest extends TestCase
19+
{
20+
public function testStringCounter(): void
21+
{
22+
$test = new StringCounter();
23+
$test->add('foo');
24+
$test->add('bar');
25+
$test->add('bar');
26+
$test->add('baz');
27+
$test->add('baz');
28+
$test->add('baz');
29+
30+
$unsorted = $test->getScores(false);
31+
$this->assertSame(['foo', 1], $unsorted[0]);
32+
33+
$sorted = $test->getScores();
34+
$this->assertSame(['bar', 2], $sorted[0]);
35+
36+
$ranking = $test->getRanking(2);
37+
$this->assertCount(2, $ranking);
38+
$this->assertSame(['baz', 3], $ranking[0]);
39+
}
40+
}

0 commit comments

Comments
 (0)