Skip to content

Commit 4951805

Browse files
authored
Merge pull request #102 from CollaboraOnline/phpstan
Phpstan
2 parents b3b726e + 0e38dd9 commit 4951805

21 files changed

+145
-43
lines changed

.github/workflows/ci.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@ jobs:
7272
run: |
7373
docker-compose exec -T web ./vendor/bin/phpcs -s
7474
75+
- name: PhpStan
76+
run: |
77+
# PhpStan behaves slightly differently in PHP 8.1.
78+
if [ ${{ matrix.php }} != 8.1 ]; then
79+
docker-compose exec -T web ./vendor/bin/phpstan -v
80+
fi
81+
7582
- name: Drupal site install
7683
run: |
7784
docker-compose exec -T web ./vendor/bin/run drupal:site-install

collabora_online.install

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ function collabora_online_requirements(string $phase): array {
2626
}
2727

2828
// The Collabora Online settings key is set.
29+
/** @var array $cool_settings */
2930
$cool_settings = \Drupal::configFactory()->get('collabora_online.settings')->get('cool');
3031
if (
3132
empty($cool_settings['key_id']) ||

collabora_online.module

+16-12
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
1111
*/
1212

13-
use Drupal\collabora_online\Cool\CoolUtils;
1413
use Drupal\collabora_online\CollaboraUrl;
14+
use Drupal\collabora_online\Cool\CoolUtils;
1515
use Drupal\collabora_online\MediaHelperInterface;
1616
use Drupal\Core\Access\AccessResult;
1717
use Drupal\Core\Access\AccessResultInterface;
@@ -32,8 +32,7 @@ use Drupal\media\MediaInterface;
3232
* If you change this method, clear theme registry and routing table
3333
* 'drush cc theme-registry' and 'drush cc router'.
3434
*/
35-
function collabora_online_theme($existing, $type, $theme, $path) {
36-
35+
function collabora_online_theme(): array {
3736
return [
3837
'collabora_online' => [
3938
'render element' => 'children',
@@ -80,16 +79,18 @@ function collabora_online_theme($existing, $type, $theme, $path) {
8079
* This is used to add the menu entry in the Media content to
8180
* open the viewer/editor directly.
8281
*/
83-
function collabora_online_entity_operation(EntityInterface $entity) {
84-
if (($entity->getEntityTypeId() != "media") ||
85-
($entity->getSource()->getPluginId() != "file")) {
82+
function collabora_online_entity_operation(EntityInterface $entity): array {
83+
if ($entity->getEntityTypeId() !== 'media') {
8684
return [];
8785
}
8886

8987
/** @var \Drupal\media\MediaInterface $media */
9088
$media = $entity;
9189

92-
if (!$media->access('preview in collabora')) {
90+
if (
91+
$media->getSource()->getPluginId() !== 'file' ||
92+
!$media->access('preview in collabora')
93+
) {
9394
return [];
9495
}
9596

@@ -115,7 +116,10 @@ function collabora_online_entity_operation(EntityInterface $entity) {
115116
],
116117
];
117118

118-
if (CoolUtils::canEdit($file) && $media->access('edit in collabora')) {
119+
if (
120+
CoolUtils::canEditMimeType($type) &&
121+
$media->access('edit in collabora')
122+
) {
119123
$entries['collabora_online_edit'] = [
120124
'title' => t("Edit in Collabora Online"),
121125
'weight' => 50,
@@ -151,8 +155,8 @@ function collabora_online_media_access(MediaInterface $media, string $operation,
151155
$access_result = AccessResult::allowed();
152156
}
153157
else {
154-
$access_result = AccessResult::neutral()
155-
->setReason("The user has the '$preview_own_permission' permission, but is not the owner of the media item.");
158+
$access_result = AccessResult::neutral();
159+
$access_result->setReason("The user has the '$preview_own_permission' permission, but is not the owner of the media item.");
156160
}
157161
return $access_result
158162
->cachePerUser()
@@ -171,8 +175,8 @@ function collabora_online_media_access(MediaInterface $media, string $operation,
171175
// Use '==' because Drupal sometimes loads integers as strings.
172176
$is_owner = ($account->id() && $account->id() == $media->getOwnerId());
173177
if (!$is_owner) {
174-
$access_result = AccessResult::neutral()
175-
->setReason("The user has the '$edit_own_permission' permission, but is not the owner of the media item.");
178+
$access_result = AccessResult::neutral();
179+
$access_result->setReason("The user has the '$edit_own_permission' permission, but is not the owner of the media item.");
176180
}
177181
else {
178182
$access_result = AccessResult::allowed();

collabora_online.post_update.php

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
*/
1818
function collabora_online_post_update_add_wopi_proof_setting(): void {
1919
$config = \Drupal::configFactory()->getEditable('collabora_online.settings');
20+
/** @var array $cool_settings */
2021
$cool_settings = $config->get('cool') ?? [];
2122
$cool_settings['wopi_proof'] ??= TRUE;
2223
$config->set('cool', $cool_settings);

modules/collabora_online_group/src/Plugin/Group/RelationHandler/CollaboraAccessControl.php

+11-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
use Drupal\Core\Access\AccessResultInterface;
1818
use Drupal\Core\Entity\EntityInterface;
19+
use Drupal\Core\Entity\EntityPublishedInterface;
1920
use Drupal\Core\Session\AccountInterface;
2021
use Drupal\group\Plugin\Group\RelationHandler\AccessControlInterface;
2122
use Drupal\group\Plugin\Group\RelationHandler\AccessControlTrait;
@@ -45,10 +46,18 @@ public function entityAccess(EntityInterface $entity, $operation, AccountInterfa
4546
// Add support for unpublished operation: preview in collabora.
4647
$check_published = $operation === 'preview in collabora' && $this->implementsPublishedInterface;
4748

48-
if ($check_published && !$entity->isPublished()) {
49-
$operation .= ' unpublished';
49+
if ($check_published) {
50+
// The $this->implementsPublishedInterface property indicates that
51+
// entities of this type implement EntityPublishedInterface.
52+
/* @see \Drupal\group\Plugin\Group\RelationHandlerDefault\AccessControl::entityAccess() */
53+
assert($entity instanceof EntityPublishedInterface);
54+
55+
if (!$entity->isPublished()) {
56+
$operation .= ' unpublished';
57+
}
5058
}
5159

60+
assert($this->parent !== NULL);
5261
return $this->parent->entityAccess($entity, $operation, $account, $return_as_object);
5362
}
5463

modules/collabora_online_group/src/Plugin/Group/RelationHandler/CollaboraPermissionProvider.php

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class CollaboraPermissionProvider extends GroupMediaPermissionProvider {
2525
* {@inheritdoc}
2626
*/
2727
public function buildPermissions(): array {
28+
assert($this->parent !== NULL);
2829
$permissions = $this->parent->buildPermissions();
2930

3031
/* @see \Drupal\group\Plugin\Group\RelationHandlerDefault\PermissionProvider::buildPermissions() */
@@ -77,6 +78,7 @@ public function getPermission($operation, $target, $scope = 'any'): bool|string
7778
}
7879
}
7980

81+
assert($this->parent !== NULL);
8082
return $this->parent->getPermission($operation, $target, $scope);
8183
}
8284

modules/collabora_online_group/tests/src/Traits/GroupRelationTrait.php

+5-3
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,11 @@ protected function createPluginRelation(GroupTypeInterface $group_type, string $
4545
$entity_type_id = 'group_content_type';
4646
}
4747

48-
$entity = $this->entityTypeManager()
49-
->getStorage($entity_type_id)
50-
->createFromPlugin($group_type, $plugin_id, $values);
48+
$storage = $this->entityTypeManager()->getStorage($entity_type_id);
49+
// The storage has a different interface in group v1 than in group v2.
50+
// Both of them have a ->createFromPlugin() method.
51+
// @phpstan-ignore method.notFound
52+
$entity = $storage->createFromPlugin($group_type, $plugin_id, $values);
5153
$entity->save();
5254

5355
return $entity;

phpstan.neon

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
parameters:
2+
level: 9
3+
treatPhpDocTypesAsCertain: false
4+
paths:
5+
- .
6+
excludePaths:
7+
# For now do not check tests.
8+
- tests
9+
- modules/collabora_online_group/tests
10+
- vendor
11+
- web
12+
ignoreErrors:
13+
# Allow 'new static()', which is common and accepted in Drupal.
14+
- "#^Unsafe usage of new static\\(\\).$#"
15+
# Allow to omit `<mixed>` as iterable or generic type qualifier.
16+
- identifier: missingType.iterableValue

src/Access/WopiProofAccessCheck.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public function __construct(
6161
*/
6262
public function access(Request $request): AccessResultInterface {
6363
$config = $this->configFactory->get('collabora_online.settings');
64-
if (!($config->get('cool')['wopi_proof'] ?? TRUE)) {
64+
if (!($config->get('cool.wopi_proof') ?? TRUE)) {
6565
return AccessResult::allowed()
6666
->addCacheableDependency($config);
6767
}
@@ -176,7 +176,7 @@ protected function getSubject(Request $request): string {
176176
// This class is not responsible for checking the expiration, but it still
177177
// needs the WOPI timestamp to build the message for the signature.
178178
$timestamp_ticks = $request->headers->get('X-WOPI-Timestamp');
179-
$token = $request->query->get('access_token', '');
179+
$token = (string) $request->query->get('access_token', '');
180180
$url = $request->getUri();
181181
return sprintf(
182182
'%s%s%s%s%s%s',

src/CollaboraMediaPermissions.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public function __construct(
4444
/**
4545
* {@inheritdoc}
4646
*/
47-
public static function create(ContainerInterface $container) {
47+
public static function create(ContainerInterface $container): static {
4848
return new static($container->get('entity_type.manager'));
4949
}
5050

src/Controller/ViewerController.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ public function editor(MediaInterface $media, Request $request, $edit = FALSE):
143143
* The key to use by Collabora is empty or not configured.
144144
*/
145145
protected function getViewerRender(MediaInterface $media, string $wopi_client, bool $can_write): array {
146+
/** @var array $cool_settings */
146147
$cool_settings = $this->configFactory->get('collabora_online.settings')->get('cool');
147148

148149
if (empty($cool_settings['wopi_base'])) {
@@ -187,8 +188,8 @@ protected function getViewerRender(MediaInterface $media, string $wopi_client, b
187188
* Expiration timestamp in seconds, with millisecond accuracy.
188189
*/
189190
protected function getExpireTimestamp(): float {
190-
$cool_settings = $this->configFactory->get('collabora_online.settings')->get('cool');
191-
$ttl_seconds = $cool_settings['access_token_ttl'] ?? 0;
191+
$ttl_seconds = $this->configFactory->get('collabora_online.settings')
192+
->get('cool.access_token_ttl') ?? 0;
192193
// Set a fallback of 24 hours.
193194
$ttl_seconds = $ttl_seconds ?: 86400;
194195

src/Controller/WopiController.php

+25-5
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ public function __construct(
7575
* The response with file contents.
7676
*/
7777
protected function wopiCheckFileInfo(FileInterface $file, UserInterface $user, bool $can_write): Response {
78+
assert($file->getChangedTime() !== NULL);
7879
$response_data = [
7980
'BaseFileName' => $file->getFilename(),
8081
'Size' => $file->getSize(),
@@ -88,9 +89,14 @@ protected function wopiCheckFileInfo(FileInterface $file, UserInterface $user, b
8889
'SupportsRename' => FALSE,
8990
];
9091

91-
$user_picture = $user->user_picture?->entity;
92-
if ($user_picture) {
93-
$response_data['UserExtraInfo']['avatar'] = $this->fileUrlGenerator->generateAbsoluteString($user_picture->getFileUri());
92+
if ($user->hasField('user_picture')) {
93+
$user_picture = $user->get('user_picture')->entity;
94+
if (
95+
$user_picture instanceof FileInterface &&
96+
$user_picture->getFileUri() !== NULL
97+
) {
98+
$response_data['UserExtraInfo']['avatar'] = $this->fileUrlGenerator->generateAbsoluteString($user_picture->getFileUri());
99+
}
94100
}
95101

96102
return new JsonResponse(
@@ -112,6 +118,7 @@ protected function wopiCheckFileInfo(FileInterface $file, UserInterface $user, b
112118
* @see \Drupal\system\FileDownloadController::download()
113119
*/
114120
protected function wopiGetFile(FileInterface $file): Response {
121+
assert($file->getFileUri() !== NULL);
115122
if (!is_file($file->getFileUri())) {
116123
throw new NotFoundHttpException('The file is missing in the file system.');
117124
}
@@ -159,6 +166,7 @@ protected function wopiPutFile(MediaInterface $media, FileInterface $file, UserI
159166
$request_time - $file->getCreatedTime() <= $new_file_interval
160167
) {
161168
// Replace file with new content.
169+
assert($file->getFileUri() !== NULL);
162170
$this->fileSystem->saveData(
163171
$new_file_content,
164172
$file->getFileUri(),
@@ -182,6 +190,7 @@ protected function wopiPutFile(MediaInterface $media, FileInterface $file, UserI
182190
],
183191
);
184192

193+
assert($file->getChangedTime() !== NULL);
185194
return new JsonResponse(
186195
[
187196
'LastModifiedTime' => DateTimeHelper::format($file->getChangedTime()),
@@ -216,6 +225,7 @@ protected function wopiPutFile(MediaInterface $media, FileInterface $file, UserI
216225
],
217226
);
218227

228+
assert($new_file->getChangedTime() !== NULL);
219229
return new JsonResponse(
220230
[
221231
'LastModifiedTime' => DateTimeHelper::format($new_file->getChangedTime()),
@@ -238,6 +248,7 @@ protected function wopiPutFile(MediaInterface $media, FileInterface $file, UserI
238248
* This may have a different uri, but will have the same filename.
239249
*/
240250
protected function createNewFileEntity(FileInterface $file, string $new_file_content): FileInterface {
251+
assert($file->getFileUri() !== NULL);
241252
// The current file uri may have a number suffix like "_0".
242253
// For the new file uri, start with the clean file name, to avoid repeated
243254
// suffixes like "_0_0_0".
@@ -250,10 +261,12 @@ protected function createNewFileEntity(FileInterface $file, string $new_file_con
250261
FileExists::Rename,
251262
);
252263

253-
/** @var \Drupal\file\FileInterface|null $new_file */
264+
/** @var \Drupal\file\FileInterface $new_file */
254265
$new_file = $this->entityTypeManager->getStorage('file')->create([
255266
'uri' => $new_file_uri,
256267
]);
268+
// Parameter and return docs on entity owner methods are inconsistent.
269+
// @phpstan-ignore argument.type
257270
$new_file->setOwnerId($file->getOwnerId());
258271
// Preserve the original file name, no matter the uri was renamed.
259272
$new_file->setFilename($file->getFilename());
@@ -285,7 +298,10 @@ protected function checkSaveTimestampConflict(Request $request, MediaInterface $
285298
return NULL;
286299
}
287300
$wopi_datetime = \DateTimeImmutable::createFromFormat(\DateTimeInterface::ATOM, $wopi_time_atom);
288-
$file_datetime = \DateTimeImmutable::createFromFormat('U', $file->getChangedTime());
301+
$file_datetime = \DateTimeImmutable::createFromFormat('U', (string) $file->getChangedTime());
302+
303+
assert($wopi_datetime !== FALSE);
304+
assert($file_datetime !== FALSE);
289305

290306
if ($wopi_datetime == $file_datetime) {
291307
return NULL;
@@ -354,6 +370,10 @@ public function wopi(string $action, MediaInterface $media, Request $request): R
354370
if ($token === NULL) {
355371
throw new AccessDeniedHttpException('Missing access token.');
356372
}
373+
if (!is_string($token)) {
374+
// A malformed request could have a non-string value for access_token.
375+
throw new AccessDeniedHttpException(sprintf('Expected a string access token, found %s.', gettype($token)));
376+
}
357377
$jwt_payload = $this->verifyTokenForMedia($token, $media);
358378

359379
/** @var \Drupal\user\UserInterface|null $user */

src/Cool/CoolUtils.php

+6-11
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212

1313
namespace Drupal\collabora_online\Cool;
1414

15-
use Drupal\file\Entity\File;
16-
1715
/**
1816
* Class with various static methods.
1917
*/
@@ -29,19 +27,16 @@ class CoolUtils {
2927
];
3028

3129
/**
32-
* Determines if we can edit that media file.
33-
*
34-
* There are few types that Collabora Online only views.
30+
* Determines if a MIME type is supported for editing.
3531
*
36-
* @param \Drupal\file\Entity\File $file
37-
* File entity.
32+
* @param string $mimetype
33+
* File MIME type.
3834
*
3935
* @return bool
40-
* TRUE if the file has a file type that is supported for editing.
41-
* FALSE if the file can only be opened as read-only.
36+
* TRUE if the MIME type is supported for editing.
37+
* FALSE if the MIME type can only be opened as read-only.
4238
*/
43-
public static function canEdit(File $file) {
44-
$mimetype = $file->getMimeType();
39+
public static function canEditMimeType(string $mimetype) {
4540
return !array_key_exists($mimetype, static::READ_ONLY);
4641
}
4742

src/Discovery/DiscoveryFetcher.php

+2
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ protected function loadDiscoveryXml(ImmutableConfig $config): string {
145145
$disable_checks = (bool) $config->get('cool.disable_cert_check');
146146
$discovery_url = $this->getDiscoveryUrl($config);
147147
try {
148+
// The ->get() method is not part of the interface.
149+
// @phpstan-ignore method.notFound
148150
$response = $this->httpClient->get($discovery_url, [
149151
RequestOptions::VERIFY => !$disable_checks,
150152
]);

src/Form/ConfigForm.php

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public function getEditableConfigNames(): array {
4545
*/
4646
public function buildForm(array $form, FormStateInterface $form_state): array {
4747
$config = $this->config(static::SETTINGS);
48+
/** @var array $cool_settings */
4849
$cool_settings = $config->get('cool');
4950

5051
$form['server'] = [

src/Jwt/JwtTranscoder.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ public function __construct(
3939
* {@inheritdoc}
4040
*/
4141
protected function getKey(): string {
42-
$cool_settings = $this->configFactory->get('collabora_online.settings')->get('cool');
43-
$key_id = $cool_settings['key_id'] ?? '';
42+
/** @var string $key_id */
43+
$key_id = $this->configFactory->get('collabora_online.settings')
44+
->get('cool.key_id') ?? '';
4445
if (!$key_id) {
4546
throw new CollaboraJwtKeyException('No key was chosen for use in Collabora.');
4647
}

0 commit comments

Comments
 (0)