1<?php
2declare( strict_types = 1 );
3
4namespace Wikimedia\Parsoid\Ext\Cite;
5
6use stdClass;
7use Wikimedia\Parsoid\DOM\Document;
8use Wikimedia\Parsoid\DOM\Element;
9use Wikimedia\Parsoid\Ext\DOMUtils;
10use Wikimedia\Parsoid\Ext\ParsoidExtensionAPI;
11use Wikimedia\Parsoid\Utils\DOMCompat;
12
13/**
14 * Helper class used by `<references>` implementation.
15 */
16class RefGroup {
17
18	/**
19	 * @var string
20	 */
21	public $name;
22
23	/**
24	 * @var stdClass[]
25	 */
26	public $refs;
27
28	/**
29	 * @var stdClass[]
30	 */
31	public $indexByName;
32
33	/**
34	 * @param string $group
35	 */
36	public function __construct( string $group = '' ) {
37		$this->name = $group;
38		$this->refs = [];
39		$this->indexByName = [];
40	}
41
42	/**
43	 * Generate leading linkbacks
44	 * @param ParsoidExtensionAPI $extApi
45	 * @param string $href
46	 * @param ?string $group
47	 * @param string $text
48	 * @param Document $ownerDoc
49	 * @return Element
50	 */
51	private static function createLinkback(
52		ParsoidExtensionAPI $extApi, string $href, ?string $group,
53		string $text, Document $ownerDoc
54	): Element {
55		$a = $ownerDoc->createElement( 'a' );
56		$s = $ownerDoc->createElement( 'span' );
57		$textNode = $ownerDoc->createTextNode( $text . ' ' );
58		$a->setAttribute( 'href', $extApi->getPageUri() . '#' . $href );
59		$s->setAttribute( 'class', 'mw-linkback-text' );
60		if ( $group ) {
61			$a->setAttribute( 'data-mw-group', $group );
62		}
63		$s->appendChild( $textNode );
64		$a->appendChild( $s );
65		return $a;
66	}
67
68	/**
69	 * @param ParsoidExtensionAPI $extApi
70	 * @param Element $refsList
71	 * @param stdClass $ref
72	 */
73	public function renderLine(
74		ParsoidExtensionAPI $extApi, Element $refsList, stdClass $ref
75	): void {
76		$ownerDoc = $refsList->ownerDocument;
77
78		// Generate the li and set ref content first, so the HTML gets parsed.
79		// We then append the rest of the ref nodes before the first node
80		$li = $ownerDoc->createElement( 'li' );
81		$refDir = $ref->dir;
82		$refTarget = $ref->target;
83		$refContentId = $ref->contentId;
84		$refGroup = $ref->group;
85		DOMUtils::addAttributes( $li, [
86				'about' => '#' . $refTarget,
87				'id' => $refTarget,
88				'class' => ( $refDir === 'rtl' || $refDir === 'ltr' ) ? 'mw-cite-dir-' . $refDir : null
89			]
90		);
91		$reftextSpan = $ownerDoc->createElement( 'span' );
92		DOMUtils::addAttributes(
93			$reftextSpan,
94			[
95				'id' => 'mw-reference-text-' . $refTarget,
96				'class' => 'mw-reference-text',
97			]
98		);
99		if ( $refContentId ) {
100			// `sup` is the wrapper created by Ref::sourceToDom()'s call to
101			// `extApi->extTagToDOM()`.  Only its contents are relevant.
102			$sup = $extApi->getContentDOM( $refContentId )->firstChild;
103			DOMUtils::migrateChildren( $sup, $reftextSpan );
104			'@phan-var Element $sup';  /** @var Element $sup */
105			DOMCompat::remove( $sup );
106			$extApi->clearContentDOM( $refContentId );
107		}
108		$li->appendChild( $reftextSpan );
109
110		if ( count( $ref->linkbacks ) === 1 ) {
111			$linkback = self::createLinkback( $extApi, $ref->id, $refGroup, "↑", $ownerDoc );
112			$linkback->setAttribute( 'rel', 'mw:referencedBy' );
113			$li->insertBefore( $linkback, $reftextSpan );
114		} else {
115			// 'mw:referencedBy' span wrapper
116			$span = $ownerDoc->createElement( 'span' );
117			$span->setAttribute( 'rel', 'mw:referencedBy' );
118			$li->insertBefore( $span, $reftextSpan );
119
120			foreach ( $ref->linkbacks as $i => $lb ) {
121				$span->appendChild(
122					self::createLinkback( $extApi, $lb, $refGroup, (string)( $i + 1 ), $ownerDoc )
123				);
124			}
125		}
126
127		// Space before content node
128		$li->insertBefore( $ownerDoc->createTextNode( ' ' ), $reftextSpan );
129
130		// Add it to the ref list
131		$refsList->appendChild( $li );
132	}
133}
134