diff --git a/.cspell-project-words.txt b/.cspell-project-words.txt index f6590a923d2a8fc2769239bd9cab610b72882c43..4d38129f48fd63b74a90c061ef274cb4e248dce2 100644 --- a/.cspell-project-words.txt +++ b/.cspell-project-words.txt @@ -26,6 +26,7 @@ docblocks doubledash Drumm drumm +drupalcode elseifs Encaps endspan diff --git a/api.install b/api.install index ec5498a1e1cf851423c3c9174ccd3880c9232880..36cb88d090dc9b95788e26547a21ddeeb451d3ae 100644 --- a/api.install +++ b/api.install @@ -10,6 +10,7 @@ use Drupal\api\ExtendedQueries; use Drupal\api\Parser; use Drupal\api\StorageSchema\ApiContentStorageSchema; use Drupal\api\Utilities; +use Drupal\Core\Field\BaseFieldDefinition; use Drupal\field\Entity\FieldStorageConfig; /** @@ -101,3 +102,27 @@ function api_update_9202() { } } } + +/** + * Add new field. + */ +function api_update_10001() { + $storage_definition = BaseFieldDefinition::create('string') + ->setLabel(t('Git Link')) + ->setDescription(t('Link where to find the source code. eg: https://git.drupalcode.org/project/drupal')) + ->setSetting('max_length', 512) + ->setDisplayOptions('form', [ + 'type' => 'string_textfield', + 'weight' => -5, + ]) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayOptions('view', [ + 'label' => 'hidden', + 'type' => 'string', + 'weight' => -5, + ]) + ->setDisplayConfigurable('view', TRUE); + + $update_manager = \Drupal::entityDefinitionUpdateManager(); + $update_manager->installFieldStorageDefinition('git_link', 'project', 'project', $storage_definition); +} diff --git a/api.module b/api.module index ad44281524aec5ac64fe8ecc7c0379c4bbe2667e..f76ab32e626644db5630aacf179ad64192b6c763 100644 --- a/api.module +++ b/api.module @@ -429,6 +429,8 @@ function api_theme() { 'hierarchy' => NULL, 'objects' => NULL, 'code' => NULL, + 'git_link' => NULL, + 'git_link_domain' => NULL, 'related_topics' => NULL, 'see' => NULL, 'deprecated' => NULL, @@ -452,6 +454,8 @@ function api_theme() { 'object' => NULL, 'class' => NULL, 'code' => NULL, + 'git_link' => NULL, + 'git_link_domain' => NULL, 'tags' => NULL, 'call_links' => [], ], @@ -464,6 +468,8 @@ function api_theme() { 'documentation' => NULL, 'objects' => NULL, 'code' => NULL, + 'git_link' => NULL, + 'git_link_domain' => NULL, 'see' => NULL, 'deprecated' => NULL, 'related_topics' => NULL, diff --git a/css/api.css b/css/api.css index fbb7e45a7f065578b08eb800baeef3f7c6f19714..a63c0dfd518dd4da97213a00bf24495368fb8b45 100644 --- a/css/api.css +++ b/css/api.css @@ -77,3 +77,11 @@ ol.api-alternatives li { .api-deprecated { background-color: #ffecd8; } + +.git-link-wrapper .git-link { + padding-left: 22px; + background-image: url(../images/git-icon.png); + background-repeat: no-repeat; + background-position: top left; + background-size: 18px 18px; +} diff --git a/images/git-icon.png b/images/git-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..51f4ae5404fc79be09e69171bfc9d34d48810297 Binary files /dev/null and b/images/git-icon.png differ diff --git a/src/Commands/ApiCommands.php b/src/Commands/ApiCommands.php index 9e4459a7d1b6c5c2b547981ae3134310647799e2..d8b1157aed4b73dca569393d6bbc1151f1b81090 100644 --- a/src/Commands/ApiCommands.php +++ b/src/Commands/ApiCommands.php @@ -276,6 +276,7 @@ class ApiCommands extends DrushCommands { ->setTitle($formatted_name) ->setType($type) ->setSlug($project_name) + ->setGitLink($repo_url) ->save(); $this->logger()->success(dt('Project ' . $project_name . ' was created.')); } diff --git a/src/Entity/Project.php b/src/Entity/Project.php index 139c9c6a45fe34b50d11b2c944fe0cb450ca475a..70552519fd43f7ec72fbe255fe7e5e2c01466b2d 100644 --- a/src/Entity/Project.php +++ b/src/Entity/Project.php @@ -99,6 +99,22 @@ class Project extends ContentEntityBase implements ProjectInterface { ]) ->setDisplayConfigurable('view', TRUE); + $fields['git_link'] = BaseFieldDefinition::create('string') + ->setLabel(t('Git Link')) + ->setDescription(t('Link where to find the source code. eg: https://git.drupalcode.org/project/drupal')) + ->setSetting('max_length', 512) + ->setDisplayOptions('form', [ + 'type' => 'string_textfield', + 'weight' => -5, + ]) + ->setDisplayConfigurable('form', TRUE) + ->setDisplayOptions('view', [ + 'label' => 'hidden', + 'type' => 'string', + 'weight' => -5, + ]) + ->setDisplayConfigurable('view', TRUE); + $fields['type'] = BaseFieldDefinition::create('string') ->setLabel(t('Type')) ->setDescription(t('Type of project (core, module, theme, etc.).')) @@ -172,6 +188,21 @@ class Project extends ContentEntityBase implements ProjectInterface { return $this; } + /** + * {@inheritdoc} + */ + public function getGitLink() { + return $this->get('git_link')->value; + } + + /** + * {@inheritdoc} + */ + public function setGitLink($git_link) { + $this->set('git_link', $git_link); + return $this; + } + /** * {@inheritdoc} */ diff --git a/src/Formatter.php b/src/Formatter.php index edb22e5b939f7370d90edee97728615e3f343421..e4c932fa037ad71515d904a13d5521dd29a00e4a 100644 --- a/src/Formatter.php +++ b/src/Formatter.php @@ -2665,6 +2665,53 @@ class Formatter { return $variables; } + /** + * Build a link to the source code of the file of an object. + * + * @param \Drupal\api\Interfaces\DocBlockInterface $object + * Object that was generated from the code. + * @param \Drupal\api\Interfaces\BranchInterface $branch + * Branch the object is in. + * + * @return string + * Url string of the file where the object is. + */ + protected static function getObjectGitLink(DocBlockInterface $object, BranchInterface $branch) { + $link = ''; + // Links to source code. + $project_git_link = $branch->getProject()->getGitLink() ?? NULL; + if (!empty($project_git_link)) { + $project_git_link = rtrim($project_git_link, '/'); + $host = parse_url($project_git_link, PHP_URL_HOST); + switch ($host) { + // https://github.com/drupal/drupal/blob/8.5.x/.htaccess + case 'github.com': + $suffix = '/blob'; + break; + + // https://git.drupalcode.org/project/drupal/-/blob/11.x/.htaccess + case 'gitlab.com': + case 'git.drupalcode.org': + $suffix = '/-/blob'; + break; + + // https://bitbucket.org/drupalorg-infrastructure/drupal.org/src/7.x-prod/drupal.org.make + case 'bitbucket.org': + $suffix = '/src'; + break; + + default: + $suffix = ''; + } + + $project_git_link = str_replace($suffix, '', $project_git_link); + $base_path = $project_git_link . $suffix; + $link = $base_path . '/' . $branch->getSlug() . '/' . $object->getFileName(); + } + + return $link; + } + /** * Prepare the render array to display a file. * @@ -2716,6 +2763,10 @@ class Formatter { 'yml_keys', ], $file, $branch); + // Links to source code. + $git_link = self::getObjectGitLink($file, $branch); + $git_link_domain = $git_link ? parse_url($git_link, PHP_URL_HOST) : NULL; + $variables = [ '#title' => $file->getTitle(), ]; @@ -2725,6 +2776,8 @@ class Formatter { '#documentation' => $documentation, '#objects' => $objects, '#code' => $code, + '#git_link' => $git_link, + '#git_link_domain' => $git_link_domain, '#see' => $see, '#deprecated' => $deprecated, '#related_topics' => $related_topics, @@ -3604,6 +3657,10 @@ class Formatter { $links = self::buildReferencesSection($reference_types, $item, $branch); + // Links to source code. + $git_link = self::getObjectGitLink($item, $branch); + $git_link_domain = $git_link ? parse_url($git_link, PHP_URL_HOST) : NULL; + // Put it all together. $output = [ '#title' => $item->getObjectType() . ' ' . $item->getTitle(), @@ -3614,6 +3671,8 @@ class Formatter { '#hierarchy' => $hierarchy, '#objects' => $objects, '#code' => $code, + '#git_link' => $git_link, + '#git_link_domain' => $git_link_domain, '#related_topics' => $related_topics, '#see' => $see, '#deprecated' => $deprecated, @@ -3962,6 +4021,10 @@ class Formatter { $links = self::buildReferencesSection(['use'], $item, $branch); + // Links to source code. + $git_link = self::getObjectGitLink($item, $branch); + $git_link_domain = $git_link ? parse_url($git_link, PHP_URL_HOST) : NULL; + // Put it all together. $output = [ '#title' => $item->getTitle(), @@ -3970,6 +4033,8 @@ class Formatter { '#object' => $item, '#class' => $class, '#code' => $code, + '#git_link' => $git_link, + '#git_link_domain' => $git_link_domain, '#tags' => $tags, '#call_links' => $links, ]; diff --git a/src/Interfaces/ProjectInterface.php b/src/Interfaces/ProjectInterface.php index d7f2ff50dc9d4a5a779cef2214bfd43206305139..95a2925412cb410552a82318072ec2f578692bb8 100644 --- a/src/Interfaces/ProjectInterface.php +++ b/src/Interfaces/ProjectInterface.php @@ -67,6 +67,25 @@ interface ProjectInterface extends ContentEntityInterface, EntityChangedInterfac */ public function setSlug($slug); + /** + * Gets the project git link. + * + * @return string + * Git link for this project. + */ + public function getGitLink(); + + /** + * Sets the project git link. + * + * @param string $git_link + * The project git link. + * + * @return \Drupal\api\Interfaces\ProjectInterface + * The called project entity. + */ + public function setGitLink($git_link); + /** * Gets the project type. * diff --git a/src/Parser.php b/src/Parser.php index ad5ea4a2a62da82a7d00dd1b3d655a2e7fdfc86d..19f59e9819be10ec42ef74129cb39ad9fc383db6 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -2187,7 +2187,7 @@ class Parser { $docblock[$property] = ''; while (preg_match('/' . self::RE_TAG_START . $property . '\s(.*?)(?=\n' . self::RE_TAG_START . '|$)/s', $docblock['content'], $matches)) { - // Replace the first occurrence only as similar namespaces could be truncated. + // Replace the first occurrence only. $pos = strpos($docblock['content'], $matches[0]); if ($pos !== FALSE) { $docblock['content'] = substr_replace($docblock['content'], '', $pos, strlen($matches[0])); diff --git a/templates/class-page.html.twig b/templates/class-page.html.twig index 1e32506dab1ce0eaecd2e03ad84534cced2563c4..82329032cb7499a3ed5330073e0184466e461835 100644 --- a/templates/class-page.html.twig +++ b/templates/class-page.html.twig @@ -13,6 +13,7 @@ * - hierarchy: Class hierarchy, if any. * - objects: Listing of member variables, constants, and functions. * - defined: HTML reference to file that defines this class. + * - git_link: Link to the origin of the source code. * - code: HTML-formatted declaration and code for this class. * - related_topics: List of related groups/topics. * - call_links: Links to uses of this class, etc. @@ -67,6 +68,12 @@ {{ namespace }} {% endif %} +{% if git_link is not empty %} + <div class="git-link-wrapper"> + <a class="git-link" href="{{ git_link }}">{{ 'View in'|t }} {{ git_link_domain }}</a> + </div> +{% endif %} + <details class="api-view-source"> <summary> {{ 'View source'|t }} diff --git a/templates/file-page.html.twig b/templates/file-page.html.twig index 9ca810385fcd9f825bff53fb698d47d74fabd400..0179166c6c1235a0fe1b970e074094b12c5de33f 100644 --- a/templates/file-page.html.twig +++ b/templates/file-page.html.twig @@ -12,6 +12,7 @@ * - objects: List of functions, classes, etc. defined in the file. * - call_links: Links to calling functions (for theme templates). * - code: Source code for the file. + * - git_link: Link to the origin of the source code. * - related_topics: List of related groups/topics. * - defined: Location of the file. * - branch: Object with information about the branch. @@ -51,6 +52,12 @@ <h3>{{ 'File'|t }}</h3> {{ defined }} +{% if git_link is not empty %} + <div class="git-link-wrapper"> + <a class="git-link" href="{{ git_link }}">{{ 'View in'|t }} {{ git_link_domain }}</a> + </div> +{% endif %} + <details class="api-view-source"> <summary> {{ 'View source'|t }} diff --git a/templates/service-page.html.twig b/templates/service-page.html.twig index fff310159b3567e0e2db6fecc2a2c259ae49b031..4a058a5c880660156b79a85db42cfd4d2b4b1f61 100644 --- a/templates/service-page.html.twig +++ b/templates/service-page.html.twig @@ -7,6 +7,7 @@ * - alternatives: List of alternate versions (branches) of this class. * - defined: HTML reference to file that defines this service. * - code: HTML-formatted declaration and code for this service. + * - git_link: Link to the origin of the source code. * - tags: List of tags for this service. * - branch: Object with information about the branch. * - object: Object with information about the service. @@ -36,6 +37,12 @@ <h3>{{ 'File'|t }}</h3> {{ defined }} +{% if git_link is not empty %} + <div class="git-link-wrapper"> + <a class="git-link" href="{{ git_link }}">{{ 'View in'|t }} {{ git_link_domain }}</a> + </div> +{% endif %} + <details class="api-view-source"> <summary> {{ 'View source'|t }}