placeholderHeading = $headingLevel === null; $this->headingLevel = $this->placeholderHeading ? static::PLACEHOLDER_HEADING_LEVEL : $headingLevel; } /** * @inheritDoc */ public function jsonSerialize( bool $deep = false, ?callable $callback = null ): array { return array_merge( parent::jsonSerialize( $deep, $callback ), [ 'headingLevel' => $this->headingLevel === static::PLACEHOLDER_HEADING_LEVEL ? null : $this->headingLevel, // Used for topic subscriptions. Not added to CommentItem's yet as there is // no use case for it. 'name' => $this->name, ] ); } /** * Get a title based on the hash ID, such that it can be linked to * * @return string Title */ public function getLinkableTitle(): string { $title = ''; // If this comment is in 0th section, there's no section title for the edit summary if ( !$this->isPlaceholderHeading() ) { // , or in Parsoid HTML $headline = $this->getRange()->startContainer; Assert::precondition( $headline instanceof Element, 'HeadingItem refers to an element node' ); $id = $headline->getAttribute( 'id' ); if ( $id ) { // Replace underscores with spaces to undo Sanitizer::escapeIdInternal(). // This assumes that $wgFragmentMode is [ 'html5', 'legacy' ] or [ 'html5' ], // otherwise the escaped IDs are super garbled and can't be unescaped reliably. $title = str_replace( '_', ' ', $id ); } // else: Not a real section, probably just HTML markup in wikitext } return $title; } /** * @return int Heading level (1-6) */ public function getHeadingLevel(): int { return $this->headingLevel; } /** * @param int $headingLevel Heading level (1-6) */ public function setHeadingLevel( int $headingLevel ): void { $this->headingLevel = $headingLevel; } /** * @return bool */ public function isPlaceholderHeading(): bool { return $this->placeholderHeading; } /** * @param bool $placeholderHeading */ public function setPlaceholderHeading( bool $placeholderHeading ): void { $this->placeholderHeading = $placeholderHeading; } /** * Check whether this heading can be used for topic subscriptions. * * @return bool */ public function isSubscribable(): bool { return ( // Placeholder headings have nothing to attach the button to. !$this->isPlaceholderHeading() && // We only allow subscribing to level 2 headings, because the user interface for sub-headings // would be difficult to present. $this->getHeadingLevel() === 2 && // Check if the name corresponds to a section that contain no comments (only sub-sections). // They can't be distinguished from each other, so disallow subscribing. $this->getName() !== 'h-' ); } /** * @inheritDoc */ public function getTranscludedFrom() { // Placeholder headings break the usual logic, because their ranges are collapsed if ( $this->isPlaceholderHeading() ) { return false; } // Collapsed ranges should otherwise be impossible, but they're not (T299583) // TODO: See if we can fix the root cause, and remove this? if ( $this->getRange()->collapsed ) { return false; } return parent::getTranscludedFrom(); } }