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