Browse Source

Code formatting

main
Cédric Belin 5 months ago
parent
commit
6770968583
35 changed files with 766 additions and 860 deletions
  1. +8
    -3
      .editorconfig
  2. +1
    -1
      .github/workflows/build.yaml
  3. +5
    -5
      .vscode/settings.json
  4. +0
    -113
      RoboFile.php
  5. +2
    -2
      doc/advanced/events.md
  6. +14
    -14
      doc/advanced/testing.md
  7. +14
    -14
      doc/features/comment_check.md
  8. +7
    -7
      doc/features/key_verification.md
  9. +14
    -14
      doc/features/submit_ham.md
  10. +14
    -14
      doc/features/submit_spam.md
  11. +2
    -2
      doc/index.md
  12. +9
    -9
      doc/installation.md
  13. +29
    -15
      etc/mkdocs.yaml
  14. +22
    -22
      etc/phpdoc.xml
  15. +4
    -4
      etc/phpstan.neon
  16. +13
    -13
      etc/phpunit.xml
  17. +1
    -1
      example/main.php
  18. +68
    -68
      src/Author.php
  19. +62
    -62
      src/Blog.php
  20. +6
    -6
      src/CheckResult.php
  21. +123
    -123
      src/Client.php
  22. +26
    -26
      src/ClientException.php
  23. +68
    -68
      src/Comment.php
  24. +14
    -14
      src/CommentType.php
  25. +1
    -1
      src/version.g.php
  26. +39
    -39
      test/AuthorTest.php
  27. +39
    -39
      test/BlogTest.php
  28. +88
    -88
      test/ClientTest.php
  29. +50
    -50
      test/CommentTest.php
  30. +5
    -5
      test/index.php
  31. +3
    -3
      tool/build.ps1
  32. +3
    -3
      tool/clean.ps1
  33. +1
    -1
      tool/doc.ps1
  34. +1
    -1
      tool/upgrade.ps1
  35. +10
    -10
      tool/watch.ps1

+ 8
- 3
.editorconfig View File

@@ -3,11 +3,16 @@ root = true

[*]
charset = utf-8
indent_size = 2
indent_style = space
indent_style = tab
insert_final_newline = true
max_line_length = 200
quote_type = double
tab_width = 2
trim_trailing_whitespace = true

[*.md]
indent_size = 4
trim_trailing_whitespace = false

[*.{yaml,yml}]
indent_size = 2
indent_style = space

+ 1
- 1
.github/workflows/build.yaml View File

@@ -3,7 +3,7 @@ on:
pull_request:
push:
schedule:
- cron: '0 0 1 * *'
- cron: "0 0 1 * *"
jobs:
test:
runs-on: ubuntu-latest


+ 5
- 5
.vscode/settings.json View File

@@ -1,7 +1,7 @@
{
"editor.insertSpaces": true,
"editor.tabSize": 2,
"files.encoding": "utf8",
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true
"editor.insertSpaces": false,
"editor.tabSize": 2,
"files.encoding": "utf8",
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true
}

+ 0
- 113
RoboFile.php View File

@@ -1,113 +0,0 @@
<?php declare(strict_types=1);
use Robo\{Result, Tasks};

// Load the dependencies.
require_once __DIR__.'/vendor/autoload.php';

/** Provides tasks for the build system. */
class RoboFile extends Tasks {

/** Creates a new task runner. */
function __construct() {
$path = (string) getenv('PATH');
$vendor = (string) realpath('vendor/bin');
if (mb_strpos($path, $vendor) === false) putenv("PATH=$vendor".PATH_SEPARATOR.$path);
$this->stopOnFail();
}

/**
* Builds the project.
* @return Result The task result.
*/
function build(): Result {
$version = $this->taskSemVer()->setFormat('%M.%m.%p')->__toString();
return $this->taskWriteToFile('src/version.g.php')
->line('<?php declare(strict_types=1);')->line('')
->line('// The version number of the package.')
->line("return \$packageVersion = '$version';")
->run();
}

/**
* Deletes all generated files and reset any saved state.
* @return Result The task result.
*/
function clean(): Result {
return $this->collectionBuilder()
->addTask($this->taskCleanDir('var'))
->addTask($this->taskDeleteDir(['build', 'doc/api', 'web']))
->run();
}

/**
* Uploads the results of the code coverage.
* @return Result The task result.
*/
function coverage(): Result {
return $this->_exec('coveralls var/coverage.xml');
}

/**
* Builds the documentation.
* @return Result The task result.
*/
function doc(): Result {
$phpdoc = PHP_OS_FAMILY == 'Windows' ? 'php '.escapeshellarg('C:\Program Files\PHP\share\phpDocumentor.phar') : 'phpdoc';
return $this->collectionBuilder()
->addTask($this->taskFilesystemStack()
->copy('CHANGELOG.md', 'doc/about/changelog.md')
->copy('LICENSE.md', 'doc/about/license.md'))
->addTask($this->taskExec("$phpdoc --config=etc/phpdoc.xml"))
->addTask($this->taskExec('mkdocs build --config-file=doc/mkdocs.yaml'))
->addTask($this->taskFilesystemStack()
->remove(['doc/about/changelog.md', 'doc/about/license.md', 'www/mkdocs.yaml']))
->run();
}

/**
* Performs the static analysis of source code.
* @return Result The task result.
*/
function lint(): Result {
return $this->taskExecStack()
->exec('php -l example/main.php')
->exec('phpstan analyse --configuration=etc/phpstan.neon')
->run();
}

/**
* Runs the test suites.
* @return Result The task result.
*/
function test(): Result {
return $this->_exec('phpunit --configuration=etc/phpunit.xml');
}

/**
* Upgrades the project to the latest revision.
* @return Result The task result.
*/
function upgrade(): Result {
$composer = PHP_OS_FAMILY == 'Windows' ? 'php '.escapeshellarg('C:\Program Files\PHP\share\composer.phar') : 'composer';
return $this->taskExecStack()
->exec('git reset --hard')
->exec('git fetch --all --prune')
->exec('git pull --rebase')
->exec("$composer update --no-interaction")
->run();
}

/**
* Increments the version number of the package.
* @param string $component The part in the version number to increment.
* @return Result The task result.
*/
function version(string $component = 'patch'): Result {
$semverTask = $this->taskSemVer()->increment($component);
$version = $semverTask->setFormat('%M.%m.%p')->__toString();
return $this->collectionBuilder()
->addTask($semverTask)
->addTask($this->taskReplaceInFile('etc/phpdoc.xml')->regex('/version number="\d+(\.\d+){2}"/')->to("version number=\"$version\""))
->run();
}
}

+ 2
- 2
doc/advanced/events.md View File

@@ -13,7 +13,7 @@ use yii\akismet\{Client};
use yii\httpclient\{RequestEvent};

$client->on(Client::eventRequest, fn(RequestEvent $event) =>
echo 'Client request: ', $event->request->url
echo "Client request: ", $event->request->url
);
```

@@ -26,6 +26,6 @@ use yii\akismet\{Client};
use yii\httpclient\{RequestEvent};

$client->on(Client::eventResponse, fn(RequestEvent $event) =>
echo 'Server response: ', $event->response->statusCode
echo "Server response: ", $event->response->statusCode
);
```

+ 14
- 14
doc/advanced/testing.md View File

@@ -11,12 +11,12 @@ The Akismet API will always return a `CheckResult::isSpam` response to a valid r
use yii\akismet\{Author, Client, Comment};

$client = new Client([
'apiKey' => '123YourAPIKey',
'blog' => 'http://www.yourblog.com'
"apiKey" => "123YourAPIKey",
"blog" => "http://www.yourblog.com"
]);

$author = new Author('127.0.0.1', 'Mozilla/5.0', ['name' => 'viagra-test-123']);
$comment = new Comment($author, 'A user comment');
$author = new Author("127.0.0.1", "Mozilla/5.0", ["name" => "viagra-test-123"]);
$comment = new Comment($author, "A user comment");

$isSpam = $client->checkComment($comment);
print("It should be 'true': $isSpam");
@@ -32,12 +32,12 @@ The Akismet API will always return a `CheckResult::isHam` response. Any other re
use yii\akismet\{Author, Client, Comment};

$client = new Client([
'apiKey' => '123YourAPIKey',
'blog' => 'http://www.yourblog.com'
"apiKey" => "123YourAPIKey",
"blog" => "http://www.yourblog.com"
]);

$author = new Author('127.0.0.1', 'Mozilla/5.0', ['role' => 'administrator']);
$comment = new Comment($author, 'A user comment');
$author = new Author("127.0.0.1", "Mozilla/5.0", ["role" => "administrator"]);
$comment = new Comment($author, "A user comment");

$isSpam = $client->checkComment($comment);
print("It should be 'false': $isSpam");
@@ -53,14 +53,14 @@ That will tell Akismet not to change its behaviour based on those API calls: the
use yii\akismet\{Author, Client, Comment};

$client = new Client([
'apiKey' => '123YourAPIKey',
'blog' => 'http://www.yourblog.com',
'isTest' => true
"apiKey" => "123YourAPIKey",
"blog" => "http://www.yourblog.com",
"isTest" => true
]);

