Passed
Push — hypernext ( 403616...d6fe90 )
by Nico
11:05
created

src/models/Teams.php (4 issues)

1
<?php
2
/**
3
 * @author Nicolas CARPi <[email protected]>
4
 * @copyright 2012 Nicolas CARPi
5
 * @see https://www.elabftw.net Official website
6
 * @license AGPL-3.0
7
 * @package elabftw
8
 */
9
declare(strict_types=1);
10
11
namespace Elabftw\Models;
12
13
use function array_diff;
14
use Elabftw\Elabftw\Db;
15
use Elabftw\Elabftw\ItemTypeParams;
16
use Elabftw\Exceptions\ImproperActionException;
17
use Elabftw\Interfaces\ContentParamsInterface;
18
use Elabftw\Interfaces\DestroyableInterface;
19
use Elabftw\Interfaces\ReadableInterface;
20
use Elabftw\Services\Filter;
21
use Elabftw\Services\TeamsHelper;
22
use Elabftw\Services\UsersHelper;
23
use Elabftw\Traits\SetIdTrait;
24
use PDO;
25
26
/**
27
 * All about the teams
28
 */
29
class Teams implements ReadableInterface, DestroyableInterface
30
{
31
    use SetIdTrait;
32
33
    protected Db $Db;
34
35
    public function __construct(public Users $Users, ?int $id = null)
36
    {
37
        $this->Db = Db::getConnection();
38 6
        $this->id = $id;
39
    }
40 6
41 6
    /**
42 6
     * Make sure that the teams exist. Input can be an array of team name, id or orgid
43
     * and the response is an array of teams, with id and name for each
44
     * Input can come from external auth and reference an uncreated team
45
     * so with this the team will be created on the fly (if it's allowed)
46
     */
47
    public function getTeamsFromIdOrNameOrOrgidArray(array $input): array
48
    {
49
        $res = array();
50
        foreach ($input as $query) {
51
            $sql = 'SELECT id, name FROM teams WHERE id = :query OR name = :query OR orgid = :query';
52
            $req = $this->Db->prepare($sql);
53
            $req->bindParam(':query', $query);
54
            $this->Db->execute($req);
55
            $team = $req->fetch();
56
            if ($team === false) {
57
                $id = $this->createTeamIfAllowed($query);
58
                $team = $this->getTeamsFromIdOrNameOrOrgidArray(array($id));
59
            }
60
            $res[] = $team;
61
        }
62
        return $res;
63
    }
64
65
    /**
66
     * Add one user to n teams
67
     *
68
     * @param array<array-key, int> $teamIdArr this is the validated array of teams that exist
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, int> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, int>.
Loading history...
69
     */
70
    public function addUserToTeams(int $userid, array $teamIdArr): void
71
    {
72
        foreach ($teamIdArr as $teamId) {
73
            $TeamsHelper = new TeamsHelper((int) $teamId);
74
            // don't add a second time
75
            if ($TeamsHelper->isUserInTeam($userid)) {
76
                break;
77
            }
78
            $sql = 'INSERT INTO users2teams (`users_id`, `teams_id`) VALUES (:userid, :team);';
79
            $req = $this->Db->prepare($sql);
80
            $req->bindParam(':userid', $userid, PDO::PARAM_INT);
81
            $req->bindParam(':team', $teamId, PDO::PARAM_INT);
82
            $this->Db->execute($req);
83
        }
84
    }
85
86
    /**
87
     * Remove a user from teams
88
     *
89
     * @param array<array-key, int> $teamIdArr this is the validated array of teams that exist
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, int> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, int>.
Loading history...
90
     */
91
    public function rmUserFromTeams(int $userid, array $teamIdArr): void
92
    {
93
        // make sure that the user is in more than one team before removing the team
94
        $UsersHelper = new UsersHelper($userid);
95
        if (count($UsersHelper->getTeamsFromUserid()) === 1) {
96
            return;
97
        }
98 2
        foreach ($teamIdArr as $teamId) {
99
            $sql = 'DELETE FROM users2teams WHERE `users_id` = :userid AND `teams_id` = :team';
100 2
            $req = $this->Db->prepare($sql);
101
            $req->bindParam(':userid', $userid, PDO::PARAM_INT);
102
            $req->bindParam(':team', $teamId, PDO::PARAM_INT);
103 2
            $this->Db->execute($req);
104 2
        }
105 2
    }
106 2
107 2
    /**
108 2
     * When the user logs in, make sure that the teams they are part of
109
     * are the same teams than the one sent by an external auth
110
     *
111
     * @param array<array-key, mixed> $teams
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<array-key, mixed> at position 2 could not be parsed: Unknown type name 'array-key' at position 2 in array<array-key, mixed>.
Loading history...
112 2
     */
113
    public function synchronize(int $userid, array $teams): void
114
    {
115 2
        $teamIdArr = array_column($teams, 'id');
116 2
        // get the difference between the teams sent by idp
117
        // and the teams that the user is in
118
        $UsersHelper = new UsersHelper($userid);
119 2
        $currentTeams = $UsersHelper->getTeamsIdFromUserid();
120 2
121 2
        $addToTeams = array_diff($teamIdArr, $currentTeams);
122 2
        $this->addUserToTeams($userid, $addToTeams);
123 2
        $currentTeams = $UsersHelper->getTeamsIdFromUserid();
124 2
125
        $rmFromTeams = array_diff($currentTeams, $teamIdArr);
126
        $this->rmUserFromTeams($userid, $rmFromTeams);
127
    }
128
129 2
    /**
130 2
     * Add a new team
131
     *
132 2
     * @param string $name The new name of the team
133
     * @return int the new team id
134
     */
135
    public function create(string $name): int
136
    {
137
        $name = Filter::sanitize($name);
138
139
        // add to the teams table
140 2
        $sql = 'INSERT INTO teams (name, common_template, link_name, link_href) VALUES (:name, :common_template, :link_name, :link_href)';
141
        $req = $this->Db->prepare($sql);
142 2
        $req->bindParam(':name', $name);
143 2
        $req->bindValue(':common_template', Templates::defaultBody);
144 2
        $req->bindValue(':link_name', 'Documentation');
145 2
        $req->bindValue(':link_href', 'https://doc.elabftw.net');
146
        $this->Db->execute($req);
147
        // grab the team ID
148
        $newId = $this->Db->lastInsertId();
149 2
150 2
        // create default status
151
        $Status = new Status($newId);
152
        $Status->createDefault();
153
154 2
        // create default item type
155
        $user = new Users();
156
        $user->team = $newId;
157
        $ItemsTypes = new ItemsTypes($user);
158
        $extra = array(
159
            'color' => '#32a100',
160
            'body' => '<p>Go to the admin panel to edit/add more items types!</p>',
161
            'canread' => 'team',
162
            'canwrite' => 'team',
163
            'bookable' => '0',
164
        );
165
        $ItemsTypes->create(new ItemTypeParams('Edit me', 'all', $extra));
166
167
        return $newId;
168
    }
169
170
    /**
171
     * Read from the current team
172
     */
173
    public function read(ContentParamsInterface $params): array
174
    {
175
        $sql = 'SELECT * FROM `teams` WHERE id = :id';
176
        $req = $this->Db->prepare($sql);
177
        $req->bindParam(':id', $this->Users->userData['team'], PDO::PARAM_INT);
178
        $this->Db->execute($req);
179
180
        $res = $req->fetch();
181
        if ($res === false) {
182
            return array();
183 1
        }
184
185
        return $res;
186
    }
187
188
    /**
189
     * Get all the teams
190
     */
191
    public function readAll(): array
192
    {
193
        $sql = 'SELECT * FROM teams ORDER BY name ASC';
194
        $req = $this->Db->prepare($sql);
195
        $this->Db->execute($req);
196 1
197 1
        $res = $req->fetchAll();
198
        if ($res === false) {
199 1
            return array();
200 1
        }
201
        return $res;
202
    }
203 1
204 1
    /**
205 1
     * Delete a team only if all the stats are at zero
206
     */
207
    public function destroy(): bool
208 1
    {
209 1
        // check for stats, should be 0
210
        $count = $this->getStats($this->id);
0 ignored issues
show
It seems like $this->id can also be of type null; however, parameter $team of Elabftw\Models\Teams::getStats() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

210
        $count = $this->getStats(/** @scrutinizer ignore-type */ $this->id);
Loading history...
211
212
        if ($count['totxp'] !== '0' || $count['totdb'] !== '0' || $count['totusers'] !== '0') {
213 1
            throw new ImproperActionException('The team is not empty! Aborting deletion!');
214 1
        }
215 1
216
        // foreign keys will take care of deleting associated data (like status or experiments_templates)
217
        $sql = 'DELETE FROM teams WHERE id = :id';
218 1
        $req = $this->Db->prepare($sql);
219 1
        $req->bindParam(':id', $this->id, PDO::PARAM_INT);
220 1
        return $this->Db->execute($req);
221
    }
222
223 1
    /**
224
     * Clear the timestamp password
225
     */
226
    public function destroyStamppass(): bool
227
    {
228
        $sql = 'UPDATE teams SET stamppass = NULL WHERE id = :id';
229
        $req = $this->Db->prepare($sql);
230
        $req->bindParam(':id', $this->Users->userData['team'], PDO::PARAM_INT);
231
232
        return $this->Db->execute($req);
233 1
    }
234 1
235 1
    /**
236 1
     * Get statistics for the whole install
237 1
     */
238 1
    public function getAllStats(): array
239 1
    {
240 1
        $sql = 'SELECT
241 1
        (SELECT COUNT(users.userid) FROM users) AS totusers,
242 1
        (SELECT COUNT(items.id) FROM items) AS totdb,
243
        (SELECT COUNT(teams.id) FROM teams) AS totteams,
244 1
        (SELECT COUNT(experiments.id) FROM experiments) AS totxp,
245
        (SELECT COUNT(experiments.id) FROM experiments WHERE experiments.timestamped = 1) AS totxpts';
246
        $req = $this->Db->prepare($sql);
247 1
        $this->Db->execute($req);
248
249
        $res = $req->fetch(PDO::FETCH_NAMED);
250
        if ($res === false) {
251
            return array();
252
        }
253
254
        return $res;
255
    }
256
257 1
    /**
258
     * Get statistics for a team
259 1
     */
260 1
    public function getStats(int $team): array
261
    {
262 1
        $sql = 'SELECT
263
        (SELECT COUNT(users.userid) FROM users CROSS JOIN users2teams ON (users2teams.users_id = users.userid) WHERE users2teams.teams_id = :team) AS totusers,
264
        (SELECT COUNT(items.id) FROM items WHERE items.team = :team) AS totdb,
265
        (SELECT COUNT(experiments.id) FROM experiments LEFT JOIN users ON (experiments.userid = users.userid) CROSS JOIN users2teams ON (users2teams.users_id = users.userid) WHERE users2teams.teams_id = :team) AS totxp,
266 1
        (SELECT COUNT(experiments.id) FROM experiments LEFT JOIN users ON (experiments.userid = users.userid) CROSS JOIN users2teams ON (users2teams.users_id = users.userid) WHERE users2teams.teams_id = :team AND experiments.timestamped = 1) AS totxpts';
267 1
        $req = $this->Db->prepare($sql);
268 1
        $req->bindParam(':team', $team, PDO::PARAM_INT);
269 1
        $this->Db->execute($req);
270
271 1
        $res = $req->fetch(PDO::FETCH_NAMED);
272
        if ($res === false) {
273
            return array();
274 1
        }
275
276
        return $res;
277
    }
278
279
    public function hasCommonTeamWithCurrent(int $userid, int $team): bool
280
    {
281
        $UsersHelper = new UsersHelper($userid);
282 1
        $teams = $UsersHelper->getTeamsIdFromUserid();
283
        return in_array((string) $team, $teams, true);
284
    }
285 1
286
    private function createTeamIfAllowed(string $name): int
287 1
    {
288 1
        $Config = Config::getConfig();
289
        if ($Config->configArr['saml_team_create']) {
290
            return $this->create($name);
291
        }
292 1
        throw new ImproperActionException('The administrator disabled team creation on login. Contact your administrator for creating the team beforehand.');
293 1
    }
294
}
295