From 964949e6812cfae7e84aee10cb1e42b2a39ed8a7 Mon Sep 17 00:00:00 2001 From: Andreas Hennings Date: Tue, 12 Nov 2024 20:39:07 +0100 Subject: [PATCH] Issue #43: Write meaningful doc comments. --- phpcs.xml | 7 -- src/Controller/ViewerController.php | 17 +++- src/Controller/WopiController.php | 45 ++++++++++- src/Cool/CoolRequest.php | 63 ++++++++++++++- src/Cool/CoolUtils.php | 121 +++++++++++++++++++++++----- src/Form/ConfigForm.php | 3 + 6 files changed, 219 insertions(+), 37 deletions(-) diff --git a/phpcs.xml b/phpcs.xml index 7ba6b304..b98b14a9 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -26,13 +26,6 @@ - - - - - - - diff --git a/src/Controller/ViewerController.php b/src/Controller/ViewerController.php index 5835bb3f..083b9166 100644 --- a/src/Controller/ViewerController.php +++ b/src/Controller/ViewerController.php @@ -24,10 +24,18 @@ */ class ViewerController extends ControllerBase { + /** + * The renderer service. + * + * @var \Drupal\Core\Render\RendererInterface + */ private $renderer; /** * The controller constructor. + * + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer service. */ public function __construct(RendererInterface $renderer) { $this->renderer = $renderer; @@ -45,9 +53,14 @@ public static function create(ContainerInterface $container): self { /** * Returns a raw page for the iframe embed. * - * Set edit to true for an editor. + * @param \Drupal\media\Entity\Media $media + * Media entity. + * @param bool $edit + * TRUE to open Collabora Online in edit mode. + * FALSE to open Collabora Online in readonly mode. * - * @return Response + * @return \Symfony\Component\HttpFoundation\Response + * Response suitable for iframe, without the usual page decorations. */ public function editor(Media $media, $edit = FALSE) { $options = [ diff --git a/src/Controller/WopiController.php b/src/Controller/WopiController.php index 9ca7d088..4f22dd4a 100644 --- a/src/Controller/WopiController.php +++ b/src/Controller/WopiController.php @@ -40,6 +40,17 @@ public static function permissionDenied(): Response { ); } + /** + * Handles the WOPI 'info' request for a media entity. + * + * @param string $id + * Media id from url. + * @param \Symfony\Component\HttpFoundation\Request $request + * Request object with query parameters. + * + * @return \Symfony\Component\HttpFoundation\Response + * The response with file contents. + */ public function wopiCheckFileInfo(string $id, Request $request) { $token = $request->query->get('access_token'); @@ -97,6 +108,17 @@ public function wopiCheckFileInfo(string $id, Request $request) { return $response; } + /** + * Handles the wopi "content" request for a media entity. + * + * @param string $id + * Media id from url. + * @param \Symfony\Component\HttpFoundation\Request $request + * Request object with query parameters. + * + * @return \Symfony\Component\HttpFoundation\Response + * The response with file contents. + */ public function wopiGetFile(string $id, Request $request) { $token = $request->query->get('access_token'); @@ -121,6 +143,17 @@ public function wopiGetFile(string $id, Request $request) { return $response; } + /** + * Handles the wopi "save" request for a media entity.. + * + * @param string $id + * Media id from url. + * @param \Symfony\Component\HttpFoundation\Request $request + * Request object with headers, query parameters and payload. + * + * @return \Symfony\Component\HttpFoundation\Response + * The response. + */ public function wopiPutFile(string $id, Request $request) { $token = $request->query->get('access_token'); $timestamp = $request->headers->get('x-cool-wopi-timestamp'); @@ -214,11 +247,15 @@ public function wopiPutFile(string $id, Request $request) { /** * The WOPI entry point. * - * action: 'info', 'content' or 'save'. - * id: the ID of the media. - * request: The request as originating. + * @param string $action + * One of 'info', 'content' or 'save', depending with path is visited. + * @param string $id + * Media id from url. + * @param \Symfony\Component\HttpFoundation\Request $request + * Request object for headers and query parameters. * - * @return Response + * @return \Symfony\Component\HttpFoundation\Response + * Response to be consumed by Collabora Online. */ public function wopi(string $action, string $id, Request $request) { $returnCode = Response::HTTP_BAD_REQUEST; diff --git a/src/Cool/CoolRequest.php b/src/Cool/CoolRequest.php index 394530ac..631457aa 100644 --- a/src/Cool/CoolRequest.php +++ b/src/Cool/CoolRequest.php @@ -13,9 +13,13 @@ namespace Drupal\collabora_online\Cool; /** - * Get the discovery XML content + * Gets the contents of discovery.xml from the Collabora server. * - * Return `false` in case of error. + * @param string $server + * Url of the Collabora Online server. + * + * @return string|false + * The full contents of discovery.xml, or FALSE on failure. */ function getDiscovery($server) { $discovery_url = $server . '/hosting/discovery'; @@ -36,6 +40,19 @@ function getDiscovery($server) { return $res; } +/** + * Extracts a WOPI url from the parsed discovery.xml. + * + * @param \SimpleXMLElement|null|false $discovery_parsed + * Parsed contents from discovery.xml from the Collabora server. + * Currently, NULL or FALSE are supported too, but lead to NULL return value. + * @param string $mimetype + * MIME type for which to fetch the WOPI url. E.g. 'text/plain'. + * + * @return mixed|null + * WOPI url as configured for this MIME type in discovery.xml, or NULL if none + * was found for the given MIME type. + */ function getWopiSrcUrl($discovery_parsed, $mimetype) { if ($discovery_parsed === NULL || $discovery_parsed == FALSE) { return NULL; @@ -47,13 +64,34 @@ function getWopiSrcUrl($discovery_parsed, $mimetype) { return NULL; } +/** + * Checks if a string starts with another string. + * + * @param string $s + * Haystack. + * @param string $ss + * Needle. + * + * @return bool + * TRUE if $ss is a prefix of $s. + * + * @see str_starts_with() + */ function strStartsWith($s, $ss) { $res = strrpos($s, $ss); return !is_bool($res) && $res == 0; } +/** + * Helper class to fetch a WOPI client url. + */ class CoolRequest { + /** + * Error code from last attempt to fetch the client WOPI url. + * + * @var int + */ private $error_code; const ERROR_MSG = [ @@ -67,18 +105,37 @@ class CoolRequest { 204 => 'Warning! You have to specify the scheme protocol too (http|https) for the server address.', ]; + /** + * The WOPI url that was last fetched, or '' as initial value. + * + * @var int + */ private $wopi_src; + /** + * Constructor. + */ public function __construct() { $this->error_code = 0; $this->wopi_src = ''; } + /** + * Gets an error string from the last attempt to fetch the WOPI url. + * + * @return string + * Error string containing int error code and a message. + */ public function errorString() { return $this->error_code . ': ' . static::ERROR_MSG[$this->error_code]; } - /** Return the wopi client URL */ + /** + * Gets the URL for the WOPI client. + * + * @return string|null + * The WOPI client url, or NULL on failure. + */ public function getWopiClientURL() { $_HOST_SCHEME = isset($_SERVER['HTTPS']) ? 'https' : 'http'; $default_config = \Drupal::config('collabora_online.settings'); diff --git a/src/Cool/CoolUtils.php b/src/Cool/CoolUtils.php index 1db446f7..f8c4b8e3 100644 --- a/src/Cool/CoolUtils.php +++ b/src/Cool/CoolUtils.php @@ -18,9 +18,20 @@ use Firebase\JWT\JWT; use Firebase\JWT\Key; +/** + * Class with various static methods. + */ class CoolUtils { - /** Get the file from the Media entity */ + /** + * Gets the file referenced by a media entity. + * + * @param \Drupal\media\Entity\Media $media + * The media entity. + * + * @return \Drupal\file\FileInterface|null + * The file entity, or NULL if not found. + */ public static function getFile(Media $media) { $fid = $media->getSource()->getSourceFieldValue($media); $file = File::load($fid); @@ -28,19 +39,40 @@ public static function getFile(Media $media) { return $file; } - /** Get the file based on the entity id */ + /** + * Gets a file based on the media id. + * + * @param int|string $id + * Media id which might be in strong form like '123'. + * + * @return \Drupal\file\FileInterface|null + * File referenced by the media entity, or NULL if not found. + */ public static function getFileById($id) { /** @var \Drupal\media\MediaInterface|null $media */ $media = \Drupal::entityTypeManager()->getStorage('media')->load($id); return CoolUtils::getFile($media); } + /** + * Sets the file entity reference for a media entity. + * + * @param \Drupal\media\Entity\Media $media + * Media entity to be modified. + * @param \Drupal\file\Entity\File $source + * File entity to reference. + */ public static function setMediaSource(Media $media, File $source) { $name = $media->getSource()->getSourceFieldDefinition($media->bundle->entity)->getName(); $media->set($name, $source); } - /** Obtain the signing key from the key storage */ + /** + * Obtains the signing key from the key storage. + * + * @return string + * The key value. + */ public static function getKey() { $default_config = \Drupal::config('collabora_online.settings'); $key_id = $default_config->get('cool')['key_id']; @@ -49,11 +81,22 @@ public static function getKey() { return $key; } - /** Verify JWT token + /** + * Decodes and verifies a JWT token. * - * Verification include: + * Verification include: * - matching $id with fid in the payload - * - verifying the expiration + * - verifying the expiration. + * + * @param string $token + * The token to verify. + * @param int|string $id + * Media id for which the token was created. + * This could be in string form like '123'. + * + * @return \stdClass|null + * Data decoded from the token, or NULL on failure or if the token has + * expired. */ public static function verifyTokenForId( #[\SensitiveParameter] @@ -75,7 +118,10 @@ public static function verifyTokenForId( } /** - * Return the TTL of the token in seconds, from the EPOCH. + * Gets the TTL of the token in seconds, from the EPOCH. + * + * @return int + * Token TTL in seconds. */ public static function getAccessTokenTtl() { $default_config = \Drupal::config('collabora_online.settings'); @@ -85,8 +131,7 @@ public static function getAccessTokenTtl() { } /** - * Create a JWT token for the Media with id $id, a $ttl, and an - * eventual write permission. + * Creates a JWT token for a media entity. * * The token will carry the following: * @@ -97,6 +142,16 @@ public static function getAccessTokenTtl() { * - wri: if true, then this token has write permissions. * * The signing key is stored in Drupal key management. + * + * @param int|string $id + * Media id, which could be in string form like '123'. + * @param int $ttl + * Access token TTL in seconds. + * @param bool $can_write + * TRUE if the token is for an editor in write/edit mode. + * + * @return string + * The access token. */ public static function tokenForFileId($id, $ttl, $can_write = FALSE) { $payload = [ @@ -112,8 +167,7 @@ public static function tokenForFileId($id, $ttl, $can_write = FALSE) { } /** - * List of read only formats. Currently limited to the one Drupal - * accept. + * List of read only formats. Currently limited to the one Drupal accept. */ const READ_ONLY = [ 'application/x-iwork-keynote-sffkey' => TRUE, @@ -122,24 +176,48 @@ public static function tokenForFileId($id, $ttl, $can_write = FALSE) { ]; /** - * Return if we can edit that media file. + * Determines if we can edit that media file. * * There are few types that Collabora Online only views. + * + * @param \Drupal\file\Entity\File $file + * File entity. + * + * @return bool + * TRUE if the file has a file type that is supported for editing. + * FALSE if the file can only be opened as read-only. */ public static function canEdit(File $file) { $mimetype = $file->getMimeType(); return !array_key_exists($mimetype, static::READ_ONLY); } - /** Return the mime type for the document. + /** + * Gets the mime type for the document. + * + * Drupal will figure it out for us. + * + * @param \Drupal\file\Entity\File $file + * File entity. * - * Drupal will figure it out for us. + * @return string|null + * The mime type, or NULL if it cannot be determined. */ public static function getDocumentType(File $file) { return $file->getMimeType(); } - /** Return the editor / viewer Drupal URL from the routes configured. */ + /** + * Gets the editor / viewer Drupal URL from the routes configured. + * + * @param \Drupal\media\Entity\Media $media + * Media entity that holds the file to open in the editor. + * @param bool $can_write + * TRUE for an edit url, FALSE for a read-only preview url. + * + * @return \Drupal\Core\Url + * Editor url to visit as full-page, or to embed in an iframe. + */ public static function getEditorUrl(Media $media, $can_write = FALSE) { if ($can_write) { return Url::fromRoute('collabora-online.edit', ['media' => $media->id()]); @@ -150,18 +228,19 @@ public static function getEditorUrl(Media $media, $can_write = FALSE) { } /** - * Get a render array for a cool viewer. - * - * @param Media $media - * The media entity to view / edit + * Gets a render array for a cool viewer. * + * @param \Drupal\media\Entity\Media $media + * The media entity to view / edit. * @param bool $can_write * Whether this is a viewer (false) or an edit (true). Permissions will * also be checked. - * - * @param array $options + * @param array{closebutton: bool} $options * Options for the renderer. Current values: * - "closebutton" if "true" will add a close box. (see COOL SDK) + * + * @return array|array{error: string} + * A stub render element array, or an array with an error on failure. */ public static function getViewerRender(Media $media, bool $can_write, $options = NULL) { $default_config = \Drupal::config('collabora_online.settings'); diff --git a/src/Form/ConfigForm.php b/src/Form/ConfigForm.php index 9831e30b..9d559fd9 100644 --- a/src/Form/ConfigForm.php +++ b/src/Form/ConfigForm.php @@ -15,6 +15,9 @@ use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; +/** + * Form to configure module settings for Collabora. + */ class ConfigForm extends ConfigFormBase { const SETTINGS = 'collabora_online.settings';