Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
94.87% |
111 / 117 |
|
72.22% |
13 / 18 |
CRAP | |
0.00% |
0 / 1 |
Person | |
94.87% |
111 / 117 |
|
72.22% |
13 / 18 |
44.26 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
__toString | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getTitle | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
getTitleHtml | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
getTitles | |
90.00% |
9 / 10 |
|
0.00% |
0 / 1 |
3.01 | |||
getWikiLink | |
90.91% |
20 / 22 |
|
0.00% |
0 / 1 |
10.08 | |||
hasDates | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
getBirthDate | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDeathDate | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDateYear | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getDescription | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getParents | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getSiblings | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
5 | |||
getPartners | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
getChildren | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getPropInbound | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
2 | |||
getPropSingle | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
getPropMulti | |
95.45% |
21 / 22 |
|
0.00% |
0 / 1 |
4 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\Genealogy; |
4 | |
5 | use MediaWiki\MediaWikiServices; |
6 | use Title; |
7 | use Wikimedia\Rdbms\ILoadBalancer; |
8 | |
9 | class Person { |
10 | |
11 | /** @var ILoadBalancer */ |
12 | private ILoadBalancer $loadBalancer; |
13 | |
14 | /** @var Title */ |
15 | private $title; |
16 | |
17 | /** @var Person[] */ |
18 | private $siblings; |
19 | |
20 | /** @var Person[] */ |
21 | private $children; |
22 | |
23 | /** |
24 | * Create a new Person based on a page in the wiki. |
25 | * @param ILoadBalancer $loadBalancer |
26 | * @param Title $title The page title. |
27 | */ |
28 | public function __construct( ILoadBalancer $loadBalancer, Title $title ) { |
29 | $this->loadBalancer = $loadBalancer; |
30 | $this->title = $title; |
31 | } |
32 | |
33 | /** |
34 | * Get some basic info about this person. |
35 | * @todo Add dates. |
36 | * @return string |
37 | */ |
38 | public function __toString() { |
39 | return $this->getTitle()->getPrefixedText(); |
40 | } |
41 | |
42 | /** |
43 | * Get this person's wiki title, following redirects (to any depth) when present. |
44 | * |
45 | * @return Title |
46 | */ |
47 | public function getTitle() { |
48 | $wikiPageFactory = MediaWikiServices::getInstance()->getWikiPageFactory(); |
49 | $page = $wikiPageFactory->newFromTitle( $this->title ); |
50 | while ( $page->isRedirect() ) { |
51 | $page = $wikiPageFactory->newFromTitle( $page->getRedirectTarget() ); |
52 | } |
53 | return $page->getTitle(); |
54 | } |
55 | |
56 | /** |
57 | * Get the person's page title, using displaytitle if it's available. |
58 | * @return string HTML of the title. |
59 | */ |
60 | public function getTitleHtml(): string { |
61 | return $this->getPropSingle( 'displaytitle', false ) ?: $this->getTitle()->getText(); |
62 | } |
63 | |
64 | /** |
65 | * Get all Titles that refer to this Person (i.e. all redirects both inward and outward, and |
66 | * the actual Title). |
67 | * @return Title[] An array of the Titles, some of which might not actually exist, keyed by the |
68 | * prefixed DB key. |
69 | */ |
70 | public function getTitles() { |
71 | $titles = [ $this->title->getPrefixedDBkey() => $this->title ]; |
72 | // Find all the outgoing redirects that leave from here. |
73 | $wikiPageFactory = MediaWikiServices::getInstance()->getWikiPageFactory(); |
74 | $page = $wikiPageFactory->newFromTitle( $this->title ); |
75 | while ( $page->isRedirect() ) { |
76 | $title = $page->getRedirectTarget(); |
77 | $titles[$title->getPrefixedDBkey()] = $title; |
78 | $page = $wikiPageFactory->newFromTitle( $title ); |
79 | } |
80 | // Find all the incoming redirects that come here. |
81 | foreach ( $this->title->getRedirectsHere() as $inwardRedirect ) { |
82 | $titles[$inwardRedirect->getPrefixedDBkey()] = $inwardRedirect; |
83 | } |
84 | return $titles; |
85 | } |
86 | |
87 | /** |
88 | * Get wikitext for a link to this Person. Non-existent people will get an 'external'-style |
89 | * link that has the 'preload' parameter added. The dates of birth and death are appended, |
90 | * outside the link. |
91 | * @return string The wikitext. |
92 | */ |
93 | public function getWikiLink(): string { |
94 | $birthYear = $this->getDateYear( $this->getBirthDate() ); |
95 | $deathYear = $this->getDateYear( $this->getDeathDate() ); |
96 | $dateString = ''; |
97 | if ( $birthYear !== '' && $deathYear !== '' ) { |
98 | $dateString = wfMessage( 'genealogy-born-and-died', $birthYear, $deathYear )->text(); |
99 | } elseif ( $birthYear !== '' && $deathYear === '' ) { |
100 | $dateString = wfMessage( 'genealogy-born', $birthYear )->text(); |
101 | } elseif ( $birthYear === '' && $deathYear !== '' ) { |
102 | $dateString = wfMessage( 'genealogy-died', $deathYear )->text(); |
103 | } |
104 | $title = $this->getTitle(); |
105 | if ( $title->exists() ) { |
106 | // If it exists, create a link (piping if not in the main namespace). |
107 | $link = $title->inNamespace( NS_MAIN ) |
108 | ? "[[" . $title->getFullText() . "]]" |
109 | : "[[" . $title->getFullText() . "|" . $title->getText() . "]]"; |
110 | } else { |
111 | // If it doesn't exist, create an edit link with a preload parameter. |
112 | $query = [ |
113 | 'action' => 'edit', |
114 | 'preload' => wfMessage( 'genealogy-person-preload' )->text(), |
115 | ]; |
116 | $url = $title->getFullURL( $query ); |
117 | $link = '[' . $url . ' ' . $title->getText() . ']'; |
118 | } |
119 | $date = ( $this->hasDates() ) ? " $dateString" : ""; |
120 | return $link . $date; |
121 | } |
122 | |
123 | /** |
124 | * Whether or not this person has a birth or death date. |
125 | * @return bool |
126 | */ |
127 | public function hasDates() { |
128 | return $this->getBirthDate() !== false || $this->getDeathDate() !== false; |
129 | } |
130 | |
131 | /** |
132 | * Get the birth date of this person. |
133 | * @return string |
134 | */ |
135 | public function getBirthDate() { |
136 | return $this->getPropSingle( 'birth date' ); |
137 | } |
138 | |
139 | /** |
140 | * Get the death date of this person. |
141 | * @return string |
142 | */ |
143 | public function getDeathDate() { |
144 | return $this->getPropSingle( 'death date' ); |
145 | } |
146 | |
147 | /** |
148 | * Get a year out of a date if possible. |
149 | * @param string $date The date to parse. |
150 | * @return string The year as a string, or the full date. |
151 | */ |
152 | public function getDateYear( string $date ): string { |
153 | preg_match( '/(\d{3,4})/', $date, $matches ); |
154 | if ( isset( $matches[1] ) ) { |
155 | return $matches[1]; |
156 | } |
157 | return $date; |
158 | } |
159 | |
160 | /** |
161 | * Get this person's description. |
162 | * @return string |
163 | */ |
164 | public function getDescription() { |
165 | $desc = $this->getPropSingle( 'description' ); |
166 | if ( !$desc ) { |
167 | $desc = ''; |
168 | } |
169 | return $desc; |
170 | } |
171 | |
172 | /** |
173 | * Get all parents. |
174 | * @return Person[] An array of parents, possibly empty. |
175 | */ |
176 | public function getParents() { |
177 | $parents = $this->getPropMulti( 'parent' ); |
178 | ksort( $parents ); |
179 | return $parents; |
180 | } |
181 | |
182 | /** |
183 | * Get all siblings. |
184 | * |
185 | * @param bool|null $excludeSelf Whether to excluding this person from the list. |
186 | * @return Person[] An array of siblings, possibly empty. |
187 | */ |
188 | public function getSiblings( ?bool $excludeSelf = false ) { |
189 | if ( !is_array( $this->siblings ) ) { |
190 | $this->siblings = []; |
191 | $descriptions = []; |
192 | foreach ( $this->getParents() as $parent ) { |
193 | foreach ( $parent->getChildren() as $child ) { |
194 | $key = $child->getTitle()->getPrefixedDBkey(); |
195 | $descriptions[ $key ] = $child->getDescription(); |
196 | $this->siblings[ $key ] = $child; |
197 | } |
198 | } |
199 | array_multisort( $descriptions, $this->siblings ); |
200 | } |
201 | if ( $excludeSelf ) { |
202 | unset( $this->siblings[ $this->getTitle()->getPrefixedDBkey() ] ); |
203 | } |
204 | return $this->siblings; |
205 | } |
206 | |
207 | /** |
208 | * Get all partners (optionally excluding those that are defined within the current page). |
209 | * @param bool $onlyDefinedElsewhere Only return those partners that are *not* defined |
210 | * within this Person's page. |
211 | * @return Person[] An array of partners, possibly empty. Keyed by the partner's page DB key. |
212 | */ |
213 | public function getPartners( $onlyDefinedElsewhere = false ) { |
214 | $partners = $this->getPropInbound( 'partner' ); |
215 | if ( $onlyDefinedElsewhere === false ) { |
216 | $partners = array_merge( $partners, $this->getPropMulti( 'partner' ) ); |
217 | } |
218 | ksort( $partners ); |
219 | return $partners; |
220 | } |
221 | |
222 | /** |
223 | * Get all children. |
224 | * @return Person[] An array of children, possibly empty, keyed by the prefixed DB key. |
225 | */ |
226 | public function getChildren() { |
227 | $this->children = $this->getPropInbound( 'parent' ); |
228 | return $this->children; |
229 | } |
230 | |
231 | /** |
232 | * Find people with properties that are equal to one of this page's titles. |
233 | * @param string $type The property type. |
234 | * @return Person[] Keyed by the prefixed DB key. |
235 | */ |
236 | protected function getPropInbound( $type ) { |
237 | $dbr = $this->loadBalancer->getConnection( DB_REPLICA ); |
238 | $tables = [ 'pp' => 'page_props', 'p' => 'page' ]; |
239 | $columns = [ 'pp_value', 'page_title', 'page_namespace' ]; |
240 | |
241 | $where = [ |
242 | 'pp_value' => $this->getTitles(), |
243 | 'pp_propname' . $dbr->buildLike( 'genealogy ', $type . ' ', $dbr->anyString() ), |
244 | 'pp_page = page_id', |
245 | ]; |
246 | $results = $dbr->select( $tables, $columns, $where, __METHOD__, [], [ 'page' => [] ] ); |
247 | $out = []; |
248 | foreach ( $results as $res ) { |
249 | $title = Title::newFromText( $res->page_title, $res->page_namespace ); |
250 | $person = new Person( $this->loadBalancer, $title ); |
251 | $out[$person->getTitle()->getPrefixedDBkey()] = $person; |
252 | } |
253 | return $out; |
254 | } |
255 | |
256 | /** |
257 | * Get the value of a single-valued page property. |
258 | * @param string $prop The property name. |
259 | * @param bool $isGenealogy Whether the property name should be prefixed with 'genealogy '. |
260 | * @return string|bool The property value, or false if not found. |
261 | */ |
262 | public function getPropSingle( string $prop, bool $isGenealogy = true ) { |
263 | $dbr = $this->loadBalancer->getConnection( DB_REPLICA ); |
264 | $where = [ |
265 | 'pp_page' => $this->getTitle()->getArticleID(), |
266 | 'pp_propname' => $isGenealogy ? "genealogy $prop" : $prop, |
267 | ]; |
268 | return $dbr->selectField( 'page_props', 'pp_value', $where, __METHOD__ ); |
269 | } |
270 | |
271 | /** |
272 | * Get a multi-valued relationship property of this Person. |
273 | * @param string $type The property name ('genealogy ' will be prepended). |
274 | * @return Person[] The related people. |
275 | */ |
276 | protected function getPropMulti( $type ) { |
277 | $out = []; |
278 | $dbr = $this->loadBalancer->getConnection( DB_REPLICA ); |
279 | $articleIds = []; |
280 | foreach ( $this->getTitles() as $t ) { |
281 | $articleIds[] = $t->getArticleID(); |
282 | } |
283 | $results = $dbr->select( |
284 | // Table to use. |
285 | 'page_props', |
286 | // Field to select. |
287 | 'pp_value', |
288 | [ |
289 | // Where conditions. |
290 | 'pp_page' => $articleIds, |
291 | 'pp_propname' . $dbr->buildLike( 'genealogy ', $type . ' ', $dbr->anyString() ), |
292 | ], |
293 | __METHOD__, |
294 | [ 'ORDER BY' => 'pp_value' ] |
295 | ); |
296 | foreach ( $results as $result ) { |
297 | $title = Title::newFromText( $result->pp_value ); |
298 | if ( $title === null ) { |
299 | // Do nothing, if this isn't a valid title. |
300 | continue; |
301 | } |
302 | $person = new Person( $this->loadBalancer, $title ); |
303 | $out[$person->getTitle()->getPrefixedDBkey()] = $person; |
304 | } |
305 | return $out; |
306 | } |
307 | } |