Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 137
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Hooks
0.00% covered (danger)
0.00%
0 / 137
0.00% covered (danger)
0.00%
0 / 6
2162
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 onParserFirstCallInit
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 onEditPage__showEditForm_initial
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 renderParserFunction
0.00% covered (danger)
0.00%
0 / 83
0.00% covered (danger)
0.00%
0 / 1
756
 saveProp
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
110
 peopleList
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3namespace MediaWiki\Extension\Genealogy;
4
5use EditPage;
6use Html;
7use MediaWiki\Hook\EditPage__showEditForm_initialHook;
8use MediaWiki\Hook\ParserFirstCallInitHook;
9use MediaWiki\Linker\LinkRenderer;
10use OutputPage;
11use Parser;
12use Title;
13use Wikimedia\ParamValidator\TypeDef\BooleanDef;
14use Wikimedia\Rdbms\ILoadBalancer;
15
16// phpcs:disable MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName
17class Hooks implements ParserFirstCallInitHook, EditPage__showEditForm_initialHook {
18
19    /** @var LinkRenderer */
20    private LinkRenderer $linkRenderer;
21
22    /** @var ILoadBalancer */
23    private ILoadBalancer $loadBalancer;
24
25    public function __construct( LinkRenderer $linkRenderer, ILoadBalancer $loadBalancer ) {
26        $this->linkRenderer = $linkRenderer;
27        $this->loadBalancer = $loadBalancer;
28    }
29
30    /**
31     * @inheritDoc
32     */
33    public function onParserFirstCallInit( $parser ) {
34        $parser->setFunctionHook( 'genealogy', [ $this, 'renderParserFunction' ] );
35        return true;
36    }
37
38    /**
39     * This method is called by the EditPage::showEditForm:initial hook and adds a list of the
40     * current page's Genealogy partners that *are not* a result of a {{#genealogy:partner|…}} call
41     * in the current page.
42     * @param EditPage $editPage The current page that's being edited.
43     * @param OutputPage $output The output.
44     * @return void
45     */
46    public function onEditPage__showEditForm_initial( $editPage, $output ) {
47        $person = new Person( $this->loadBalancer, $editPage->getTitle() );
48        $peopleList = [];
49        foreach ( $person->getPartners( true ) as $partner ) {
50            $peopleList[] = $this->linkRenderer->makeKnownLink( $partner->getTitle() );
51        }
52        if ( count( $peopleList ) > 0 ) {
53            $msg = $output->msg( 'genealogy-existing-partners', count( $peopleList ) );
54            $partnersMsg = $msg->escaped() . '&#160;' . implode( ', ', $peopleList );
55            $output->addHTML( Html::rawElement( 'p', [], $partnersMsg ) );
56        }
57    }
58
59    /**
60     * Render the output of the parser function.
61     * The input parameters are wikitext with templates expanded.
62     * The output should be wikitext too.
63     * @param Parser $parser The parser.
64     * @return string|mixed[] The wikitext with which to replace the parser function call.
65     */
66    public function renderParserFunction( Parser $parser ) {
67        $params = [];
68        $args = func_get_args();
69        // Remove $parser from the args.
70        array_shift( $args );
71        // Get param 1, the function type.
72        $type = array_shift( $args );
73        // Everything that remains is required to be named (i.e. we discard other unnamed args).
74        foreach ( $args as $arg ) {
75            $pair = explode( '=', $arg, 2 );
76            if ( count( $pair ) == 2 ) {
77                $name = trim( $pair[0] );
78                $value = trim( $pair[1] );
79                if ( in_array( $value, BooleanDef::$TRUEVALS, true ) ) {
80                    $value = true;
81                }
82                if ( in_array( $value, BooleanDef::$FALSEVALS, true ) ) {
83                    $value = false;
84                }
85                if ( $value !== '' ) {
86                    $params[$name] = $value;
87                }
88            } else {
89                $params[] = $arg;
90            }
91        }
92        $out = '';
93        $isHtml = false;
94        switch ( $type ) {
95            case 'person':
96                if ( isset( $params['birth date'] ) ) {
97                    $out .= $params['birth date'];
98                    $this->saveProp( $parser, 'birth date', $params['birth date'], false );
99                }
100                if ( isset( $params['death date'] ) ) {
101                    $out .= $params['death date'];
102                    $this->saveProp( $parser, 'death date', $params['death date'], false );
103                }
104                break;
105            case 'description':
106                if ( isset( $params[0] ) ) {
107                    $out = $params[0];
108                    $this->saveProp( $parser, 'description', $out, false );
109                }
110                break;
111            case 'parent':
112                $parentTitle = Title::newFromText( $params[0] );
113                if ( !$parentTitle instanceof Title ) {
114                    $invalidTitle = wfEscapeWikiText( $params[0] );
115                    $isHtml = true;
116                    $msg = wfMessage( 'genealogy-invalid-parent-title', $invalidTitle )->escaped();
117                    $out .= Html::rawElement( 'span', [ 'class' => 'error' ], $msg );
118                } else {
119                    $parent = new Person( $this->loadBalancer, $parentTitle );
120                    // Even though it's a list of one, output a parent link according to the same
121                    // system as the other relation types, so that it uses the same template.
122                    $out .= $this->peopleList( $parser, [ $parent ] );
123                    $this->saveProp( $parser, 'parent', $parentTitle );
124                }
125                break;
126            case 'siblings':
127                $person = new Person( $this->loadBalancer, $parser->getTitle() );
128                $excludeSelf = isset( $params['exclude_self'] ) && $params['exclude_self'];
129                $out .= $this->peopleList( $parser, $person->getSiblings( $excludeSelf ) );
130                break;
131            case 'partner':
132                $partnerTitle = Title::newFromText( $params[0] );
133                if ( !$partnerTitle instanceof Title ) {
134                    $invalidTitle = wfEscapeWikiText( $params[0] );
135                    $isHtml = true;
136                    $msg = wfMessage( 'genealogy-invalid-partner-title', $invalidTitle )->escaped();
137                    $out .= Html::rawElement( 'span', [ 'class' => 'error' ], $msg );
138                } else {
139                    $this->saveProp( $parser, 'partner', $partnerTitle );
140                }
141                break;
142            case 'partners':
143                $person = new Person( $this->loadBalancer, $parser->getTitle() );
144                $out .= $this->peopleList( $parser, $person->getPartners() );
145                break;
146            case 'children':
147                $person = new Person( $this->loadBalancer, $parser->getTitle() );
148                $out .= $this->peopleList( $parser, $person->getChildren() );
149                break;
150            case 'tree':
151                $tree = new Tree( $this->loadBalancer );
152                $delimiter = $params['delimiter'] ?? "\n";
153                if ( isset( $params['ancestors'] ) ) {
154                    $tree->addAncestors( explode( $delimiter, $params['ancestors'] ) );
155                }
156                if ( isset( $params['ancestor depth'] ) ) {
157                    $tree->setAncestorDepth( $params['ancestor depth'] );
158                }
159                if ( isset( $params['descendants'] ) ) {
160                    $tree->addDescendants( explode( $delimiter, $params['descendants'] ) );
161                }
162                if ( isset( $params['descendant depth'] ) ) {
163                    $tree->setDescendantDepth( $params['descendant depth'] );
164                }
165                if ( isset( $params['format'] ) ) {
166                    $tree->setFormat( $params['format'] );
167                }
168                $out = $tree->getWikitext( $parser );
169                break;
170            default:
171                $msg = wfMessage( 'genealogy-parser-function-not-found', [ $type ] )->text();
172                $out .= Html::element( 'span', [ 'class' => 'error' ], $msg );
173                break;
174        }
175        // Return format is documented in Parser::setFunctionHook().
176        return $isHtml ? [ 0 => $out, 'isHTML' => true ] : $out;
177    }
178
179    /**
180     * Save a page property.
181     * @todo Remove ParserOutput::getProperty and ParserOutput::setProperty fallbacks after dropping support for MW 1.37
182     * @param Parser $parser The parser object.
183     * @param string $prop The property name; it will be prefixed with 'genealogy '.
184     * @param string|Title $val The property value ('full text' will be used if this is a Title).
185     * @param bool $multi Whether this property can have multiple values (will be stored as
186     * multiple properties, with an integer appended to their name.
187     */
188    public function saveProp( Parser $parser, $prop, $val, $multi = true ) {
189        $output = $parser->getOutput();
190        $valString = ( $val instanceof Title ) ? $val->getFullText() : $val;
191        if ( $multi ) {
192            // Figure out what number we're up to for this property.
193            $propNum = 1;
194            $propVal = method_exists( $output, 'getPageProperty' )
195                ? $output->getPageProperty( "genealogy $prop $propNum" )
196                // @phan-suppress-next-line PhanUndeclaredMethod
197                : $output->getProperty( "genealogy $prop $propNum" );
198            while ( $propVal && $propVal !== $valString ) {
199                $propNum++;
200                $propVal = method_exists( $output, 'getPageProperty' )
201                    ? $output->getPageProperty( "genealogy $prop $propNum" )
202                    // @phan-suppress-next-line PhanUndeclaredMethod
203                    : $output->getProperty( "genealogy $prop $propNum" );
204            }
205            // Save the property.
206            if ( method_exists( $output, 'setPageProperty' ) ) {
207                $output->setPageProperty( "genealogy $prop $propNum", $valString );
208            } else {
209                // @phan-suppress-next-line PhanUndeclaredMethod
210                $output->setProperty( "genealogy $prop $propNum", $valString );
211            }
212
213        } else {
214            // A single-valued property.
215            if ( method_exists( $output, 'setPageProperty' ) ) {
216                $output->setPageProperty( "genealogy $prop", $valString );
217            } else {
218                // @phan-suppress-next-line PhanUndeclaredMethod
219                $output->setProperty( "genealogy $prop", $valString );
220            }
221        }
222        // For page-linking properties, add the referenced page as a dependency for this page.
223        // https://www.mediawiki.org/wiki/Manual:Tag_extensions#How_do_I_disable_caching_for_pages_using_my_extension.3F
224        if ( $val instanceof Title ) {
225            // Register the dependency in templatelinks table.
226            $output->addTemplate( $val, $val->getArticleID(), $val->getLatestRevID() );
227        }
228    }
229
230    /**
231     * Get a wikitext list of siblings, partners, or children.
232     * @param Parser $parser The parser to use to render each line.
233     * @param Person[] $people The people to list.
234     * @return string Wikitext list of people.
235     */
236    public function peopleList( Parser $parser, $people ) {
237        $templateName = wfMessage( 'genealogy-person-list-item' )->text();
238        $out = '';
239        $index = 1;
240        $peopleCount = count( $people );
241        $templateTitle = Title::newFromText( $templateName );
242        $templateExists = $templateTitle instanceof Title && $templateTitle->exists();
243        foreach ( $people as $person ) {
244            if ( $templateExists ) {
245                $template = '{{' . $templateName
246                    . '|title=' . $person->getTitle()->getFullText()
247                    . '|pagename=' . $person->getTitle()->getText()
248                    . '|link=' . $person->getWikiLink()
249                    . '|description=' . $person->getDescription()
250                    . '|index=' . $index
251                    . '|count=' . $peopleCount
252                    . '}}';
253                $out .= $parser->recursivePreprocess( $template );
254                $index++;
255            } else {
256                $out .= "* " . $person->getWikiLink() . "\n";
257            }
258        }
259        return $out;
260    }
261
262}