$author = new Author('127.0.0.1', 'Mozilla/5.0');
$comment = new Comment($author, 'A user comment');
$author = new Author("127.0.0.1", "Mozilla/5.0");
$comment = new Comment($author, "A user comment");

echo 'It should not influence subsequent calls.';
echo "It should not influence subsequent calls.";
$client->checkComment($comment);
```

+ 14
- 14
doc/features/comment_check.md View File

@@ -25,7 +25,7 @@ The `Comment` providing the user message to be checked.
A `CheckResult` value indicating whether the given `Comment` is ham, spam or pervasive spam.

!!! tip
A comment classified as pervasive spam can be safely discarded.
A comment classified as pervasive spam can be safely discarded.

The method throws a `ClientException` when an error occurs.
The exception `getMessage()` usually includes some debug information, provided by the `X-akismet-debug-help` HTTP header, about what exactly was invalid about the call.
@@ -37,22 +37,22 @@ The exception `getMessage()` usually includes some debug information, provided b
use yii\akismet\{Author, Client, ClientException, Comment};

try {
$client = new Client([
'apiKey' => '123YourAPIKey',
'blog' => 'http://www.yourblog.com'
]);
$comment = new Comment(
new Author('127.0.0.1', 'Mozilla/5.0'),
['content' => 'A user comment', 'date' => new \DateTimeImmutable]
);
$result = $client->checkComment($comment);
echo $result == CheckResult::isHam ? 'The comment is ham.' : 'The comment is spam.';
$client = new Client([
"apiKey" => "123YourAPIKey",
"blog" => "http://www.yourblog.com"
]);
$comment = new Comment(
new Author("127.0.0.1", "Mozilla/5.0"),
["content" => "A user comment", "date" => new \DateTimeImmutable]
);
$result = $client->checkComment($comment);
echo $result == CheckResult::isHam ? "The comment is ham." : "The comment is spam.";
}

catch (ClientException $e) {
echo 'An error occurred: ', $e->getMessage();
echo "An error occurred: ", $e->getMessage();
}
```



+ 7
- 7
doc/features/key_verification.md View File

@@ -30,16 +30,16 @@ The exception `getMessage()` usually includes some debug information, provided b
use yii\akismet\{Client, ClientException};

try {
$client = new Client([
'apiKey' => '123YourAPIKey',
'blog' => 'http://www.yourblog.com'
]);
$client = new Client([
"apiKey" => "123YourAPIKey",
"blog" => "http://www.yourblog.com"
]);

$isValid = $client->verifyKey();
echo $isValid ? 'The API key is valid.' : 'The API key is invalid.';
$isValid = $client->verifyKey();
echo $isValid ? "The API key is valid." : "The API key is invalid.";
}

catch (ClientException $e) {
echo 'An error occurred: ', $e->getMessage();
echo "An error occurred: ", $e->getMessage();
}
```

+ 14
- 14
doc/features/submit_ham.md View File

@@ -17,7 +17,7 @@ See the [Akismet API documentation](https://akismet.com/development/api/#submit-
The user `Comment` to be submitted, incorrectly classified as spam.

!!! tip
Ideally, it should be the same object as the one passed to the original [comment check](comment_check.md) API call.
Ideally, it should be the same object as the one passed to the original [comment check](comment_check.md) API call.

## Return value
None.
@@ -32,24 +32,24 @@ The exception `getMessage()` usually includes some debug information, provided b
use yii\akismet\{Author, Client, ClientException, Comment};

try {
$client = new Client([
'apiKey' => '123YourAPIKey',
'blog' => 'http://www.yourblog.com'
]);
$client = new Client([
"apiKey" => "123YourAPIKey",
"blog" => "http://www.yourblog.com"
]);

$comment = new Comment(
new Author('127.0.0.1', 'Mozilla/5.0'),
['content' => 'A valid user comment (ham)']
);
$comment = new Comment(
new Author("127.0.0.1", "Mozilla/5.0"),
["content" => "A valid user comment (ham)"]
);

$result = $client->checkComment($comment); // `true`, but `false` expected.
// Got `CheckResult::isSpam`, but `CheckResult::isHam` expected.
$result = $client->checkComment($comment); // `true`, but `false` expected.
// Got `CheckResult::isSpam`, but `CheckResult::isHam` expected.

echo 'The comment was incorrectly classified as spam.';
$client->submitHam($comment);
echo "The comment was incorrectly classified as spam.";
$client->submitHam($comment);
}

catch (ClientException $e) {
echo 'An error occurred: ', $e->getMessage();
echo "An error occurred: ", $e->getMessage();
}
```

+ 14
- 14
doc/features/submit_spam.md View File

