mTemplateName = str_replace( '_', ' ', $name ); $tif->mFields = array(); if ( is_null( $formFields ) ) { $template = PFTemplate::newFromName( $tif->mTemplateName ); $fields = $template->getTemplateFields(); foreach ( $fields as $field ) { $tif->mFields[] = PFFormField::create( $field ); } } else { $tif->mFields = $formFields; } $tif->mLabel = $label; $tif->mAllowMultiple = $allowMultiple; $tif->mMaxAllowed = $maxAllowed; return $tif; } public static function newFromFormTag( $tag_components ) { global $wgParser; $template_name = str_replace( '_', ' ', trim( $wgParser->recursiveTagParse( $tag_components[1] ) ) ); $tif = new PFTemplateInForm(); $tif->mTemplateName = str_replace( '_', ' ', $template_name ); $tif->mAddButtonText = wfMessage( 'pf_formedit_addanother' )->text(); // Cycle through the other components. for ( $i = 2; $i < count( $tag_components ); $i++ ) { $component = $tag_components[$i]; if ( $component == 'multiple' ) { $tif->mAllowMultiple = true; } elseif ( $component == 'strict' ) { $tif->mStrictParsing = true; } $sub_components = array_map( 'trim', explode( '=', $component, 2 ) ); if ( count( $sub_components ) == 2 ) { if ( $sub_components[0] == 'label' ) { $tif->mLabel = $wgParser->recursiveTagParse( $sub_components[1] ); } elseif ( $sub_components[0] == 'minimum instances' ) { $tif->mMinAllowed = $sub_components[1]; } elseif ( $sub_components[0] == 'maximum instances' ) { $tif->mMaxAllowed = $sub_components[1]; } elseif ( $sub_components[0] == 'add button text' ) { $tif->mAddButtonText = $wgParser->recursiveTagParse( $sub_components[1] ); } elseif ( $sub_components[0] == 'embed in field' ) { // Placeholder on form template level. Assume that the template form def // will have a multiple+placeholder parameters, and get the placeholder value. // We expect something like TemplateName[fieldName], and convert it to the // TemplateName___fieldName form used internally. preg_match( '/\s*(.*)\[(.*)\]\s*/', $sub_components[1], $matches ); if ( count( $matches ) > 2 ) { $tif->mEmbedInTemplate = $matches[1]; $tif->mEmbedInField = $matches[2]; $tif->mPlaceholder = PFFormPrinter::placeholderFormat( $tif->mEmbedInTemplate, $tif->mEmbedInField ); } } elseif ( $sub_components[0] == 'display' ) { $tif->mDisplay = $sub_components[1]; } elseif ( $sub_components[0] == 'height' ) { $tif->mHeight = $sub_components[1]; } } } return $tif; } function getTemplateName() { return $this->mTemplateName; } function getHeight() { return $this->mHeight; } function getFields() { return $this->mFields; } function getEmbedInTemplate() { return $this->mEmbedInTemplate; } function getEmbedInField() { return $this->mEmbedInField; } function getLabel() { return $this->mLabel; } function getAddButtonText() { return $this->mAddButtonText; } function getDisplay() { return $this->mDisplay; } function getPlaceholder() { return $this->mPlaceholder; } function allowsMultiple() { return $this->mAllowMultiple; } function strictParsing() { return $this->mStrictParsing; } function getMinInstancesAllowed() { return $this->mMinAllowed; } function getMaxInstancesAllowed() { return $this->mMaxAllowed; } function createMarkup() { $text = "{{{for template|" . $this->mTemplateName; if ( $this->mAllowMultiple ) { $text .= "|multiple"; } if ( $this->mLabel != '' ) { $text .= "|label=" . $this->mLabel; } $text .= "}}}\n"; // For now, HTML for templates differs for multiple-instance // templates; this may change if handling of form definitions // gets more sophisticated. if ( ! $this->mAllowMultiple ) { $text .= "{| class=\"formtable\"\n"; } foreach ( $this->mFields as $i => $field ) { $is_last_field = ( $i == count( $this->mFields ) - 1 ); $text .= $field->createMarkup( $this->mAllowMultiple, $is_last_field ); } if ( ! $this->mAllowMultiple ) { $text .= "|}\n"; } $text .= "{{{end template}}}\n"; return $text; } // The remaining functions here are intended for an instance of a // template in a specific form, and perhaps should be moved into // another class. function getFullTextInPage() { return $this->mFullTextInPage; } function pageCallsThisTemplate() { return $this->mPageCallsThisTemplate; } function hasValueFromPageForField( $field_name ) { return array_key_exists( $field_name, $this->mValuesFromPage ); } function getAndRemoveValueFromPageForField( $field_name ) { $value = $this->mValuesFromPage[$field_name]; unset( $this->mValuesFromPage[$field_name] ); return $value; } function getValuesFromPage() { return $this->mValuesFromPage; } function getInstanceNum() { return $this->mInstanceNum; } function getGridValues() { return $this->mGridValues; } function incrementInstanceNum() { $this->mInstanceNum++; } function allInstancesPrinted() { return $this->mAllInstancesPrinted; } function addGridValue( $field_name, $cur_value ) { if ( ! array_key_exists( $this->mInstanceNum, $this->mGridValues ) ) { $this->mGridValues[$this->mInstanceNum] = array(); } $this->mGridValues[$this->mInstanceNum][$field_name] = $cur_value; } function addField( $form_field ) { $this->mFields[] = $form_field; } function setFieldValuesFromSubmit() { global $wgRequest; $this->mValuesFromSubmit = null; $query_template_name = str_replace( ' ', '_', $this->mTemplateName ); // Also replace periods with underlines, since that's what // POST does to strings anyway. $query_template_name = str_replace( '.', '_', $query_template_name ); // ...and escape apostrophes. // (Or don't.) // $query_template_name = str_replace( "'", "\'", $query_template_name ); $allValuesFromSubmit = $wgRequest->getArray( $query_template_name ); if ( is_null( $allValuesFromSubmit ) ) { return; } // If this is a multiple-instance template, get the values for // this instance of the template. if ( $this->mAllowMultiple ) { $valuesFromSubmitKeys = array(); foreach ( array_keys( $allValuesFromSubmit ) as $key ) { if ( $key != 'num' ) { $valuesFromSubmitKeys[] = $key; } } $this->mNumInstancesFromSubmit = count( $valuesFromSubmitKeys ); if ( $this->mNumInstancesFromSubmit > $this->mInstanceNum ) { $instanceKey = $valuesFromSubmitKeys[$this->mInstanceNum]; $this->mValuesFromSubmit = $allValuesFromSubmit[$instanceKey]; } } else { $this->mValuesFromSubmit = $allValuesFromSubmit; } } function getValuesFromSubmit() { return $this->mValuesFromSubmit; } /** * Change "non-template pipes", i.e. pipes that do not separate * between template params but rather are contained within tag * functions, into another character, so that they don't get * handled and can be changed back into pipes later. * (This doesn't include pipes contained within curly bracket * parser functions - those are handled separately.) * * @param string $str * @return string */ static function escapeNonTemplatePipes( $str ) { $startAndEndTags = array( array( '' ), array( '' ), array( '' ), array( '' ) ); foreach ( $startAndEndTags as $tags ) { list( $startTag, $endTag ) = $tags; $pattern = "/($startTag.*)\|(.*$endTag)/mis"; while ( preg_match( $pattern, $str, $matches ) ) { // Special handling, to avoid escaping pipes // within a string that looks like: // startTag ... endTag | startTag ... endTag if ( strpos( $matches[1], $endTag ) && strpos( $matches[2], $startTag ) ) { $str = preg_replace( $pattern, "$1" . "\2" . "$2", $str ); } else { $str = preg_replace( $pattern, "$1" . "\1" . "$2", $str ); } } } // Change the "true" pipes back into pipes. $str = str_replace( "\2", '|', $str ); return $str; } /** * @param string $str * @return string */ static function unescapeNonTemplatePipes( $str ) { return str_replace( "\1", '|', $str ); } function setFieldValuesFromPage( $existing_page_content ) { global $wgPageFormsUseTranslatableTemplates; $existing_page_content = self::escapeNonTemplatePipes( $existing_page_content ); $matches = array(); $search_pattern = '/{{' . $this->mPregMatchTemplateStr . '\s*[\|}]/i'; $content_str = str_replace( '_', ' ', $existing_page_content ); preg_match( $search_pattern, $content_str, $matches, PREG_OFFSET_CAPTURE ); // case of templates called whith {{tntn}} template if ( ! array_key_exists( 0, $matches ) && $wgPageFormsUseTranslatableTemplates ) { $search_pattern = '/{{ {{tntn\|' . $this->mPregMatchTemplateStr . '\s*}}\s*[\|}]/i'; preg_match( $search_pattern, $content_str, $matches, PREG_OFFSET_CAPTURE ); } // is this check necessary? if ( array_key_exists( 0, $matches ) && array_key_exists( 1, $matches[0] ) ) { $start_char = $matches[0][1]; $fields_start_char = $start_char + strlen( $matches[0][0]) -1; // Skip ahead to the first real character. while ( in_array( $existing_page_content[$fields_start_char], array( ' ', '\n' ) ) ) { $fields_start_char++; } // Skip ahead to the first real character. // If the next character is a pipe, skip that too. if ( $existing_page_content[$fields_start_char] == '|' ) { $fields_start_char++; } $this->mValuesFromPage = array( '0' => '' ); // Cycle through template call, splitting it up by pipes ('|'), // except when that pipe is part of a piped link. $field = ""; $uncompleted_square_brackets = 0; $uncompleted_curly_brackets = 2; $template_ended = false; for ( $i = $fields_start_char; ! $template_ended && ( $i < strlen( $existing_page_content ) ); $i++ ) { $c = $existing_page_content[$i]; if ( $c == '[' ) { $uncompleted_square_brackets++; } elseif ( $c == ']' && $uncompleted_square_brackets > 0 ) { $uncompleted_square_brackets--; } elseif ( $c == '{' ) { $uncompleted_curly_brackets++; } elseif ( $c == '}' && $uncompleted_curly_brackets > 0 ) { $uncompleted_curly_brackets--; } // handle an end to a field and/or template declaration $template_ended = ( $uncompleted_curly_brackets == 0 && $uncompleted_square_brackets == 0 ); $field_ended = ( $c == '|' && $uncompleted_square_brackets == 0 && $uncompleted_curly_brackets <= 2 ); if ( $template_ended || $field_ended ) { // If this was the last character in the template, remove // the closing curly brackets. if ( $template_ended ) { $field = substr( $field, 0, - 1 ); } $field = self::unescapeNonTemplatePipes( $field ); // Either there's an equals sign near the beginning or not - // handling is similar in either way; if there's no equals // sign, the index of this field becomes the key. $sub_fields = explode( '=', $field, 2 ); if ( count( $sub_fields ) > 1 ) { $this->mValuesFromPage[trim( $sub_fields[0] )] = trim( $sub_fields[1] ); } else { $this->mValuesFromPage[] = trim( $sub_fields[0] ); } $field = ''; } else { $field .= $c; } } // If there are uncompleted opening brackets, the whole form will get messed up - // throw an exception. // (If there are too many *closing* brackets, some template stuff will end up in // the "free text" field - which is bad, but it's harder for the code to detect // the problem - though hopefully, easier for users.) if ( $uncompleted_curly_brackets > 0 || $uncompleted_square_brackets > 0 ) { throw new MWException( "PageFormsMismatchedBrackets" ); } $existing_page_content = self::unescapeNonTemplatePipes( $existing_page_content ); $this->mFullTextInPage = substr( $existing_page_content, $start_char, $i - $start_char ); } } /** * Set some vars based on the current contents of the page being * edited - or at least vars that only need to be set if there's * an existing page. * @param string $existing_page_content */ function setPageRelatedInfo( $existing_page_content ) { // Replace underlines with spaces in template name, to allow for // searching on either. $this->mSearchTemplateStr = str_replace( '_', ' ', $this->mTemplateName ); $this->mPregMatchTemplateStr = str_replace( array( '/', '(', ')', '^' ), array( '\/', '\(', '\)', '\^' ), $this->mSearchTemplateStr ); $directCall = preg_match( '/{{' . $this->mPregMatchTemplateStr . '\s*[\|}]/i', str_replace( '_', ' ', $existing_page_content ), $matches ); $translatableModelCall = preg_match( '/{{ {{tntn\|' . $this->mPregMatchTemplateStr . '\s*[\|}]/i', str_replace( '_', ' ', $existing_page_content ) ); $this->mPageCallsThisTemplate = $directCall || $translatableModelCall; } function checkIfAllInstancesPrinted( $form_submitted, $source_is_page ) { // Find additional instances of this template in the page // (if it's an existing page) or the query string (if it's a // new page). // If there's at least one, re-parse this section of the // definition form for the subsequent template instance; // if there's none, don't include fields at all. // @TODO - There has to be a more efficient way to handle // multiple instances of templates, one that doesn't involve // re-parsing the same tags, but I don't know what it is. // (Also add additional, blank instances if there's a minimum // number required in this form, and we haven't reached it yet.) if ( !$this->mAllowMultiple ) { return; } if ( $form_submitted && $this->mInstanceNum < $this->mNumInstancesFromSubmit ) { return; } if ( !$form_submitted && $this->mInstanceNum < $this->mMinAllowed ) { return; } if ( !$form_submitted && $source_is_page && $this->mPageCallsThisTemplate ) { return; } if ( !$form_submitted && !$source_is_page && $this->mValuesFromSubmit != null ) { return; } $this->mAllInstancesPrinted = true; } }