Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
61.70% covered (warning)
61.70%
29 / 47
90.00% covered (success)
90.00%
9 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
Tree
61.70% covered (warning)
61.70%
29 / 47
90.00% covered (success)
90.00%
9 / 10
52.72
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setAncestorDepth
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setDescendantDepth
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addAncestors
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addDescendants
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addAncestorsOrDescendants
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 hasAncestorsOrDescendants
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setFormat
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getWikitext
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
90
 getTreeSource
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3namespace MediaWiki\Extension\Genealogy;
4
5use ExtensionRegistry;
6use Html;
7use MediaWiki\Page\WikiPageFactory;
8use Parser;
9use Title;
10use Wikimedia\Rdbms\ILoadBalancer;
11
12class Tree {
13
14    /** @var string The tree's format, either 'graphviz' or 'mermaid'. */
15    protected $format = 'graphviz';
16
17    /** @var Person[] */
18    protected $ancestors = [];
19
20    /** @var Person[] */
21    protected $descendants = [];
22
23    /** @var int */
24    protected $ancestorDepth;
25
26    /** @var int */
27    protected $descendantDepth;
28
29    /** @var ILoadBalancer */
30    private ILoadBalancer $loadBalancer;
31
32    /** @var WikiPageFactory */
33    private WikiPageFactory $wikiPageFactory;
34
35    public function __construct(
36        ILoadBalancer $loadBalancer,
37        WikiPageFactory $wikiPageFactory
38    ) {
39        $this->loadBalancer = $loadBalancer;
40        $this->wikiPageFactory = $wikiPageFactory;
41    }
42
43    /**
44     * Set the number of levels the tree will go up to from the ancestors' starting points.
45     * @param int $ancestorDepth The new ancestor depth.
46     */
47    public function setAncestorDepth( $ancestorDepth ) {
48        $this->ancestorDepth = (int)$ancestorDepth;
49    }
50
51    /**
52     * Set the number of levels the tree will go down to from the descendants' starting points.
53     * @param int $descendantDepth The new descendant depth.
54     */
55    public function setDescendantDepth( $descendantDepth ) {
56        $this->descendantDepth = (int)$descendantDepth;
57    }
58
59    /**
60     * Add ancestor starting points to this tree, from which to traverse upwards.
61     * @param string[] $ancestors Array of page titles.
62     */
63    public function addAncestors( $ancestors ) {
64        $this->addAncestorsOrDescendants( 'ancestors', $ancestors );
65    }
66
67    /**
68     * Add descendant starting points to this tree, from which to traverse downwards.
69     * @param string[] $descendants Array of page titles.
70     */
71    public function addDescendants( $descendants ) {
72        $this->addAncestorsOrDescendants( 'descendants', $descendants );
73    }
74
75    /**
76     * Add ancestor or descendant starting points to this tree.
77     * @param string $type Either 'ancestors' or 'descendants'.
78     * @param string[] $list Array of page titles.
79     */
80    protected function addAncestorsOrDescendants( $type, $list ) {
81        foreach ( $list as $a ) {
82            $title = Title::newFromText( $a );
83            if ( $title ) {
84                $person = new Person( $this->loadBalancer, $this->wikiPageFactory, $title );
85                $this->{$type}[] = $person;
86            }
87        }
88    }
89
90    /**
91     * Whether any ancestors or descendants have been added to this tree.
92     * @return bool
93     */
94    public function hasAncestorsOrDescendants() {
95        return ( count( $this->ancestors ) + count( $this->descendants ) ) > 0;
96    }
97
98    /**
99     * Set the output format for the tree.
100     *
101     * @param string $format Either 'graphviz' or 'mermaid' (case insensitive).
102     * @return void
103     */
104    public function setFormat( $format ) {
105        $this->format = strtolower( $format );
106    }
107
108    /**
109     * Get the wikitext output for the tree.
110     *
111     * @param Parser $parser The parser.
112     * @return string Unsafe half-parsed HTML, as returned by Parser::recursiveTagParse().
113     */
114    public function getWikitext( Parser $parser ) {
115        // If there's nothing to render, give up.
116        if ( !$this->hasAncestorsOrDescendants() ) {
117            return '';
118        }
119
120        $extenstionRegistry = ExtensionRegistry::getInstance();
121        $diagramsInstalled = $extenstionRegistry->isLoaded( 'Diagrams' );
122        $graphvizInstalled = $extenstionRegistry->isLoaded( 'GraphViz' )
123            || $diagramsInstalled;
124        $mermaidInstalled = $extenstionRegistry->isLoaded( 'Mermaid' );
125        $treeSource = $this->getTreeSource();
126        if ( $this->format === 'mermaid' && $mermaidInstalled ) {
127            $wikitext = "{{#mermaid:$treeSource|config.flowchart.useMaxWidth=0|config.theme=neutral}}";
128            $out = $parser->recursiveTagParse( $wikitext );
129        } elseif ( $this->format === 'mermaid' && $diagramsInstalled ) {
130            $out = $parser->recursiveTagParse( "<mermaid>$treeSource</mermaid>" );
131        } elseif ( $this->format === 'graphviz' && $graphvizInstalled ) {
132            $out = $parser->recursiveTagParse( "<graphviz>$treeSource</graphviz>" );
133        } else {
134            $err = wfMessage( 'genealogy-invalid-tree-format', $this->format )->text();
135            return Html::element( 'p', [ 'class' => 'error' ], $err );
136        }
137
138        // Debugging.
139        // $out .= $parser->recursiveTagParse( "<pre>$treeSource</pre>" );
140
141        return $out;
142    }
143
144    /**
145     * @return string
146     */
147    public function getTreeSource() {
148        $traverser = new Traverser();
149        $formatter = $this->format === 'mermaid'
150            ? new MermaidTreeFormatter( $this->ancestors, $this->descendants )
151            : new GraphVizTreeFormatter( $this->ancestors, $this->descendants );
152        $formatter->setName( md5(
153            implode( '', $this->ancestors )
154            . implode( '', $this->descendants )
155            . $this->ancestorDepth
156            . $this->descendantDepth
157        ) );
158        $traverser->register( [ $formatter, 'visit' ] );
159
160        foreach ( $this->ancestors as $ancestor ) {
161            $traverser->ancestors( $ancestor, $this->ancestorDepth );
162        }
163
164        foreach ( $this->descendants as $descendant ) {
165            $traverser->descendants( $descendant, $this->descendantDepth );
166        }
167
168        return $formatter->getOutput();
169    }
170}