Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 390
0.00% covered (danger)
0.00%
0 / 22
CRAP
0.00% covered (danger)
0.00%
0 / 1
PFTemplate
0.00% covered (danger)
0.00%
0 / 390
0.00% covered (danger)
0.00%
0 / 22
25440
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
 newFromName
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 loadTemplateParams
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 getTemplateParams
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 loadTemplateFields
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 loadTemplateFieldsSMWAndOther
0.00% covered (danger)
0.00%
0 / 55
0.00% covered (danger)
0.00%
0 / 1
702
 loadPropertySettingInTemplate
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 loadTemplateFieldsCargo
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 1
462
 getCargoTableAndSchema
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 getTemplateFields
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFieldNamed
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 setConnectingProperty
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setCategoryName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setCargoTable
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setFullWikiTextStatus
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setAggregatingInfo
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setFormat
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 createCargoDeclareCall
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
56
 createCargoStoreCall
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 createText
0.00% covered (danger)
0.00%
0 / 164
0.00% covered (danger)
0.00%
0 / 1
4970
 createTextForField
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
30
 printCategoryTag
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * Defines a class, PFTemplate, that represents a MediaWiki "infobox"
4 * template that holds structured data, which may or may not be
5 * additionally stored by Cargo and/or Semantic MediaWiki.
6 *
7 * @author Yaron Koren
8 * @file
9 * @ingroup PF
10 */
11
12use MediaWiki\MediaWikiServices;
13use MediaWiki\Title\Title;
14
15class PFTemplate {
16    private $mTemplateName;
17    private $mTemplateText;
18    private $mTemplateFields;
19    private $mTemplateParams;
20    private $mConnectingProperty;
21    private $mCategoryName;
22    private $mCargoTable;
23    private $mAggregatingProperty;
24    private $mAggregationLabel;
25    private $mTemplateFormat;
26    private $mFieldStart;
27    private $mFieldEnd;
28    private $mTemplateStart;
29    private $mTemplateEnd;
30    private $mFullWikiText;
31
32    public function __construct( $templateName, $templateFields ) {
33        $this->mTemplateName = $templateName;
34        $this->mTemplateFields = $templateFields;
35    }
36
37    public static function newFromName( $templateName ) {
38        $template = new PFTemplate( $templateName, [] );
39        $template->loadTemplateParams();
40        $template->loadTemplateFields();
41        return $template;
42    }
43
44    /**
45     * Get (and store in memory) the values from this template's
46     * #template_params call, if it exists.
47     */
48    public function loadTemplateParams() {
49        $embeddedTemplate = null;
50        $templateTitle = Title::makeTitleSafe( NS_TEMPLATE, $this->mTemplateName );
51        if ( $templateTitle === null ) {
52            return;
53        }
54        $properties = MediaWikiServices::getInstance()->getPageProps()->getProperties(
55            [ $templateTitle ], [ 'PageFormsTemplateParams' ]
56        );
57        if ( count( $properties ) == 0 ) {
58            return;
59        }
60
61        $paramsForPage = reset( $properties );
62        $paramsForProperty = reset( $paramsForPage );
63        $this->mTemplateParams = unserialize( $paramsForProperty );
64    }
65
66    public function getTemplateParams() {
67        return $this->mTemplateParams;
68    }
69
70    /**
71     * @todo - fix so that this function only gets called once per
72     * template; right now it seems to get called once per field. (!)
73     */
74    function loadTemplateFields() {
75        $templateTitle = Title::makeTitleSafe( NS_TEMPLATE, $this->mTemplateName );
76        if ( !isset( $templateTitle ) ) {
77            return;
78        }
79
80        $templateText = PFUtils::getPageText( $templateTitle ) ?? '';
81        // Ignore 'noinclude' sections and 'includeonly' tags.
82        $templateText = StringUtils::delimiterReplace( '<noinclude>', '</noinclude>', '', $templateText );
83        $this->mTemplateText = strtr( $templateText, [ '<includeonly>' => '', '</includeonly>' => '' ] );
84
85        // The Cargo-based function is more specific; it only gets
86        // data structure information from the template schema. If
87        // there's no Cargo schema for this template, we call
88        // loadTemplateFieldsSMWAndOther(), which doesn't require the
89        // presence of SMW and can get non-SMW information as well.
90        if ( defined( 'CARGO_VERSION' ) ) {
91            $this->loadTemplateFieldsCargo( $templateTitle );
92            if ( count( $this->mTemplateFields ) > 0 ) {
93                return;
94            }
95        }
96        $this->loadTemplateFieldsSMWAndOther();
97    }
98
99    /**
100     * Get the fields of the template, along with the semantic property
101     * attached to each one (if any), by parsing the text of the template.
102     */
103    function loadTemplateFieldsSMWAndOther() {
104        $templateFields = [];
105        $fieldNamesArray = [];
106
107        // The way this works is that fields are found and then stored
108        // in an array based on their location in the template text, so
109        // that they can be returned in the order in which they appear
110        // in the template, not the order in which they were found.
111        // Some fields can be found more than once (especially if
112        // they're part of an "#if" statement), so they're only
113        // recorded the first time they're found.
114
115        // Replace all calls to #set within #arraymap with standard
116        // SMW tags. This is done so that they will later get
117        // parsed correctly.
118        // This is "cheating", since it modifies the template text
119        // (the rest of the function doesn't do that), but trying to
120        // get the #arraymap check regexp to find both kinds of SMW
121        // property tags seemed too hard to do.
122        $this->mTemplateText = preg_replace( '/#arraymap.*{{\s*#set:\s*([^=]*)=([^}]*)}}/', '[[$1:$2]]', $this->mTemplateText );
123
124        // Look for "arraymap" parser function calls that map a
125        // property onto a list.
126        $ret = preg_match_all( '/{{#arraymap:{{{([^|}]*:?[^|}]*)[^\[]*\[\[([^:]*:?[^:]*)::/mis', $this->mTemplateText, $matches );
127        if ( $ret ) {
128            foreach ( $matches[1] as $i => $field_name ) {
129                if ( !in_array( $field_name, $fieldNamesArray ) ) {
130                    $propertyName = $matches[2][$i];
131                    $this->loadPropertySettingInTemplate( $field_name, $propertyName, true );
132                    $fieldNamesArray[] = $field_name;
133                }
134            }
135        } elseif ( $ret === false ) {
136            // There was an error in the preg_match_all()
137            // call - let the user know about it.
138            if ( preg_last_error() == PREG_BACKTRACK_LIMIT_ERROR ) {
139                print 'Page Forms error: backtrace limit exceeded during parsing! Please increase the value of <a href="http://www.php.net/manual/en/pcre.configuration.php#ini.pcre.backtrack-limit">pcre.backtrack_limit</a> in php.ini or LocalSettings.php.';
140            }
141        }
142
143        // Look for normal property calls.
144        if ( preg_match_all( '/\[\[([^:|\[\]]*:*?[^:|\[\]]*)::{{{([^\]\|}]*).*?\]\]/mis', $this->mTemplateText, $matches ) ) {
145            foreach ( $matches[1] as $i => $propertyName ) {
146                $field_name = trim( $matches[2][$i] );
147                if ( !in_array( $field_name, $fieldNamesArray ) ) {
148                    $propertyName = trim( $propertyName );
149                    $this->loadPropertySettingInTemplate( $field_name, $propertyName, false );
150                    $fieldNamesArray[] = $field_name;
151                }
152            }
153        }
154
155        // Then, get calls to #set, #set_internal and #subobject.
156        // (Thankfully, they all have similar syntax).
157        if ( preg_match_all( '/#(set|set_internal|subobject):(.*?}}})\s*}}/mis', $this->mTemplateText, $matches ) ) {
158            foreach ( $matches[2] as $match ) {
159                if ( preg_match_all( '/([^|{]*?)=\s*{{{([^|}]*)/mis', $match, $matches2 ) ) {
160                    foreach ( $matches2[1] as $i => $propertyName ) {
161                        $fieldName = trim( $matches2[2][$i] );
162                        if ( !in_array( $fieldName, $fieldNamesArray ) ) {
163                            $propertyName = trim( $propertyName );
164                            $this->loadPropertySettingInTemplate( $fieldName, $propertyName, false );
165                            $fieldNamesArray[] = $fieldName;
166                        }
167                    }
168                }
169            }
170        }
171
172        // Then, get calls to #declare. (This is really rather
173        // optional, since no one seems to use #declare.)
174        if ( preg_match_all( '/#declare:(.*?)}}/mis', $this->mTemplateText, $matches ) ) {
175            foreach ( $matches[1] as $match ) {
176                $setValues = explode( '|', $match );
177                foreach ( $setValues as $valuePair ) {
178                    $keyAndVal = explode( '=', $valuePair );
179                    if ( count( $keyAndVal ) == 2 ) {
180                        $propertyName = trim( $keyAndVal[0] );
181                        $fieldName = trim( $keyAndVal[1] );
182                        if ( !in_array( $fieldName, $fieldNamesArray ) ) {
183                            $this->loadPropertySettingInTemplate( $fieldName, $propertyName, false );
184                            $fieldNamesArray[] = $fieldName;
185                        }
186                    }
187                }
188            }
189        }
190
191        // Finally, get any non-semantic fields defined.
192        if ( preg_match_all( '/{{{([^|}]*)/mis', $this->mTemplateText, $matches ) ) {
193            foreach ( $matches[1] as $fieldName ) {
194                $fieldName = trim( $fieldName );
195                if ( !empty( $fieldName ) && ( !in_array( $fieldName, $fieldNamesArray ) ) ) {
196                    $cur_pos = stripos( $this->mTemplateText, $fieldName );
197                    $this->mTemplateFields[$cur_pos] = PFTemplateField::create( $fieldName, PFUtils::getContLang()->ucfirst( $fieldName ) );
198                    $fieldNamesArray[] = $fieldName;
199                }
200            }
201        }
202
203        // If #template_params was declared for this template, go
204        // through the declared fields, and, for any that were not
205        // already found by parsing the template, populate
206        // $mTemplateFields with it.
207        // @todo - it would be good to combine the #template_params
208        // data with any SMW data found, instead of just getting one
209        // or the other. In practice, though, it doesn't really matter.
210        if ( $this->mTemplateParams !== null ) {
211            foreach ( $this->mTemplateParams as $fieldName => $fieldParams ) {
212                if ( in_array( $fieldName, $fieldNamesArray ) ) {
213                    continue;
214                }
215                $templateField = PFTemplateField::newFromParams( $fieldName, $fieldParams );
216                $this->mTemplateFields[$fieldName] = $templateField;
217            }
218            return;
219        }
220
221        ksort( $this->mTemplateFields );
222    }
223
224    /**
225     * For a field name and its attached property name located in the
226     * template text, create an PFTemplateField object out of it, and
227     * add it to $this->mTemplateFields.
228     * @param string $fieldName
229     * @param string $propertyName
230     * @param bool $isList
231     */
232    function loadPropertySettingInTemplate( $fieldName, $propertyName, $isList ) {
233        $templateField = PFTemplateField::create(
234            $fieldName, PFUtils::getContLang()->ucfirst( $fieldName ), $propertyName,
235            $isList
236        );
237        $cur_pos = stripos( $this->mTemplateText, $fieldName . '|' );
238        $this->mTemplateFields[$cur_pos] = $templateField;
239    }
240
241    function loadTemplateFieldsCargo( $templateTitle ) {
242        $cargoFieldsOfTemplateParams = [];
243
244        // First, get the table name, and fields, declared for this
245        // template, if any.
246        [ $tableName, $tableSchema ] = $this->getCargoTableAndSchema( $templateTitle );
247        if ( $tableName == null ) {
248            $fieldDescriptions = [];
249        } else {
250            $fieldDescriptions = $tableSchema->mFieldDescriptions;
251        }
252
253        // If #template_params was declared for this template, our
254        // job is easy - we just go through the declared fields, get
255        // the Cargo data for each field if it exists, and populate
256        // $mTemplateFields with it.
257        if ( $this->mTemplateParams !== null ) {
258            foreach ( $this->mTemplateParams as $fieldName => $fieldParams ) {
259                $templateField = PFTemplateField::newFromParams( $fieldName, $fieldParams );
260                $cargoField = $templateField->getExpectedCargoField();
261                if ( array_key_exists( $cargoField, $fieldDescriptions ) ) {
262                    $fieldDescription = $fieldDescriptions[$cargoField];
263                    $templateField->setCargoFieldData( $tableName, $cargoField, $fieldDescription );
264                }
265                $this->mTemplateFields[$fieldName] = $templateField;
266            }
267            return;
268        }
269
270        // If there are no declared template params *or* Cargo fields,
271        // exit.
272        if ( count( $fieldDescriptions ) == 0 ) {
273            return;
274        }
275
276        // No #template_params call, so we have to do a more manual
277        // process.
278        // Match template params to Cargo table fields, by parsing
279        // call(s) to #cargo_store.
280        // Let's find every #cargo_store tag.
281        // Unfortunately, it doesn't seem possible to use a regexp
282        // search for this, because it's hard to know which set of
283        // double brackets represents the end of such a call. Instead,
284        // we'll do some manual parsing.
285        $cargoStoreLocations = [];
286        $curPos = 0;
287        while ( true ) {
288            $newPos = strpos( $this->mTemplateText, "#cargo_store:", $curPos );
289            if ( $newPos === false ) {
290                break;
291            }
292            $curPos = $newPos + 13;
293            $cargoStoreLocations[] = $curPos;
294        }
295
296        $cargoStoreCalls = [];
297        foreach ( $cargoStoreLocations as $locNum => $startPos ) {
298            $numUnclosedBrackets = 2;
299            if ( $locNum < count( $cargoStoreLocations ) - 1 ) {
300                $lastPos = $cargoStoreLocations[$locNum + 1];
301            } else {
302                $lastPos = strlen( $this->mTemplateText ) - 1;
303            }
304            $curCargoStoreCall = '';
305            $curPos = $startPos;
306            while ( $curPos <= $lastPos ) {
307                $curChar = $this->mTemplateText[$curPos];
308                $curCargoStoreCall .= $curChar;
309                if ( $curChar == '}' ) {
310                    $numUnclosedBrackets--;
311                } elseif ( $curChar == '{' ) {
312                    $numUnclosedBrackets++;
313                }
314                if ( $numUnclosedBrackets == 0 ) {
315                    break;
316                }
317                $curPos++;
318            }
319            $cargoStoreCalls[] = $curCargoStoreCall;
320        }
321
322        foreach ( $cargoStoreCalls as $cargoStoreCall ) {
323            if ( preg_match_all( '/([^|{]*?)=\s*{{{([^|}]*)/mis', $cargoStoreCall, $matches ) ) {
324                foreach ( $matches[1] as $i => $cargoFieldName ) {
325                    $templateParameter = trim( $matches[2][$i] );
326                    $cargoFieldsOfTemplateParams[$templateParameter] = $cargoFieldName;
327                }
328            }
329        }
330
331        // Now, combine the two sets of information into an array of
332        // PFTemplateFields objects.
333        // First, go through the #cargo_store parameters, add add them
334        // all to the array, matching them with Cargo field descriptions
335        // where possible.
336        foreach ( $cargoFieldsOfTemplateParams as $templateParameter => $cargoField ) {
337            $templateField = PFTemplateField::create( $templateParameter, $templateParameter );
338            if ( array_key_exists( $cargoField, $fieldDescriptions ) ) {
339                $fieldDescription = $fieldDescriptions[$cargoField];
340                $templateField->setCargoFieldData( $tableName, $cargoField, $fieldDescription );
341            }
342            $this->mTemplateFields[] = $templateField;
343        }
344
345        // Now, go through the Cargo field descriptions, and add
346        // whichever ones were not in #cargo_store (as of version 3.0,
347        // Cargo does not require template parameters to be passed in
348        // to #cargo_store).
349        foreach ( $fieldDescriptions as $cargoField => $fieldDescription ) {
350            $templateParameter = array_search( $cargoField, $cargoFieldsOfTemplateParams );
351            if ( $templateParameter !== false ) {
352                continue;
353            }
354            $templateParameter = str_replace( '_', ' ', $cargoField );
355            $templateField = PFTemplateField::create( $templateParameter, $templateParameter );
356            $templateField->setCargoFieldData( $tableName, $cargoField, $fieldDescription );
357            $this->mTemplateFields[] = $templateField;
358        }
359    }
360
361    function getCargoTableAndSchema( $templateTitle ) {
362        $templatePageID = $templateTitle->getArticleID();
363        $tableSchemaString = CargoUtils::getPageProp( $templatePageID, 'CargoFields' );
364        // See if there even is DB storage for this template - if not,
365        // exit.
366        if ( $tableSchemaString === null ) {
367            // There's no declared table - but see if there's an
368            // attached table.
369            [ $tableName, $isDeclared ] = CargoUtils::getTableNameForTemplate( $templateTitle );
370            if ( $tableName == null ) {
371                return [ null, null ];
372            }
373            $mainTemplatePageID = CargoUtils::getTemplateIDForDBTable( $tableName );
374            $tableSchemaString = CargoUtils::getPageProp( $mainTemplatePageID, 'CargoFields' );
375        } else {
376            $tableName = CargoUtils::getPageProp( $templatePageID, 'CargoTableName' );
377        }
378        $tableSchema = CargoTableSchema::newFromDBString( $tableSchemaString );
379        return [ $tableName, $tableSchema ];
380    }
381
382    public function getTemplateFields() {
383        return $this->mTemplateFields;
384    }
385
386    public function getFieldNamed( $fieldName ) {
387        foreach ( $this->mTemplateFields as $curField ) {
388            if ( $curField->getFieldName() == $fieldName ) {
389                return $curField;
390            }
391        }
392        return null;
393    }
394
395    public function setConnectingProperty( $connectingProperty ) {
396        $this->mConnectingProperty = $connectingProperty;
397    }
398
399    public function setCategoryName( $categoryName ) {
400        $this->mCategoryName = $categoryName;
401    }
402
403    public function setCargoTable( $cargoTable ) {
404        $this->mCargoTable = str_replace( ' ', '_', $cargoTable );
405    }
406
407    public function setFullWikiTextStatus( $status ) {
408        $this->mFullWikiText = $status;
409    }
410
411    public function setAggregatingInfo( $aggregatingProperty, $aggregationLabel ) {
412        $this->mAggregatingProperty = $aggregatingProperty;
413        $this->mAggregationLabel = $aggregationLabel;
414    }
415
416    public function setFormat( $templateFormat ) {
417        $this->mTemplateFormat = $templateFormat;
418    }
419
420    public function createCargoDeclareCall() {
421        $text = '{{#cargo_declare:';
422        $text .= '_table=' . $this->mCargoTable;
423        foreach ( $this->mTemplateFields as $i => $field ) {
424            if ( $field->getFieldType() == '' ) {
425                continue;
426            }
427
428            $text .= '|';
429            $text .= str_replace( ' ', '_', $field->getFieldName() ) . '=';
430            if ( $field->isList() ) {
431                $delimiter = $field->getDelimiter();
432                if ( $delimiter == '' ) {
433                    $delimiter = ',';
434                }
435                $text .= "List ($delimiter) of ";
436            }
437            $text .= $field->getFieldType();
438            if ( $field->getHierarchyStructure() ) {
439                $hierarchyStructureString = $field->getHierarchyStructure();
440                $text .= " (hierarchy;allowed values=$hierarchyStructureString)";
441            } elseif ( count( $field->getPossibleValues() ) > 0 ) {
442                $allowedValuesString = implode( ',', $field->getPossibleValues() );
443                $text .= " (allowed values=$allowedValuesString)";
444            }
445        }
446        $text .= '}}';
447        return $text;
448    }
449
450    public function createCargoStoreCall() {
451        $text = '{{#cargo_store:';
452        $text .= '_table=' . $this->mCargoTable;
453        if ( defined( 'CargoStore::PARAMS_OPTIONAL' ) ) {
454            // Cargo 3.0+
455            $text .= '}}';
456            return $text;
457        }
458
459        foreach ( $this->mTemplateFields as $i => $field ) {
460            $text .= '|' .
461                str_replace( ' ', '_', $field->getFieldName() ) .
462                '={{{' . $field->getFieldName() . '|}}}';
463        }
464        $text .= ' }}';
465        return $text;
466    }
467
468    /**
469     * Creates the text of a template, when called from
470     * Special:CreateTemplate, Special:CreateClass or the Page Schemas
471     * extension.
472     * @return string
473     */
474    public function createText() {
475        // Avoid PHP 7.1 warning from passing $this by reference
476        $template = $this;
477        MediaWikiServices::getInstance()->getHookContainer()->run( 'PageForms::CreateTemplateText', [ &$template ] );
478        // Check whether the user needs the full wikitext instead of #template_display
479        if ( $this->mFullWikiText ) {
480            $templateHeader = wfMessage( 'pf_template_docu', $this->mTemplateName )->inContentLanguage()->text();
481            $text = <<<END
482<noinclude>
483$templateHeader
484<pre>
485
486END;
487            $text .= '{{' . $this->mTemplateName;
488            if ( count( $this->mTemplateFields ) > 0 ) {
489                $text .= "\n";
490            }
491            foreach ( $this->mTemplateFields as $field ) {
492                if ( $field->getFieldName() == '' ) {
493                    continue;
494                }
495                $text .= "|" . $field->getFieldName() . "=\n";
496            }
497            if ( defined( 'CARGO_VERSION' ) && !defined( 'SMW_VERSION' ) && $this->mCargoTable != '' ) {
498                $cargoInUse = true;
499                $cargoDeclareCall = $this->createCargoDeclareCall() . "\n";
500                $cargoStoreCall = $this->createCargoStoreCall();
501            } else {
502                $cargoInUse = false;
503                $cargoDeclareCall = '';
504                $cargoStoreCall = '';
505            }
506
507            $templateFooter = wfMessage( 'pf_template_docufooter' )->inContentLanguage()->text();
508            $text .= <<<END
509}}
510</pre>
511$templateFooter
512$cargoDeclareCall</noinclude><includeonly>$cargoStoreCall
513END;
514        } else {
515            $text = <<<END
516<noinclude>
517{{#template_params:
518END;
519            foreach ( $this->mTemplateFields as $i => $field ) {
520                if ( $field->getFieldName() == '' ) {
521                    continue;
522                }
523                if ( $i > 0 ) {
524                    $text .= "|";
525                }
526                $text .= $field->toWikitext();
527            }
528            if ( defined( 'CARGO_VERSION' ) && !defined( 'SMW_VERSION' ) && $this->mCargoTable != '' ) {
529                $cargoInUse = true;
530                $cargoDeclareCall = $this->createCargoDeclareCall() . "\n";
531                $cargoStoreCall = $this->createCargoStoreCall();
532            } else {
533                $cargoInUse = false;
534                $cargoDeclareCall = '';
535                $cargoStoreCall = '';
536            }
537
538            $text .= <<<END
539}}
540$cargoDeclareCall</noinclude><includeonly>$cargoStoreCall
541END;
542            if ( !defined( 'SMW_VERSION' ) ) {
543                $text .= "\n{{#template_display:";
544                if ( $this->mTemplateFormat != null ) {
545                    $text .= "_format=" . $this->mTemplateFormat;
546                }
547                $text .= "}}";
548                $text .= $this->printCategoryTag();
549                $text .= "</includeonly>";
550                return $text;
551            }
552        }
553
554        // Before text
555        $text .= $this->mTemplateStart;
556
557        // $internalObjText can be either a call to #subobject, or null.
558        $internalObjText = null;
559        if ( $this->mConnectingProperty ) {
560            $internalObjText = '{{#subobject:-|' . $this->mConnectingProperty . '={{PAGENAME}}';
561        }
562        $setText = '';
563
564        // Topmost part of table depends on format.
565        if ( !$this->mTemplateFormat ) {
566            $this->mTemplateFormat = 'table';
567        }
568        if ( $this->mTemplateFormat == 'table' ) {
569            $tableText = '{| class="wikitable"' . "\n";
570        } elseif ( $this->mTemplateFormat == 'infobox' ) {
571            // A CSS style can't be used, unfortunately, since most
572            // MediaWiki setups don't have an 'infobox' or
573            // comparable CSS class.
574            $tableText = <<<END
575{| style="width: 30em; font-size: 90%; border: 1px solid #aaaaaa; background-color: #f9f9f9; color: black; margin-bottom: 0.5em; margin-left: 1em; padding: 0.2em; float: right; clear: right; text-align:left;"
576! style="text-align: center; background-color:#ccccff;" colspan="2" |<span style="font-size: larger;">{{PAGENAME}}</span>
577|-
578
579END;
580        } else {
581            $tableText = '';
582        }
583
584        foreach ( $this->mTemplateFields as $i => $field ) {
585            if ( $field->getFieldName() == '' ) {
586                continue;
587            }
588
589            $fieldParam = '{{{' . $field->getFieldName() . '|}}}';
590            if ( $field->getNamespace() === null ) {
591                $fieldString = $fieldParam;
592            } else {
593                $fieldString = $field->getNamespace() . ':' . $fieldParam;
594            }
595            $separator = '';
596
597            $fieldLabel = $field->getLabel();
598            if ( $fieldLabel == '' ) {
599                $fieldLabel = $field->getFieldName();
600            }
601            $fieldDisplay = $field->getDisplay();
602            $fieldProperty = $field->getSemanticProperty();
603            $fieldIsList = $field->isList();
604
605            // Header/field label column
606            if ( $fieldDisplay === null ) {
607                if ( $this->mTemplateFormat == 'table' || $this->mTemplateFormat == 'infobox' ) {
608                    if ( $i > 0 ) {
609                        $tableText .= "|-\n";
610                    }
611                    $tableText .= '! ' . $fieldLabel . "\n";
612                } elseif ( $this->mTemplateFormat == 'plain' ) {
613                    $tableText .= "\n'''" . $fieldLabel . ":''' ";
614                } elseif ( $this->mTemplateFormat == 'sections' ) {
615                    $tableText .= "\n==" . $fieldLabel . "==\n";
616                }
617            } elseif ( $fieldDisplay == 'nonempty' ) {
618                if ( $this->mTemplateFormat == 'plain' || $this->mTemplateFormat == 'sections' ) {
619                    $tableText .= "\n";
620                }
621                $tableText .= '{{#if:' . $fieldParam . '|';
622                if ( $this->mTemplateFormat == 'table' || $this->mTemplateFormat == 'infobox' ) {
623                    if ( $i > 0 ) {
624                        $tableText .= "\n{{!}}-\n";
625                    }
626                    $tableText .= '! ' . $fieldLabel . "\n";
627                    $separator = '{{!}}';
628                } elseif ( $this->mTemplateFormat == 'plain' ) {
629                    $tableText .= "'''" . $fieldLabel . ":''' ";
630                    $separator = '';
631                } elseif ( $this->mTemplateFormat == 'sections' ) {
632                    $tableText .= '==' . $fieldLabel . "==\n";
633                    $separator = '';
634                }
635            } else {
636                // If it's 'hidden', do nothing
637            }
638            // Value column
639            if ( $this->mTemplateFormat == 'table' || $this->mTemplateFormat == 'infobox' ) {
640                if ( $fieldDisplay == 'hidden' ) {
641                } elseif ( $fieldDisplay == 'nonempty' ) {
642                    // $tableText .= "{{!}} ";
643                } else {
644                    $tableText .= "| ";
645                }
646            }
647
648            // If we're using Cargo, fields can simply be displayed
649            // normally - no need for any special tags - *unless*
650            // the field holds a list of Page values, in which case
651            // we need to apply #arraymap.
652            $isCargoListOfPages = $cargoInUse && $field->isList() && $field->getFieldType() == 'Page';
653            if ( !$fieldProperty && !$isCargoListOfPages ) {
654                if ( $separator != '' ) {
655                    $tableText .= "$separator ";
656                }
657                $tableText .= $this->createTextForField( $field );
658                if ( $fieldDisplay == 'nonempty' ) {
659                    $tableText .= " }}";
660                }
661                $tableText .= "\n";
662            } elseif ( $internalObjText !== null ) {
663                if ( $separator != '' ) {
664                    $tableText .= "$separator ";
665                }
666                $tableText .= $this->createTextForField( $field );
667                if ( $fieldDisplay == 'nonempty' ) {
668                    $tableText .= " }}";
669                }
670                $tableText .= "\n";
671                if ( $field->isList() ) {
672                    $internalObjText .= '|' . $fieldProperty . '=' . $fieldString . '|+sep=,';
673                } else {
674                    $internalObjText .= '|' . $fieldProperty . '=' . $fieldString;
675                }
676            } elseif ( $fieldDisplay == 'hidden' ) {
677                if ( $fieldIsList ) {
678                    $setText .= $fieldProperty . '#list=' . $fieldString . '|';
679                } else {
680                    $setText .= $fieldProperty . '=' . $fieldString . '|';
681                }
682            } elseif ( $fieldDisplay == 'nonempty' ) {
683                if ( $this->mTemplateFormat == 'table' || $this->mTemplateFormat == 'infobox' ) {
684                    $tableText .= '{{!}} ';
685                }
686                $tableText .= $this->createTextForField( $field ) . "\n}}\n";
687            } else {
688                $tableText .= $this->createTextForField( $field ) . "\n";
689            }
690        }
691
692        // Add an inline query to the output text, for
693        // aggregation, if a property was specified.
694        if ( $this->mAggregatingProperty !== null && $this->mAggregatingProperty !== '' ) {
695            if ( $this->mTemplateFormat == 'table' || $this->mTemplateFormat == 'infobox' ) {
696                if ( count( $this->mTemplateFields ) > 0 ) {
697                    $tableText .= "|-\n";
698                }
699                $tableText .= <<<END
700$this->mAggregationLabel
701|
702END;
703            } elseif ( $this->mTemplateFormat == 'plain' ) {
704                $tableText .= "\n'''" . $this->mAggregationLabel . ":''' ";
705            } elseif ( $this->mTemplateFormat == 'sections' ) {
706                $tableText .= "\n==" . $this->mAggregationLabel . "==\n";
707            }
708            $tableText .= "{{#ask:[[" . $this->mAggregatingProperty . "::{{SUBJECTPAGENAME}}]]|format=list}}\n";
709        }
710        if ( $this->mTemplateFormat == 'table' || $this->mTemplateFormat == 'infobox' ) {
711            $tableText .= "|}";
712        }
713        // Leave out newlines if there's an internal property
714        // set here (which would mean that there are meant to be
715        // multiple instances of this template.)
716        if ( $internalObjText === null ) {
717            if ( $this->mTemplateFormat == 'table' || $this->mTemplateFormat == 'infobox' ) {
718                $tableText .= "\n";
719            }
720        } else {
721            $internalObjText .= "}}";
722            $text .= $internalObjText;
723        }
724
725        // Add a call to #set, if necessary
726        if ( $setText !== '' ) {
727            $setText = '{{#set:' . $setText . "}}\n";
728            $text .= $setText;
729        }
730
731        $text .= $tableText;
732        $text .= $this->printCategoryTag();
733
734        // After text
735        $text .= $this->mTemplateEnd;
736
737        $text .= "</includeonly>\n";
738
739        return $text;
740    }
741
742    function createTextForField( $field ) {
743        $text = '';
744        $fieldStart = $this->mFieldStart;
745        $hookContainer = MediaWikiServices::getInstance()->getHookContainer();
746        $hookContainer->run( 'PageForms::TemplateFieldStart', [ $field, &$fieldStart ] );
747        if ( $fieldStart != '' ) {
748            $text .= "$fieldStart ";
749        }
750
751        $cargoInUse = defined( 'CARGO_VERSION' ) && !defined( 'SMW_VERSION' ) && $this->mCargoTable != '';
752        $text .= $field->createText( $cargoInUse );
753
754        $fieldEnd = $this->mFieldEnd;
755        $hookContainer->run( 'PageForms::TemplateFieldEnd', [ $field, &$fieldEnd ] );
756        if ( $fieldEnd != '' ) {
757            $text .= " $fieldEnd";
758        }
759
760        return $text;
761    }
762
763    function printCategoryTag() {
764        if ( ( $this->mCategoryName === '' || $this->mCategoryName === null ) ) {
765            return '';
766        }
767        $namespaceLabels = PFUtils::getContLang()->getNamespaces();
768        $categoryNamespace = $namespaceLabels[NS_CATEGORY];
769        return "\n[[$categoryNamespace:" . $this->mCategoryName . "]]\n";
770    }
771
772}