Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
UserPermissionChecker
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
4 / 4
11
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 checkUserCanPublish
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 checkUserGroupRequirements
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 userHasRequiredGroup
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2declare( strict_types = 1 );
3
4/**
5 * Service for checking user permissions for ContentTranslation publishing.
6 *
7 * @license GPL-2.0-or-later
8 */
9
10namespace ContentTranslation\Service;
11
12use MediaWiki\Config\ServiceOptions;
13use MediaWiki\Title\TitleFactory;
14use MediaWiki\User\User;
15use MediaWiki\User\UserGroupManager;
16
17class UserPermissionChecker {
18
19    /**
20     * @var string[] Members of these groups can publish translations
21     */
22    private array $publishGroups;
23
24    /**
25     * @internal For use by ServiceWiring
26     */
27    public const CONSTRUCTOR_OPTIONS = [
28        'ContentTranslationPublishRequirements'
29    ];
30
31    public function __construct(
32        private readonly TitleFactory $titleFactory,
33        private readonly UserGroupManager $userGroupManager,
34        ServiceOptions $options
35    ) {
36        $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
37
38        $publishRequirements = $options->get( 'ContentTranslationPublishRequirements' );
39        $userGroups = $publishRequirements['userGroups'] ?? null;
40
41        if ( $userGroups === null ) {
42            $this->publishGroups = [];
43        } elseif ( !is_array( $userGroups ) ) {
44            $this->publishGroups = [ $userGroups ];
45        } else {
46            $this->publishGroups = $userGroups;
47        }
48    }
49
50    /**
51     * Check if the user is allowed to publish based on ContentTranslationPublishRequirements configuration.
52     *
53     * @param User $user The user attempting to publish
54     * @param string $title The target title being published to
55     * @param bool $isSandbox Whether this is a sandbox publication (optional, defaults to false)
56     * @return bool True if user can publish, false otherwise
57     */
58    public function checkUserCanPublish( User $user, string $title, bool $isSandbox = false ): bool {
59        // Don't enforce requirements for sandbox publications
60        if ( $isSandbox ) {
61            return true;
62        }
63
64        $targetTitle = $this->titleFactory->newFromText( $title );
65        if ( !$targetTitle ) {
66            return false;
67        }
68
69        // Only enforce requirements for main namespace publications
70        if ( !$targetTitle->inNamespace( NS_MAIN ) ) {
71            return true;
72        }
73
74        return $this->checkUserGroupRequirements( $user );
75    }
76
77    /**
78     * Check if the user meets the configured user group requirements.
79     *
80     * @param User $user The user to check
81     * @return bool True if user meets requirements, false otherwise
82     */
83    private function checkUserGroupRequirements( User $user ): bool {
84        if ( $this->publishGroups === [] ) {
85            // No specific groups required, allow publishing
86            return true;
87        }
88
89        // Allow if '*' (everyone) is in the required groups
90        if ( in_array( '*', $this->publishGroups ) ) {
91            return true;
92        }
93
94        return $this->userHasRequiredGroup( $user, $this->publishGroups );
95    }
96
97    /**
98     * Check if the user has any of the required groups (case-insensitive).
99     *
100     * @param User $user The user to check
101     * @param array $requiredGroups Array of required group names
102     * @return bool True if user has any required group, false otherwise
103     */
104    private function userHasRequiredGroup( User $user, array $requiredGroups ): bool {
105        $userGroups = $this->userGroupManager->getUserGroups( $user );
106        $userGroupsLower = array_map( 'strtolower', $userGroups );
107        $requiredGroupsLower = array_map( 'strtolower', $requiredGroups );
108        return count( array_intersect( $userGroupsLower, $requiredGroupsLower ) ) > 0;
109    }
110}