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