Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.73% covered (success)
97.73%
43 / 44
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
SimilarEditorsClient
97.73% covered (success)
97.73%
43 / 44
75.00% covered (warning)
75.00%
3 / 4
10
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getEditor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSimilarEditors
97.06% covered (success)
97.06%
33 / 34
0.00% covered (danger)
0.00%
0 / 1
7
 logErrors
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace MediaWiki\Extension\SimilarEditors;
4
5use MediaWiki\Http\HttpRequestFactory;
6use MediaWiki\Status\Status;
7use Psr\Log\LoggerInterface;
8
9class SimilarEditorsClient implements Client {
10
11    public function __construct(
12        private readonly HttpRequestFactory $httpRequestFactory,
13        private readonly LoggerInterface $logger,
14        private readonly string $apiUrl,
15        private readonly string $apiUser,
16        private readonly string $apiPassword,
17    ) {
18    }
19
20    /**
21     * @inheritDoc
22     */
23    public function getEditor( string $editor ) {
24        return null;
25    }
26
27    /**
28     * @inheritDoc
29     */
30    public function getSimilarEditors( string $editor ) {
31        $request = $this->httpRequestFactory->create(
32            $this->apiUrl . '?usertext=' . urlencode( $editor ),
33            [
34                'method' => 'GET',
35                'username' => $this->apiUser,
36                'password' => $this->apiPassword
37            ],
38            __METHOD__
39        );
40
41        $status = $request->execute();
42        $requestContent = $request->getContent();
43        $json = $requestContent !== null ? json_decode( $requestContent, true ) : null;
44
45        if ( $status->isOK() ) {
46            if ( $json && isset( $json['results'] ) ) {
47                return array_map( static function ( $result ) {
48                    return new Neighbor(
49                        $result['user_text'],
50                        $result['num_edits_in_data'],
51                        $result['edit-overlap'],
52                        $result['edit-overlap-inv'],
53                        new TimeOverlap(
54                            $result['day-overlap']['cos-sim'],
55                            $result['day-overlap']['level']
56                        ),
57                        new TimeOverlap(
58                            $result['hour-overlap']['cos-sim'],
59                            $result['hour-overlap']['level']
60                        ),
61                        $result['follow-up'] ?? [] );
62                }, $json['results'] );
63            }
64        }
65
66        // Bad status, or good status but response body contains either an error or bad data
67        $this->logErrors( $status, $requestContent );
68        if ( $json && isset( $json['error-type'] ) ) {
69            return $json['error-type'];
70        }
71        return '';
72    }
73
74    /**
75     * @param Status $status
76     * @param string|null $content
77     * @return void
78     */
79    private function logErrors( $status, $content ) {
80        $this->logger->warning(
81            Status::wrap( $status )->getWikiText( false, false, 'en' ),
82            [
83                'error' => $status->getErrorsByType( 'error' ),
84                'caller' => __METHOD__,
85                'content' => $content
86            ]
87        );
88    }
89}