@@ -19,7 +19,7 @@ See the [Akismet API documentation](https://akismet.com/development/api/#submit-
The user `Comment` to be submitted, incorrectly classified as ham.

!!! tip
Ideally, it should be the same object as the one passed to the original [comment check](comment_check.md) API call.
Ideally, it should be the same object as the one passed to the original [comment check](comment_check.md) API call.

## Return value
None.
@@ -34,24 +34,24 @@ The exception `getMessage()` usually includes some debug information, provided b
use yii\akismet\{Author, Client, ClientException, Comment};

try {
$client = new Client([
'apiKey' => '123YourAPIKey',
'blog' => 'http://www.yourblog.com'
]);
$client = new Client([
"apiKey" => "123YourAPIKey",
"blog" => "http://www.yourblog.com"
]);

$comment = new Comment(
new Author('127.0.0.1', 'Mozilla/5.0'),
['content' => 'An invalid user comment (spam)']
);
$comment = new Comment(
new Author("127.0.0.1", "Mozilla/5.0"),
["content" => "An invalid user comment (spam)"]
);

$result = $client->checkComment($comment);
// Got `CheckResult::isHam`, but `CheckResult::isSpam` expected.
$result = $client->checkComment($comment);
// Got `CheckResult::isHam`, but `CheckResult::isSpam` expected.

echo 'The comment was incorrectly classified as ham.';
$client->submitSpam($comment);
echo "The comment was incorrectly classified as ham.";
$client->submitSpam($comment);
}

catch (ClientException $e) {
echo 'An error occurred: ', $e->getMessage();
echo "An error occurred: ", $e->getMessage();
}
```

+ 2
- 2
doc/index.md View File

@@ -14,8 +14,8 @@ You first need to [sign up for a developer key](https://akismet.com/signup/?plan
This will give you access to the API and will allow Akismet to monitor its results to make sure things are running as smoothly as possible.

!!! warning
All Akismet endpoints require an API key. If you are not already registered,
[join the developer program](https://akismet.com/signup/?plan=developer).
All Akismet endpoints require an API key. If you are not already registered,
[join the developer program](https://akismet.com/signup/?plan=developer).

### Get the library
Install the latest version of **Akismet for Yii** with [Composer](https://getcomposer.org):


+ 9
- 9
doc/installation.md View File

@@ -15,8 +15,8 @@ composer --version
```

!!! info
If you plan to play with the package sources, you will also need the latest versions of
[PowerShell](https://docs.microsoft.com/en-us/powershell) and [Material for MkDocs](https://squidfunk.github.io/mkdocs-material).
If you plan to play with the package sources, you will also need the latest versions of
[PowerShell](https://docs.microsoft.com/en-us/powershell) and [Material for MkDocs](https://squidfunk.github.io/mkdocs-material).

## Installing with Composer package manager

@@ -33,12 +33,12 @@ Now in your [PHP](https://www.php.net) code, you can use:
```php
<?php
use yii\akismet\{
Author,
Blog,
CheckResult,
Client,
ClientException,
Comment,
CommentType
Author,
Blog,
CheckResult,
Client,
ClientException,
Comment,
CommentType
};
```

doc/mkdocs.yaml → etc/mkdocs.yaml View File

@@ -1,30 +1,37 @@
site_name: Akismet for Yii
site_description: Akismet connector for Yii, high-performance PHP framework.
site_author: Cédric Belin - cedric@belin.io
site_url: https://dev.belin.io/yii2-akismet
site_url: https://docs.belin.io/yii2-akismet

docs_dir: .
docs_dir: ../doc
site_dir: ../www

repo_name: GitHub
repo_url: https://github.com/cedx/yii2-akismet
edit_uri: ''
repo_name: git.belin.io
repo_url: https://git.belin.io/cedx/yii2-akismet
edit_uri: ""

copyright: Copyright &copy; 2016 - 2020 Cédric Belin
google_analytics:
- !!python/object/apply:os.getenv [GOOGLE_ANALYTICS_ID]
- auto

extra:
social:
- {type: globe, link: 'https://belin.io'}
- {type: github, link: 'https://github.com/cedx'}
- {type: twitter, link: 'https://twitter.com/cedxbelin'}
- {type: linkedin, link: 'https://linkedin.com/in/cedxbelin'}
- icon: fontawesome/solid/globe
link: "https://belin.io"
name: Belin.io
- icon: fontawesome/brands/github
link: "https://github.com/cedx"
name: GitHub
- icon: fontawesome/brands/twitter
link: "https://twitter.com/cedxbelin"
name: Twitter
- icon: fontawesome/brands/linkedin
link: "https://linkedin.com/in/cedxbelin"
name: LinkedIn

markdown_extensions:
- admonition
- codehilite
- meta
- toc:
permalink: true

nav:
- Overview: index.md
@@ -44,6 +51,13 @@ nav:
- See also: about/see_also.md

theme:
name: material
favicon: img/favicon.ico
palette: {primary: blue grey, accent: light blue}
features:
- instant
icon:
logo: fontawesome/solid/book-reader
repo: fontawesome/brands/git-alt
name: material
palette:
accent: indigo
primary: indigo

+ 22
- 22
etc/phpdoc.xml View File

@@ -1,25 +1,25 @@
<?xml version="1.0"?>
<phpdocumentor
configVersion="3"
xmlns="http://www.phpdoc.org"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://docs.phpdoc.org/latest/phpdoc.xsd">
<title>Enums for PHP</title>
<paths>
<cache>../var/phpdoc</cache>
<output>../doc/api</output>
</paths>
<version number="8.0.0">
<api>
<markers>
<marker>TODO</marker>
</markers>
<source dsn="..">
<path>src</path>
</source>
<visibility>protected</visibility>
<visibility>public</visibility>
</api>
</version>
configVersion="3"
xmlns="http://www.phpdoc.org"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://docs.phpdoc.org/latest/phpdoc.xsd">
<title>Enums for PHP</title>
<paths>
<cache>../var/phpdoc</cache>
<output>../doc/api</output>
</paths>
<version number="8.0.0">
<api>
<markers>
<marker>TODO</marker>
</markers>
<source dsn="..">
<path>src</path>
</source>
<visibility>protected</visibility>
<visibility>public</visibility>
</api>
</version>
</phpdocumentor>

+ 4
- 4
etc/phpstan.neon View File

@@ -1,5 +1,5 @@
parameters:
autoload_files: [../vendor/yiisoft/yii2/Yii.php]
checkMissingIterableValueType: false
level: max
paths: [../src, ../test]
autoload_files: [../vendor/yiisoft/yii2/Yii.php]
checkMissingIterableValueType: false
level: max
paths: [../src, ../test]

+ 13
- 13
etc/phpunit.xml View File

@@ -1,18 +1,18 @@
<?xml version="1.0"?>
<phpunit bootstrap="../test/index.php" cacheResult="false" testdox="true">
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">../src</directory>
</whitelist>
</filter>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">../src</directory>
</whitelist>
</filter>

<logging>
<log type="coverage-clover" target="../var/coverage.xml"/>
</logging>
<logging>
<log type="coverage-clover" target="../var/coverage.xml"/>
</logging>

<testsuites>
<testsuite name="all">
<directory suffix="Test.php">../test</directory>
</testsuite>
</testsuites>
<testsuites>
<testsuite name="all">
<directory suffix="Test.php">../test</directory>
</testsuite>
</testsuites>
</phpunit>

+ 1
- 1
example/main.php View File

@@ -2,5 +2,5 @@

/** TODO */
function main(): void {
// TODO
// TODO
}

+ 68
- 68
src/Author.php View File

@@ -8,83 +8,83 @@ use yii\base\{Model};
/** Represents the author of a comment. */
class Author extends Model implements \JsonSerializable {

/** @var string The author's mail address. */
public string $email = '';
/** @var string The author's mail address. */
public string $email = "";

/** @var string The author's IP address. */
public string $ipAddress;
/** @var string The author's IP address. */
public string $ipAddress;

/** @var string The author's name. If you set it to `"viagra-test-123"`, Akismet will always return `true`. */
public string $name = '';
/** @var string The author's name. If you set it to `"viagra-test-123"`, Akismet will always return `true`. */
public string $name = "";

/** @var string The author's role. If you set it to `"administrator"`, Akismet will always return `false`. */
public string $role = '';
/** @var string The author's role. If you set it to `"administrator"`, Akismet will always return `false`. */
public string $role = "";

/** @var UriInterface|null The URL of the author's website. */
public ?UriInterface $url = null;
/** @var UriInterface|null The URL of the author's website. */
public ?UriInterface $url = null;

/** @var string The author's user agent, that is the string identifying the Web browser used to submit comments. */
public string $userAgent;
/** @var string The author's user agent, that is the string identifying the Web browser used to submit comments. */
public string $userAgent;

/**
* Creates a new author.
* @param string $ipAddress The author's IP address.
* @param string $userAgent The author's user agent.
* @param array<string, mixed> $config Name-value pairs that will be used to initialize the object properties.
*/
function __construct(string $ipAddress, string $userAgent, array $config = []) {
$this->ipAddress = $ipAddress;
$this->userAgent = $userAgent;
parent::__construct($config);
}
/**
* Creates a new author.
* @param string $ipAddress The author's IP address.
* @param string $userAgent The author's user agent.
* @param array<string, mixed> $config Name-value pairs that will be used to initialize the object properties.
*/
function __construct(string $ipAddress, string $userAgent, array $config = []) {
$this->ipAddress = $ipAddress;
$this->userAgent = $userAgent;
parent::__construct($config);
}

/**
* Creates a new author from the specified JSON map.
* @param array<string, mixed> $map A JSON map representing an author.
* @return self The instance corresponding to the specified JSON map.
*/
static function fromJson(array $map): self {
$options = [
'email' => isset($map['comment_author_email']) && is_string($map['comment_author_email']) ? $map['comment_author_email'] : '',
'name' => isset($map['comment_author']) && is_string($map['comment_author']) ? $map['comment_author'] : '',
'role' => isset($map['user_role']) && is_string($map['user_role']) ? $map['user_role'] : '',
'url' => isset($map['comment_author_url']) && is_string($map['comment_author_url']) ? new Uri($map['comment_author_url']) : null
];
/**
* Creates a new author from the specified JSON map.
* @param array<string, mixed> $map A JSON map representing an author.
* @return self The instance corresponding to the specified JSON map.
*/
static function fromJson(array $map): self {
$options = [
"email" => isset($map["comment_author_email"]) && is_string($map["comment_author_email"]) ? $map["comment_author_email"] : "",
"name" => isset($map["comment_author"]) && is_string($map["comment_author"]) ? $map["comment_author"] : "",
"role" => isset($map["user_role"]) && is_string($map["user_role"]) ? $map["user_role"] : "",
"url" => isset($map["comment_author_url"]) && is_string($map["comment_author_url"]) ? new Uri($map["comment_author_url"]) : null
];

return new self(
isset($map['user_ip']) && is_string($map['user_ip']) ? $map['user_ip'] : '',
isset($map['user_agent']) && is_string($map['user_agent']) ? $map['user_agent'] : '',
$options
);
}
return new self(
isset($map["user_ip"]) && is_string($map["user_ip"]) ? $map["user_ip"] : "",
isset($map["user_agent"]) && is_string($map["user_agent"]) ? $map["user_agent"] : "",
$options
);
}

/**
* Converts this object to a map in JSON format.
* @return \stdClass The map in JSON format corresponding to this object.
*/
function jsonSerialize(): \stdClass {
$map = new \stdClass;
$map->user_agent = $this->userAgent;
$map->user_ip = $this->ipAddress;
/**
* Converts this object to a map in JSON format.
* @return \stdClass The map in JSON format corresponding to this object.
*/
function jsonSerialize(): \stdClass {
$map = new \stdClass;
$map->user_agent = $this->userAgent;
$map->user_ip = $this->ipAddress;

if (mb_strlen($this->name)) $map->comment_author = $this->name;
if (mb_strlen($this->email)) $map->comment_author_email = $this->email;
if ($this->url) $map->comment_author_url = (string) $this->url;
if (mb_strlen($this->role)) $map->user_role = $this->role;
return $map;
}
if (mb_strlen($this->name)) $map->comment_author = $this->name;
if (mb_strlen($this->email)) $map->comment_author_email = $this->email;
if ($this->url) $map->comment_author_url = (string) $this->url;
if (mb_strlen($this->role)) $map->user_role = $this->role;
return $map;
}

/**
* Returns the validation rules for attributes.
* @return array[] The validation rules.
*/
function rules(): array {
return [
[$this->attributes(), 'trim'],
[['email'], 'filter', 'filter' => 'mb_strtolower'],
[['ipAddress', 'userAgent'], 'required'],
[['email'], 'email', 'checkDNS' => true],
[['ipAddress'], 'ip']
];
}
/**
* Returns the validation rules for attributes.
* @return array[] The validation rules.
*/
function rules(): array {
return [
[$this->attributes(), "trim"],
[["email"], "filter", "filter" => "mb_strtolower"],
[["ipAddress", "userAgent"], "required"],
[["email"], "email", "checkDNS" => true],
[["ipAddress"], "ip"]
];
}
}

+ 62
- 62
src/Blog.php View File

@@ -9,75 +9,75 @@ use yii\helpers\{StringHelper};
/** Represents the front page or home URL transmitted when making requests. */
class Blog extends Model implements \JsonSerializable {

/** @var string The character encoding for the values included in comments. */
public string $charset = '';
/** @var string The character encoding for the values included in comments. */
public string $charset = "";

/** @var UriInterface|null The blog or site URL. */
public ?UriInterface $url;
/** @var UriInterface|null The blog or site URL. */
public ?UriInterface $url;

/** @var \ArrayObject<int, string> The languages in use on the blog or site, in ISO 639-1 format. */
private \ArrayObject $languages;
/** @var \ArrayObject<int, string> The languages in use on the blog or site, in ISO 639-1 format. */
private \ArrayObject $languages;

/**
* Creates a new blog.
* @param UriInterface|null $url The blog or site URL.
* @param array<string, mixed> $config Name-value pairs that will be used to initialize the object properties.
*/
function __construct(?UriInterface $url, array $config = []) {
$this->languages = new \ArrayObject;
$this->url = $url;
parent::__construct($config);
}
/**
* Creates a new blog.
* @param UriInterface|null $url The blog or site URL.
* @param array<string, mixed> $config Name-value pairs that will be used to initialize the object properties.
*/
function __construct(?UriInterface $url, array $config = []) {
$this->languages = new \ArrayObject;
$this->url = $url;
parent::__construct($config);
}

/**
* Creates a new blog from the specified JSON map.
* @param array<string, mixed> $map A JSON map representing a blog.
* @return self The instance corresponding to the specified JSON map.
*/
static function fromJson(array $map): self {
return new self(isset($map['blog']) && is_string($map['blog']) ? new Uri($map['blog']) : null, [
'charset' => isset($map['blog_charset']) && is_string($map['blog_charset']) ? $map['blog_charset'] : '',
'languages' => isset($map['blog_lang']) && is_string($map['blog_lang']) ? StringHelper::explode($map['blog_lang'], ',', true, true) : []
]);
}
/**
* Creates a new blog from the specified JSON map.
* @param array<string, mixed> $map A JSON map representing a blog.
* @return self The instance corresponding to the specified JSON map.
*/
static function fromJson(array $map): self {
return new self(isset($map["blog"]) && is_string($map["blog"]) ? new Uri($map["blog"]) : null, [
"charset" => isset($map["blog_charset"]) && is_string($map["blog_charset"]) ? $map["blog_charset"] : "",
"languages" => isset($map["blog_lang"]) && is_string($map["blog_lang"]) ? StringHelper::explode($map["blog_lang"], ",", true, true) : []
]);
}

/**
* Gets the languages in use on the blog or site, in ISO 639-1 format.
* @return \ArrayObject<int, string> The languages in use on the blog or site.
*/
function getLanguages(): \ArrayObject {
return $this->languages;
}
/**
* Gets the languages in use on the blog or site, in ISO 639-1 format.
* @return \ArrayObject<int, string> The languages in use on the blog or site.
*/
function getLanguages(): \ArrayObject {
return $this->languages;
}

/** Initializes this object. */
function init(): void {
parent::init();
if (!mb_strlen($this->charset)) $this->charset = \Yii::$app->charset;
}
/** Initializes this object. */
function init(): void {
parent::init();
if (!mb_strlen($this->charset)) $this->charset = \Yii::$app->charset;
}

/**
* Converts this object to a map in JSON format.
* @return \stdClass The map in JSON format corresponding to this object.
*/
function jsonSerialize(): \stdClass {
$map = new \stdClass;
$map->blog = (string) $this->url;
/**
* Converts this object to a map in JSON format.
* @return \stdClass The map in JSON format corresponding to this object.
*/
function jsonSerialize(): \stdClass {
$map = new \stdClass;
$map->blog = (string) $this->url;

if (mb_strlen($this->charset)) $map->blog_charset = $this->charset;
if (count($languages = $this->getLanguages())) $map->blog_lang = implode(',', (array) $languages);
return $map;
}
if (mb_strlen($this->charset)) $map->blog_charset = $this->charset;
if (count($languages = $this->getLanguages())) $map->blog_lang = implode(",", (array) $languages);
return $map;
}

/**
* Returns the validation rules for attributes.
* @return array[] The validation rules.
*/
function rules(): array {
return [
[['charset', 'url'], 'trim'],
[['charset'], 'filter', 'filter' => 'mb_strtoupper'],
[['url'], 'required'],
[['url'], 'url']
];
}
/**
* Returns the validation rules for attributes.
* @return array[] The validation rules.
*/
function rules(): array {
return [
[["charset", "url"], "trim"],
[["charset"], "filter", "filter" => "mb_strtoupper"],
[["url"], "required"],
[["url"], "url"]
];
}
}

+ 6
- 6
src/CheckResult.php View File

@@ -4,12 +4,12 @@ namespace yii\akismet;
/** Specifies the result of a comment check. */
abstract class CheckResult {

/** @var string The comment is not a spam (i.e. a ham). */
const isHam = 'isHam';
/** @var string The comment is not a spam (i.e. a ham). */
const isHam = "isHam";

/** @var string The comment is a pervasive spam (i.e. it can be safely discarded). */
const isPervasiveSpam = 'isPervasiveSpam';
/** @var string The comment is a pervasive spam (i.e. it can be safely discarded). */
const isPervasiveSpam = "isPervasiveSpam";

/** @var string The comment is a spam. */
const isSpam = 'isSpam';
/** @var string The comment is a spam. */
const isSpam = "isSpam";
}

+ 123
- 123
src/Client.php View File

@@ -10,127 +10,127 @@ use yii\httpclient\{Client as HttpClient, CurlTransport, Exception as HttpExcept
/** Submits comments to the [Akismet](https://akismet.com) service. */
class Client extends Component {

/** @var string An event that is triggered when a request is made to the remote service. */
const eventRequest = 'request';
/** @var string An event that is triggered when a response is received from the remote service. */
const eventResponse = 'response';
/** @var string The Akismet API key. */
public string $apiKey = '';
/** @var Blog The front page or home URL. */
public Blog $blog;
/** @var UriInterface The URL of the API end point. */
public UriInterface $endPoint;
/** @var bool Value indicating whether the client operates in test mode. */
public bool $isTest = false;
/** @var string The user agent string to use when making requests. */
public string $userAgent = '';
/** @var HttpClient The underlying HTTP client. */
private HttpClient $http;
/**
* Creates a new client.
* @param array<string, mixed> $config Name-value pairs that will be used to initialize the object properties.
*/
function __construct(array $config = []) {
$this->http = new HttpClient(['transport' => CurlTransport::class]);
$this->http->on(HttpClient::EVENT_BEFORE_SEND, fn($event) => $this->trigger(static::eventRequest, $event));
$this->http->on(HttpClient::EVENT_AFTER_SEND, fn($event) => $this->trigger(static::eventResponse, $event));
parent::__construct($config);
}
/**
* Checks the specified comment against the service database, and returns a value indicating whether it is spam.
* @param Comment $comment The comment to be checked.
* @return bool A boolean value indicating whether it is spam.
* @throws ClientException An error occurred while querying the end point.
*/
function checkComment(Comment $comment): bool {
$host = $this->endPoint->getHost() . (($port = $this->endPoint->getPort()) ? ":$port" : '');
$endPoint = new Uri("{$this->endPoint->getScheme()}://{$this->apiKey}.$host{$this->endPoint->getPath()}");
return $this->fetch(UriResolver::resolve($endPoint, new Uri('comment-check')), \Yii::getObjectVars($comment->jsonSerialize())) == 'true';
}
/**
* Initializes this object.
* @throws InvalidConfigException The API key or the blog URL is empty.
*/
function init(): void {
parent::init();
$this->endPoint ??= new Uri('https://rest.akismet.com/1.1/');
if (!mb_strlen($this->apiKey)) throw new InvalidConfigException('The API key is empty.');
/** @var Blog|null $blog */
$blog = $this->blog;
if (!$blog) throw new InvalidConfigException('The blog URL is empty.');
if (!mb_strlen($this->userAgent)) {
/** @var string $version */
$version = preg_replace('/^(\d+(\.\d+){2}).*$/', '$1', \Yii::getVersion());
$this->userAgent = sprintf('YiiFramework/%s | Akismet/%s', $version, require __DIR__.'/version.g.php');
}
}
/**
* Submits the specified comment that was incorrectly marked as spam but should not have been.
* @param Comment $comment The comment to be submitted.
* @throws ClientException An error occurred while querying the end point.
*/
function submitHam(Comment $comment): void {
$host = $this->endPoint->getHost() . (($port = $this->endPoint->getPort()) ? ":$port" : '');
$endPoint = new Uri("{$this->endPoint->getScheme()}://{$this->apiKey}.$host{$this->endPoint->getPath()}");
$this->fetch(UriResolver::resolve($endPoint, new Uri('submit-ham')), \Yii::getObjectVars($comment->jsonSerialize()));
}
/**
* Submits the specified comment that was not marked as spam but should have been.
* @param Comment $comment The comment to be submitted.
* @throws ClientException An error occurred while querying the end point.
*/
function submitSpam(Comment $comment): void {
$host = $this->endPoint->getHost() . (($port = $this->endPoint->getPort()) ? ":$port" : '');
$endPoint = new Uri("{$this->endPoint->getScheme()}://{$this->apiKey}.$host{$this->endPoint->getPath()}");
$this->fetch(UriResolver::resolve($endPoint, new Uri('submit-spam')), \Yii::getObjectVars($comment->jsonSerialize()));
}
/**
* Checks the API key against the service database, and returns a value indicating whether it is valid.
* @return bool A boolean value indicating whether it is a valid API key.
* @throws ClientException An error occurred while querying the end point.
*/
function verifyKey(): bool {
return $this->fetch(UriResolver::resolve($this->endPoint, new Uri('verify-key')), ['key' => $this->apiKey]) == 'valid';
}
/**
* Queries the service by posting the specified fields to a given end point, and returns the response as a string.
* @param UriInterface $endPoint The URL of the end point to query.
* @param array<string, string> $fields The fields describing the query body.
* @return string The response body.
* @throws ClientException An error occurred while querying the end point.
*/
private function fetch(UriInterface $endPoint, array $fields = []): string {
$bodyFields = ArrayHelper::merge(\Yii::getObjectVars($this->blog->jsonSerialize()), $fields);
if ($this->isTest) $bodyFields['is_test'] = '1';
try { $response = $this->http->post((string) $endPoint, $bodyFields, ['user-agent' => $this->userAgent])->send(); }
catch (HttpException $e) { throw new ClientException($e->getMessage(), $endPoint, $e); }
if (!$response->getIsOk()) throw new ClientException($response->getStatusCode(), $endPoint);
$headers = $response->getHeaders();
if ($headers->has('X-akismet-debug-help')) {
/** @var string $header */
$header = $headers->get('X-akismet-debug-help');
throw new ClientException($header, $endPoint);
}
return $response->getContent();
}
/** @var string An event that is triggered when a request is made to the remote service. */
const eventRequest = "request";
/** @var string An event that is triggered when a response is received from the remote service. */
const eventResponse = "response";
/** @var string The Akismet API key. */
public string $apiKey = "";
/** @var Blog The front page or home URL. */
public Blog $blog;
/** @var UriInterface The URL of the API end point. */
public UriInterface $endPoint;
/** @var bool Value indicating whether the client operates in test mode. */
public bool $isTest = false;
/** @var string The user agent string to use when making requests. */
public string $userAgent = "";
/** @var HttpClient The underlying HTTP client. */
private HttpClient $http;
/**
* Creates a new client.
* @param array<string, mixed> $config Name-value pairs that will be used to initialize the object properties.
*/
function __construct(array $config = []) {
$this->http = new HttpClient(["transport" => CurlTransport::class]);
$this->http->on(HttpClient::EVENT_BEFORE_SEND, fn($event) => $this->trigger(static::eventRequest, $event));
$this->http->on(HttpClient::EVENT_AFTER_SEND, fn($event) => $this->trigger(static::eventResponse, $event));
parent::__construct($config);
}
/**
* Checks the specified comment against the service database, and returns a value indicating whether it is spam.
* @param Comment $comment The comment to be checked.
* @return bool A boolean value indicating whether it is spam.
* @throws ClientException An error occurred while querying the end point.
*/
function checkComment(Comment $comment): bool {
$host = $this->endPoint->getHost() . (($port = $this->endPoint->getPort()) ? ":$port" : "");
$endPoint = new Uri("{$this->endPoint->getScheme()}://{$this->apiKey}.$host{$this->endPoint->getPath()}");
return $this->fetch(UriResolver::resolve($endPoint, new Uri("comment-check")), \Yii::getObjectVars($comment->jsonSerialize())) == "true";
}
/**
* Initializes this object.
* @throws InvalidConfigException The API key or the blog URL is empty.
*/
function init(): void {
parent::init();
$this->endPoint ??= new Uri("https://rest.akismet.com/1.1/");
if (!mb_strlen($this->apiKey)) throw new InvalidConfigException("The API key is empty.");
/** @var Blog|null $blog */
$blog = $this->blog;
if (!$blog) throw new InvalidConfigException("The blog URL is empty.");
if (!mb_strlen($this->userAgent)) {
/** @var string $version */
$version = preg_replace('/^(\d+(\.\d+){2}).*$/', '$1', \Yii::getVersion());
$this->userAgent = sprintf("YiiFramework/%s | Akismet/%s", $version, require __DIR__."/version.g.php");
}
}
/**
* Submits the specified comment that was incorrectly marked as spam but should not have been.
* @param Comment $comment The comment to be submitted.
* @throws ClientException An error occurred while querying the end point.
*/
function submitHam(Comment $comment): void {
$host = $this->endPoint->getHost() . (($port = $this->endPoint->getPort()) ? ":$port" : "");
$endPoint = new Uri("{$this->endPoint->getScheme()}://{$this->apiKey}.$host{$this->endPoint->getPath()}");
$this->fetch(UriResolver::resolve($endPoint, new Uri("submit-ham")), \Yii::getObjectVars($comment->jsonSerialize()));
}
/**
* Submits the specified comment that was not marked as spam but should have been.
* @param Comment $comment The comment to be submitted.
* @throws ClientException An error occurred while querying the end point.
*/
function submitSpam(Comment $comment): void {
$host = $this->endPoint->getHost() . (($port = $this->endPoint->getPort()) ? ":$port" : "");
$endPoint = new Uri("{$this->endPoint->getScheme()}://{$this->apiKey}.$host{$this->endPoint->getPath()}");
$this->fetch(UriResolver::resolve($endPoint, new Uri("submit-spam")), \Yii::getObjectVars($comment->jsonSerialize()));
}
/**
* Checks the API key against the service database, and returns a value indicating whether it is valid.
* @return bool A boolean value indicating whether it is a valid API key.
* @throws ClientException An error occurred while querying the end point.
*/
function verifyKey(): bool {
return $this->fetch(UriResolver::resolve($this->endPoint, new Uri("verify-key")), ["key" => $this->apiKey]) == "valid";
}
/**
* Queries the service by posting the specified fields to a given end point, and returns the response as a string.
* @param UriInterface $endPoint The URL of the end point to query.
* @param array<string, string> $fields The fields describing the query body.
* @return string The response body.
* @throws ClientException An error occurred while querying the end point.
*/
private function fetch(UriInterface $endPoint, array $fields = []): string {
$bodyFields = ArrayHelper::merge(\Yii::getObjectVars($this->blog->jsonSerialize()), $fields);
if ($this->isTest) $bodyFields["is_test"] = "1";
try { $response = $this->http->post((string) $endPoint, $bodyFields, ["user-agent" => $this->userAgent])->send(); }
catch (HttpException $e) { throw new ClientException($e->getMessage(), $endPoint, $e); }
if (!$response->getIsOk()) throw new ClientException($response->getStatusCode(), $endPoint);
$headers = $response->getHeaders();
if ($headers->has("X-akismet-debug-help")) {
/** @var string $header */
$header = $headers->get("X-akismet-debug-help");
throw new ClientException($header, $endPoint);
}
return $response->getContent();
}
}

+ 26
- 26
src/ClientException.php View File

@@ -7,33 +7,33 @@ use yii\base\{Exception};
/** An exception caused by an error in a `Client` request. */
class ClientException extends Exception {

/** @var UriInterface|null The URL of the HTTP request or response that failed. */
private ?UriInterface $uri;
/** @var UriInterface|null The URL of the HTTP request or response that failed. */
private ?UriInterface $uri;

/**
* Creates a new client exception.
* @param string $message A message describing the error.
* @param UriInterface|null $uri The URL of the HTTP request or response that failed.
* @param \Throwable|null $previous The previous exception used for the exception chaining.
*/
function __construct(string $message, ?UriInterface $uri = null, ?\Throwable $previous = null) {
parent::__construct($message, 0, $previous);
$this->uri = $uri;
}
/**
* Creates a new client exception.
* @param string $message A message describing the error.
* @param UriInterface|null $uri The URL of the HTTP request or response that failed.
* @param \Throwable|null $previous The previous exception used for the exception chaining.
*/
function __construct(string $message, ?UriInterface $uri = null, ?\Throwable $previous = null) {
parent::__construct($message, 0, $previous);
$this->uri = $uri;
}

/**
* Gets the user-friendly name of this exception.
* @return string The user-friendly name of this exception.
*/
function getName(): string {
return 'Akismet Client Exception';
}
/**
* Gets the user-friendly name of this exception.
* @return string The user-friendly name of this exception.
*/
function getName(): string {
return "Akismet Client Exception";
}

/**
* Gets the URL of the HTTP request or response that failed.
* @return UriInterface|null The URL of the HTTP request or response that failed.
*/
function getUri(): ?UriInterface {
return $this->uri;
}
/**
* Gets the URL of the HTTP request or response that failed.
* @return UriInterface|null The URL of the HTTP request or response that failed.
*/
function getUri(): ?UriInterface {
return $this->uri;
}
}

+ 68
- 68
src/Comment.php View File

@@ -8,83 +8,83 @@ use yii\base\{Model};
/** Represents a comment submitted by an author. */
class Comment extends Model implements \JsonSerializable {

/** @var Author|null The comment's author. */
public ?Author $author;
/** @var Author|null The comment's author. */
public ?Author $author;

/** @var string The comment's content. */
public string $content = '';
/** @var string The comment's content. */
public string $content = "";

/** @var \DateTimeInterface|null The UTC timestamp of the creation of the comment. */
public ?\DateTimeInterface $date = null;
/** @var \DateTimeInterface|null The UTC timestamp of the creation of the comment. */
public ?\DateTimeInterface $date = null;

/** @var UriInterface|null The permanent location of the entry the comment is submitted to. */
public ?UriInterface $permalink = null;
/** @var UriInterface|null The permanent location of the entry the comment is submitted to. */
public ?UriInterface $permalink = null;

/** @var \DateTimeInterface|null The UTC timestamp of the publication time for the post, page or thread on which the comment was posted. */
public ?\DateTimeInterface $postModified = null;
/** @var \DateTimeInterface|null The UTC timestamp of the publication time for the post, page or thread on which the comment was posted. */
public ?\DateTimeInterface $postModified = null;

/** @var string A string describing why the content is being rechecked. */
public string $recheckReason = '';
/** @var string A string describing why the content is being rechecked. */
public string $recheckReason = "";

/** @var UriInterface|null The URL of the webpage that linked to the entry being requested. */
public ?UriInterface $referrer = null;
/** @var UriInterface|null The URL of the webpage that linked to the entry being requested. */
public ?UriInterface $referrer = null;

/** @var string The comment's type. This string value specifies a `CommentType` constant or a made up value like `"registration"`. */
public string $type = '';
/** @var string The comment's type. This string value specifies a `CommentType` constant or a made up value like `"registration"`. */
public string $type = "";

/**
* Creates a new comment.
* @param Author $author The comment's author.
* @param array<string, mixed> $config Name-value pairs that will be used to initialize the object properties.
*/
function __construct(?Author $author, array $config = []) {
$this->author = $author;
parent::__construct($config);
}
/**
* Creates a new comment.
* @param Author $author The comment's author.
* @param array<string, mixed> $config Name-value pairs that will be used to initialize the object properties.
*/
function __construct(?Author $author, array $config = []) {
$this->author = $author;
parent::__construct($config);
}

/**
* Creates a new comment from the specified JSON map.
* @param array<string, mixed> $map A JSON map representing a comment.
* @return self The instance corresponding to the specified JSON map.
*/
static function fromJson(array $map): self {
$hasAuthor = count(array_filter(array_keys($map), fn($key) => (bool) preg_match('/^(comment_author|user)/', $key))) > 0;
return new self($hasAuthor ? Author::fromJson($map) : null, [
'content' => isset($map['comment_content']) && is_string($map['comment_content']) ? $map['comment_content'] : '',
'date' => isset($map['comment_date_gmt']) && is_string($map['comment_date_gmt']) ? new \DateTimeImmutable($map['comment_date_gmt']) : null,
'permalink' => isset($map['permalink']) && is_string($map['permalink']) ? new Uri($map['permalink']) : null,
'postModified' => isset($map['comment_post_modified_gmt']) && is_string($map['comment_post_modified_gmt']) ? new \DateTimeImmutable($map['comment_post_modified_gmt']) : null,
'recheckReason' => isset($map['recheck_reason']) && is_string($map['recheck_reason']) ? $map['recheck_reason'] : '',
'referrer' => isset($map['referrer']) && is_string($map['referrer']) ? new Uri($map['referrer']) : null,
'type' => isset($map['comment_type']) && is_string($map['comment_type']) ? $map['comment_type'] : ''
]);
}
/**
* Creates a new comment from the specified JSON map.
* @param array<string, mixed> $map A JSON map representing a comment.
* @return self The instance corresponding to the specified JSON map.
*/
static function fromJson(array $map): self {
$hasAuthor = count(array_filter(array_keys($map), fn($key) => (bool) preg_match("/^(comment_author|user)/", $key))) > 0;
return new self($hasAuthor ? Author::fromJson($map) : null, [
"content" => isset($map["comment_content"]) && is_string($map["comment_content"]) ? $map["comment_content"] : "",
"date" => isset($map["comment_date_gmt"]) && is_string($map["comment_date_gmt"]) ? new \DateTimeImmutable($map["comment_date_gmt"]) : null,
"permalink" => isset($map["permalink"]) && is_string($map["permalink"]) ? new Uri($map["permalink"]) : null,
"postModified" => isset($map["comment_post_modified_gmt"]) && is_string($map["comment_post_modified_gmt"]) ? new \DateTimeImmutable($map["comment_post_modified_gmt"]) : null,
"recheckReason" => isset($map["recheck_reason"]) && is_string($map["recheck_reason"]) ? $map["recheck_reason"] : "",
"referrer" => isset($map["referrer"]) && is_string($map["referrer"]) ? new Uri($map["referrer"]) : null,
"type" => isset($map["comment_type"]) && is_string($map["comment_type"]) ? $map["comment_type"] : ""
]);
}

/**
* Converts this object to a map in JSON format.
* @return \stdClass The map in JSON format corresponding to this object.
*/
function jsonSerialize(): \stdClass {
$map = $this->author ? $this->author->jsonSerialize() : new \stdClass;
if (mb_strlen($this->content)) $map->comment_content = $this->content;
if ($this->date) $map->comment_date_gmt = $this->date->format('c');
if ($this->postModified) $map->comment_post_modified_gmt = $this->postModified->format('c');
if (mb_strlen($this->type)) $map->comment_type = $this->type;
if ($this->permalink) $map->permalink = (string) $this->permalink;
if (mb_strlen($this->recheckReason)) $map->recheck_reason = $this->recheckReason;
if ($this->referrer) $map->referrer = (string) $this->referrer;
return $map;
}
/**
* Converts this object to a map in JSON format.
* @return \stdClass The map in JSON format corresponding to this object.
*/
function jsonSerialize(): \stdClass {
$map = $this->author ? $this->author->jsonSerialize() : new \stdClass;
if (mb_strlen($this->content)) $map->comment_content = $this->content;
if ($this->date) $map->comment_date_gmt = $this->date->format("c");
if ($this->postModified) $map->comment_post_modified_gmt = $this->postModified->format("c");
if (mb_strlen($this->type)) $map->comment_type = $this->type;
if ($this->permalink) $map->permalink = (string) $this->permalink;
if (mb_strlen($this->recheckReason)) $map->recheck_reason = $this->recheckReason;
if ($this->referrer) $map->referrer = (string) $this->referrer;
return $map;
}

/**
* Returns the validation rules for attributes.
* @return array[] The validation rules.
*/
function rules(): array {
return [
[['content', 'permalink', 'recheckReason', 'referrer', 'type'], 'trim'],
[['author'], 'required'],
[['permalink', 'referrer'], 'url', 'defaultScheme' => 'http']
];
}
/**
* Returns the validation rules for attributes.
* @return array[] The validation rules.
*/
function rules(): array {
return [
[["content", "permalink", "recheckReason", "referrer", "type"], "trim"],
[["author"], "required"],
[["permalink", "referrer"], "url", "defaultScheme" => "http"]
];
}
}

+ 14
- 14
src/CommentType.php View File

@@ -4,24 +4,24 @@ namespace yii\akismet;
/** Specifies the type of a comment. */
abstract class CommentType {

/** @var string A blog post. */
const blogPost = 'blog-post';
/** @var string A blog post. */
const blogPost = "blog-post";

/** @var string A blog comment. */
const comment = 'comment';
/** @var string A blog comment. */
const comment = "comment";

/** @var string A contact form or feedback form submission. */
const contactForm = 'contact-form';
/** @var string A contact form or feedback form submission. */
const contactForm = "contact-form";

/** @var string A top-level forum post. */
const forumPost = 'forum-post';
/** @var string A top-level forum post. */
const forumPost = "forum-post";

/** @var string A [pingback](https://en.wikipedia.org/wiki/Pingback) post. */
const pingback = 'pingback';
/** @var string A [pingback](https://en.wikipedia.org/wiki/Pingback) post. */
const pingback = "pingback";

/** @var string A [trackback](https://en.wikipedia.org/wiki/Trackback) post. */
const trackback = 'trackback';
/** @var string A [trackback](https://en.wikipedia.org/wiki/Trackback) post. */
const trackback = "trackback";

/** @var string A [Twitter](https://twitter.com) message. */
const tweet = 'tweet';
/** @var string A [Twitter](https://twitter.com) message. */
const tweet = "tweet";
}

+ 1
- 1
src/version.g.php View File

@@ -1,4 +1,4 @@
<?php declare(strict_types=1);

// The version number of the package.
return $packageVersion = '8.0.0';
return $packageVersion = "8.0.0";

+ 39
- 39
test/AuthorTest.php View File

@@ -7,43 +7,43 @@ use function PHPUnit\Framework\{assertThat, countOf, equalTo, isEmpty};
/** @testdox yii\akismet\Author */
class AuthorTest extends TestCase {

/** @testdox ::fromJson() */
function testFromJson(): void {
// It should return an empty instance with an empty map.
$author = Author::fromJson([]);
assertThat($author->email, isEmpty());
assertThat($author->ipAddress, isEmpty());
// It should return an initialized instance with a non-empty map.
$author = Author::fromJson([
'comment_author_email' => 'cedric@belin.io',
'comment_author_url' => 'https://belin.io'
]);
assertThat($author->email, equalTo('cedric@belin.io'));
assertThat((string) $author->url, equalTo('https://belin.io'));
}
/** @testdox ->jsonSerialize() */
function testJsonSerialize(): void {
// It should return only the IP address and user agent with a newly created instance.
$data = (new Author('127.0.0.1', 'Doom/6.6.6'))->jsonSerialize();
assertThat(\Yii::getObjectVars($data), countOf(2));
assertThat($data->user_agent, equalTo('Doom/6.6.6'));
assertThat($data->user_ip, equalTo('127.0.0.1'));
// It should return a non-empty map with a initialized instance.
$data = (new Author('192.168.0.1', 'Mozilla/5.0', [
'email' => 'cedric@belin.io',
'name' => 'Cédric Belin',
'url' => 'https://belin.io'
]))->jsonSerialize();
assertThat(\Yii::getObjectVars($data), countOf(5));
assertThat($data->comment_author, equalTo('Cédric Belin'));
assertThat($data->comment_author_email, equalTo('cedric@belin.io'));
assertThat($data->comment_author_url, equalTo('https://belin.io'));
assertThat($data->user_agent, equalTo('Mozilla/5.0'));
assertThat($data->user_ip, equalTo('192.168.0.1'));
}
/** @testdox ::fromJson() */
function testFromJson(): void {
// It should return an empty instance with an empty map.
$author = Author::fromJson([]);
assertThat($author->email, isEmpty());
assertThat($author->ipAddress, isEmpty());
// It should return an initialized instance with a non-empty map.
$author = Author::fromJson([
"comment_author_email" => "cedric@belin.io",
"comment_author_url" => "https://belin.io"
]);
assertThat($author->email, equalTo("cedric@belin.io"));
assertThat((string) $author->url, equalTo("https://belin.io"));
}
/** @testdox ->jsonSerialize() */
function testJsonSerialize(): void {
// It should return only the IP address and user agent with a newly created instance.
$data = (new Author("127.0.0.1", "Doom/6.6.6"))->jsonSerialize();
assertThat(\Yii::getObjectVars($data), countOf(2));
assertThat($data->user_agent, equalTo("Doom/6.6.6"));
assertThat($data->user_ip, equalTo("127.0.0.1"));
// It should return a non-empty map with a initialized instance.
$data = (new Author("192.168.0.1", "Mozilla/5.0", [
"email" => "cedric@belin.io",
"name" => "Cédric Belin",
"url" => "https://belin.io"
]))->jsonSerialize();
assertThat(\Yii::getObjectVars($data), countOf(5));
assertThat($data->comment_author, equalTo("Cédric Belin"));
assertThat($data->comment_author_email, equalTo("cedric@belin.io"));
assertThat($data->comment_author_url, equalTo("https://belin.io"));
assertThat($data->user_agent, equalTo("Mozilla/5.0"));
assertThat($data->user_ip, equalTo("192.168.0.1"));
}
}

+ 39
- 39
test/BlogTest.php View File

@@ -8,43 +8,43 @@ use function PHPUnit\Framework\{assertThat, countOf, equalTo, isEmpty, isNull};
/** @testdox yii\akismet\Blog */
class BlogTest extends TestCase {

/** @testdox ::fromJson() */
function testFromJson(): void {
// It should return an empty instance with an empty map.
$blog = Blog::fromJson([]);
assertThat($blog->charset, equalTo('UTF-8'));
assertThat($blog->getLanguages(), isEmpty());
assertThat($blog->url, isNull());
// It should return an initialized instance with a non-empty map.
$blog = Blog::fromJson([
'blog' => 'https://dev.belin.io/yii2-akismet',
'blog_charset' => 'ISO-8859-1',
'blog_lang' => 'en, fr'
]);
assertThat($blog->charset, equalTo('ISO-8859-1'));
assertThat((array) $blog->getLanguages(), equalTo(['en', 'fr']));
assertThat((string) $blog->url, equalTo('https://dev.belin.io/yii2-akismet'));
}
/** @testdox ->jsonSerialize() */
function testJsonSerialize(): void {
// It should return only the blog URL with a newly created instance.
$data = (new Blog(new Uri('https://dev.belin.io/yii2-akismet')))->jsonSerialize();
assertThat(\Yii::getObjectVars($data), countOf(2));
assertThat($data->blog, equalTo('https://dev.belin.io/yii2-akismet'));
assertThat($data->blog_charset, equalTo('UTF-8'));
// It should return a non-empty map with a initialized instance.
$data = (new Blog(new Uri('https://dev.belin.io/yii2-akismet'), [
'charset' => 'ISO-8859-1',
'languages' => ['en', 'fr']
]))->jsonSerialize();
assertThat(\Yii::getObjectVars($data), countOf(3));
assertThat($data->blog, equalTo('https://dev.belin.io/yii2-akismet'));
assertThat($data->blog_charset, equalTo('ISO-8859-1'));
assertThat($data->blog_lang, equalTo('en,fr'));
}
/** @testdox ::fromJson() */
function testFromJson(): void {
// It should return an empty instance with an empty map.
$blog = Blog::fromJson([]);
assertThat($blog->charset, equalTo("UTF-8"));
assertThat($blog->getLanguages(), isEmpty());
assertThat($blog->url, isNull());
// It should return an initialized instance with a non-empty map.
$blog = Blog::fromJson([
"blog" => "https://dev.belin.io/yii2-akismet",
"blog_charset" => "ISO-8859-1",
"blog_lang" => "en, fr"
]);
assertThat($blog->charset, equalTo("ISO-8859-1"));
assertThat((array) $blog->getLanguages(), equalTo(["en", "fr"]));
assertThat((string) $blog->url, equalTo("https://dev.belin.io/yii2-akismet"));
}
/** @testdox ->jsonSerialize() */
function testJsonSerialize(): void {
// It should return only the blog URL with a newly created instance.
$data = (new Blog(new Uri("https://dev.belin.io/yii2-akismet")))->jsonSerialize();
assertThat(\Yii::getObjectVars($data), countOf(2));
assertThat($data->blog, equalTo("https://dev.belin.io/yii2-akismet"));
assertThat($data->blog_charset, equalTo("UTF-8"));
// It should return a non-empty map with a initialized instance.
$data = (new Blog(new Uri("https://dev.belin.io/yii2-akismet"), [
"charset" => "ISO-8859-1",
"languages" => ["en", "fr"]
]))->jsonSerialize();
assertThat(\Yii::getObjectVars($data), countOf(3));
assertThat($data->blog, equalTo("https://dev.belin.io/yii2-akismet"));
assertThat($data->blog_charset, equalTo("ISO-8859-1"));
assertThat($data->blog_lang, equalTo("en,fr"));
}
}

+ 88
- 88
test/ClientTest.php View File

@@ -9,92 +9,92 @@ use function PHPUnit\Framework\{assertThat, equalTo, isFalse, isTrue, logicalOr}
/** @testdox yii\akismet\Client */
class ClientTest extends TestCase {

/** @var Client The client used to query the service database. */
private Client $client;
/** @var Comment A comment with content marked as ham. */
private Comment $ham;
/** @var Comment A comment with content marked as spam. */
private Comment $spam;
/** @testdox ->checkComment() */
function testCheckComment(): void {
// It should return `CheckResult::isHam` for valid comment (e.g. ham).
assertThat($this->client->checkComment($this->ham), equalTo(CheckResult::isHam));
// It should return `CheckResult::isSpam` for invalid comment (e.g. spam).
assertThat($this->client->checkComment($this->spam), logicalOr(
equalTo(CheckResult::isSpam),
equalTo(CheckResult::isPervasiveSpam)
));
}
/** @testdox ->init() */
function testInit(): void {
// It should throw an exception if the API key or blog is empty.
$this->expectException(InvalidConfigException::class);
new Client;
}
/**
* @testdox ->submitHam()
* @doesNotPerformAssertions
*/
function testSubmitHam(): void {
// It should complete without error.
try { $this->client->submitHam($this->ham); }
catch (\Throwable $e) { Assert::fail($e->getMessage()); }
}
/**
* @testdox ->submitSpam()
* @doesNotPerformAssertions
*/
function testSubmitSpam(): void {
// It should complete without error.
try { $this->client->submitSpam($this->spam); }
catch (\Throwable $e) { Assert::fail($e->getMessage()); }
}
/** @testdox ->verifyKey() */
function testVerifyKey(): void {
// It should return `true` for a valid API key.
assertThat($this->client->verifyKey(), isTrue());
// It should return `false` for an invalid API key.
$client = new Client(['apiKey' => '0123456789-ABCDEF', 'blog' => $this->client->blog, 'isTest' => true]);
assertThat($client->verifyKey(), isFalse());
}
/** @before This method is called before each test. */
protected function setUp(): void {
$this->client = new Client([
'apiKey' => getenv('AKISMET_API_KEY'),
'blog' => new Blog(new Uri('https://dev.belin.io/yii2-akismet')),
'isTest' => true
]);
$author = new Author('192.168.0.1', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0', [
'name' => 'Akismet',
'role' => 'administrator',
'url' => new Uri('https://dev.belin.io/yii2-akismet')
]);
$this->ham = new Comment($author, [
'content' => 'I\'m testing out the Service API.',
'referrer' => new Uri('https://packagist.org/packages/cedx/yii2-akismet'),
'type' => CommentType::comment
]);
$author = new Author('127.0.0.1', 'Spam Bot/6.6.6', [
'email' => 'akismet-guaranteed-spam@example.com',
'name' => 'viagra-test-123'
]);
$this->spam = new Comment($author, [
'content' => 'Spam!',
'type' => CommentType::trackback
]);
}
/** @var Client The client used to query the service database. */
private Client $client;
/** @var Comment A comment with content marked as ham. */
private Comment $ham;
/** @var Comment A comment with content marked as spam. */
private Comment $spam;
/** @testdox ->checkComment() */
function testCheckComment(): void {
// It should return `CheckResult::isHam` for valid comment (e.g. ham).
assertThat($this->client->checkComment($this->ham), equalTo(CheckResult::isHam));
// It should return `CheckResult::isSpam` for invalid comment (e.g. spam).
assertThat($this->client->checkComment($this->spam), logicalOr(
equalTo(CheckResult::isSpam),
equalTo(CheckResult::isPervasiveSpam)
));
}
/** @testdox ->init() */
function testInit(): void {
// It should throw an exception if the API key or blog is empty.
$this->expectException(InvalidConfigException::class);
new Client;
}
/**
* @testdox ->submitHam()
* @doesNotPerformAssertions
*/
function testSubmitHam(): void {
// It should complete without error.
try { $this->client->submitHam($this->ham); }
catch (\Throwable $e) { Assert::fail($e->getMessage()); }
}
/**
* @testdox ->submitSpam()
* @doesNotPerformAssertions
*/
function testSubmitSpam(): void {
// It should complete without error.
try { $this->client->submitSpam($this->spam); }
catch (\Throwable $e) { Assert::fail($e->getMessage()); }
}
/** @testdox ->verifyKey() */
function testVerifyKey(): void {
// It should return `true` for a valid API key.
assertThat($this->client->verifyKey(), isTrue());
// It should return `false` for an invalid API key.
$client = new Client(["apiKey" => "0123456789-ABCDEF", "blog" => $this->client->blog, "isTest" => true]);
assertThat($client->verifyKey(), isFalse());
}
/** @before This method is called before each test. */
protected function setUp(): void {
$this->client = new Client([
"apiKey" => getenv("AKISMET_API_KEY"),
"blog" => new Blog(new Uri("https://dev.belin.io/yii2-akismet")),
"isTest" => true
]);
$author = new Author("192.168.0.1", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0", [
"name" => "Akismet",
"role" => "administrator",
"url" => new Uri("https://dev.belin.io/yii2-akismet")
]);
$this->ham = new Comment($author, [
"content" => "I\"m testing out the Service API.",
"referrer" => new Uri("https://packagist.org/packages/cedx/yii2-akismet"),
"type" => CommentType::comment
]);
$author = new Author("127.0.0.1", "Spam Bot/6.6.6", [
"email" => "akismet-guaranteed-spam@example.com",
"name" => "viagra-test-123"
]);
$this->spam = new Comment($author, [
"content" => "Spam!",
"type" => CommentType::trackback
]);
}
}

+ 50
- 50
test/CommentTest.php View File

@@ -7,61 +7,61 @@ use function PHPUnit\Framework\{assertThat, countOf, equalTo, isEmpty, isNull};
/** @testdox yii\akismet\Comment */
class CommentTest extends TestCase {

/** @testdox ::fromJson() */
function testFromJson(): void {
// It should return an empty instance with an empty map.
$comment = Comment::fromJson([]);
assertThat($comment->author, isNull());
assertThat($comment->content, isEmpty());
assertThat($comment->date, isNull());
assertThat($comment->referrer, isEmpty());
assertThat($comment->type, isEmpty());
/** @testdox ::fromJson() */
function testFromJson(): void {
// It should return an empty instance with an empty map.
$comment = Comment::fromJson([]);
assertThat($comment->author, isNull());
assertThat($comment->content, isEmpty());
assertThat($comment->date, isNull());
assertThat($comment->referrer, isEmpty());
assertThat($comment->type, isEmpty());

// It should return an initialized instance with a non-empty map.
$comment = Comment::fromJson([
'comment_author' => 'Cédric Belin',
'comment_content' => 'A user comment.',
'comment_date_gmt' => '2000-01-01T00:00:00.000Z',
'comment_type' => 'trackback',
'referrer' => 'https://belin.io'
]);
// It should return an initialized instance with a non-empty map.
$comment = Comment::fromJson([
"comment_author" => "Cédric Belin",
"comment_content" => "A user comment.",
"comment_date_gmt" => "2000-01-01T00:00:00.000Z",
"comment_type" => "trackback",
"referrer" => "https://belin.io"
]);

/** @var Author $author */
$author = $comment->author;
assertThat($author->name, equalTo('Cédric Belin'));
/** @var Author $author */
$author = $comment->author;
assertThat($author->name, equalTo("Cédric Belin"));

/** @var \DateTimeInterface $date */
$date = $comment->date;
assertThat($date->format('Y'), equalTo(2000));
/** @var \DateTimeInterface $date */
$date = $comment->date;
assertThat($date->format("Y"), equalTo(2000));

assertThat($comment->content, equalTo('A user comment.'));
assertThat($comment->referrer, equalTo('https://belin.io'));
assertThat($comment->type, equalTo(CommentType::trackback));
}
assertThat($comment->content, equalTo("A user comment."));
assertThat($comment->referrer, equalTo("https://belin.io"));
assertThat($comment->type, equalTo(CommentType::trackback));
}

/** @testdox ->jsonSerialize() */
function testJsonSerialize(): void {
// It should return only the author info with a newly created instance.
$data = (new Comment(new Author('127.0.0.1', 'Doom/6.6.6')))->jsonSerialize();
assertThat(\Yii::getObjectVars($data), countOf(2));
assertThat($data->user_agent, equalTo('Doom/6.6.6'));
assertThat($data->user_ip, equalTo('127.0.0.1'));
/** @testdox ->jsonSerialize() */
function testJsonSerialize(): void {
// It should return only the author info with a newly created instance.
$data = (new Comment(new Author("127.0.0.1", "Doom/6.6.6")))->jsonSerialize();
assertThat(\Yii::getObjectVars($data), countOf(2));
assertThat($data->user_agent, equalTo("Doom/6.6.6"));
assertThat($data->user_ip, equalTo("127.0.0.1"));

// It should return a non-empty map with a initialized instance.
$data = (new Comment(new Author('127.0.0.1', 'Doom/6.6.6', ['name' => 'Cédric Belin']), [
'content' => 'A user comment.',
'date' => new \DateTimeImmutable('2000-01-01T00:00:00.000Z'),
'referrer' => 'https://belin.io',
'type' => CommentType::pingback
]))->jsonSerialize();
// It should return a non-empty map with a initialized instance.
$data = (new Comment(new Author("127.0.0.1", "Doom/6.6.6", ["name" => "Cédric Belin"]), [
"content" => "A user comment.",
"date" => new \DateTimeImmutable("2000-01-01T00:00:00.000Z"),
"referrer" => "https://belin.io",
"type" => CommentType::pingback
]))->jsonSerialize();

assertThat(\Yii::getObjectVars($data), countOf(7));
assertThat($data->comment_author, equalTo('Cédric Belin'));
assertThat($data->comment_content, equalTo('A user comment.'));
assertThat($data->comment_date_gmt, equalTo('2000-01-01T00:00:00+00:00'));
assertThat($data->comment_type, equalTo('pingback'));
assertThat($data->referrer, equalTo('https://belin.io'));
assertThat($data->user_agent, equalTo('Doom/6.6.6'));
assertThat($data->user_ip, equalTo('127.0.0.1'));
}
assertThat(\Yii::getObjectVars($data), countOf(7));
assertThat($data->comment_author, equalTo("Cédric Belin"));
assertThat($data->comment_content, equalTo("A user comment."));
assertThat($data->comment_date_gmt, equalTo("2000-01-01T00:00:00+00:00"));
assertThat($data->comment_type, equalTo("pingback"));
assertThat($data->referrer, equalTo("https://belin.io"));
assertThat($data->user_agent, equalTo("Doom/6.6.6"));
assertThat($data->user_ip, equalTo("127.0.0.1"));
}
}

+ 5
- 5
test/index.php View File

@@ -2,15 +2,15 @@
use yii\console\{Application};

// Set the environment.
define('YII_DEBUG', true);
define('YII_ENV', 'test');
define("YII_DEBUG", true);
define("YII_ENV", "test");

// Load the class library.
$rootPath = dirname(__DIR__);
require_once "$rootPath/vendor/autoload.php";
require_once "$rootPath/vendor/yiisoft/yii2/Yii.php";
Yii::setAlias('@root', $rootPath);
Yii::setAlias('@yii/akismet', "$rootPath/src");
Yii::setAlias("@root", $rootPath);
Yii::setAlias("@yii/akismet", "$rootPath/src");

// Start the application.
new Application(['id' => 'yii2-akismet', 'basePath' => '@root/src']);
new Application(["id" => "yii2-akismet", "basePath" => "@root/src"]);

+ 3
- 3
tool/build.ps1 View File