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