Difference between revisions of "Schema Evolution Benchmark"

From Schema Evolution
Jump to: navigation, search
Line 107: Line 107:
  
 
Please visit our [[Benchmark_Downloadables | '''Downloads section''']]
 
Please visit our [[Benchmark_Downloadables | '''Downloads section''']]
 
<source lang="php">
 
<?php
 
/**
 
* Handles the creation and running of a user-created form.
 
*
 
* @author Yaron Koren
 
* @author Nils Oppermann
 
* @author Jeffrey Stuckman
 
*/
 
 
class SFFormPrinter {
 
 
  function formHTML($form_def, $form_submitted, $source_is_page, $existing_page_content = null, $page_title = null) {
 
    global $wgRequest, $wgUser;
 
    global $gTabIndex; // used to represent the current tab index in the form
 
    global $gDisabledText; // disables all form elements if user doesn't have edit permission
 
 
    // initialize some variables
 
    $title = null;
 
    $gTabIndex = 0;
 
    if ( $wgUser->isAllowed('edit') ) {
 
      $gDisabledText = "";
 
      $form_text = "";
 
    } else {
 
      $gDisabledText = "disabled";
 
      // display a message to the user explaining why they can't edit the
 
      // page - borrowed heavily from EditPage.php
 
      if ( $wgUser->isAnon() ) {
 
        $skin = $wgUser->getSkin();
 
        $loginTitle = SpecialPage::getTitleFor( 'Userlogin' );
 
        $loginLink = $skin->makeKnownLinkObj( $loginTitle, wfMsgHtml( 'loginreqlink' ) );
 
        $form_text = wfMsgWikiHtml( 'whitelistedittext', $loginLink );
 
      } else {
 
        $form_text = wfMsg( 'protectedpagetext' );
 
      }
 
    }
 
    $javascript_text = "";
 
    $js_validation_calls = array();
 
 
    // Remove <noinclude> sections and <includeonly> tags from form definition
 
    $form_def = StringUtils::delimiterReplace('<noinclude>', '</noinclude>', '', $form_def);
 
    $form_def = strtr($form_def, array('<includeonly>' => '', '</includeonly>' => ''));
 
 
    // turn form definition file into an array of sections, one for each
 
    // template definition (plus the first section)
 
    $form_def_sections = array();
 
    $start_position = 0;
 
    $section_start = 0;
 
    $free_text_was_included = false;
 
    $all_values_for_template = array();
 
    while ($brackets_loc = strpos($form_def, "{{{", $start_position)) {
 
      $brackets_end_loc = strpos($form_def, "}}}", $brackets_loc);
 
      $bracketed_string = substr($form_def, $brackets_loc + 3, $brackets_end_loc - ($brackets_loc + 3));
 
      $tag_components = explode('|', $bracketed_string);
 
      $tag_title = trim($tag_components[0]);
 
      if ($tag_title == 'for template' || $tag_title == 'end template') {
 
        // create a section for everything up to here
 
        $section = substr($form_def, $section_start, $brackets_loc - $section_start);
 
        $form_def_sections[] = $section;
 
        $section_start = $brackets_loc;
 
      }
 
      $start_position = $brackets_loc + 1;
 
    } // end while
 
    $form_def_sections[] = trim(substr($form_def, $section_start));
 
 
    // cycle through form definition file (and possibly an existing article
 
    // as well), finding template and field declarations and replacing them
 
    // with form elements, either blank or pre-populated, as appropriate
 
    $all_fields = array();
 
    $data_text = "";
 
    $template_name = "";
 
    $allow_multiple = false;
 
    $instance_num = 0;
 
    $all_instances_printed = false;
 
    $strict_parsing = false;
 
$autocomplete_value_array = array();
 
$autocomplete_value_array['mappings'] = array();
 
    for ($section_num = 0; $section_num < count($form_def_sections); $section_num++) {
 
      $tif = new SFTemplateInForm();
 
      $start_position = 0;
 
      $template_text = "";
 
      // the append is there to ensure that the original array doesn't get
 
      // modified; is it necessary?
 
      $section = " " . $form_def_sections[$section_num];
 
 
      while ($brackets_loc = strpos($section, '{{{', $start_position)) {
 
        $brackets_end_loc = strpos($section, "}}}", $brackets_loc);
 
        $bracketed_string = substr($section, $brackets_loc + 3, $brackets_end_loc - ($brackets_loc + 3));
 
        $tag_components = explode('|', $bracketed_string);
 
        $tag_title = trim($tag_components[0]);
 
        if ($tag_title == 'for template') {
 
          $old_template_name = $template_name;
 
          $template_name = trim($tag_components[1]);
 
          $tif->template_name = $template_name;
 
          $query_template_name = str_replace(' ', '_', $template_name);
 
          // cycle through the other components
 
          for ($i = 2; $i < count($tag_components); $i++) {
 
            $component = $tag_components[$i];
 
            if ($component == 'multiple') $allow_multiple = true;
 
            if ($component == 'strict') $strict_parsing = true;
 
            $sub_components = explode('=', $component);
 
            if (count($sub_components) == 2) {
 
              if ($sub_components[0] == 'label') {
 
                $template_label = $sub_components[1];
 
              }
 
            }
 
          }
 
          // if this is the first instance, add the label in the form
 
          if (($old_template_name != $template_name) && isset($template_label)) {
 
            $form_text .= "<fieldset>\n";
 
            $form_text .= "<legend>$template_label</legend>\n";
 
          }
 
          $template_text .= "{{" . $tif->template_name;
 
          $all_fields = $tif->getAllFields();
 
          // remove template tag
 
          $section = substr_replace($section, '', $brackets_loc, $brackets_end_loc + 3 - $brackets_loc);
 
          $template_instance_query_values = $wgRequest->getArray($query_template_name);
 
          // if we are editing a page, and this template can be found more than
 
          // once in that page, and multiple values are allowed, repeat this
 
          // section
 
          $existing_template_text = null;
 
          if ($source_is_page) {
 
            if ($allow_multiple) {
 
              // find the number of instances of this template in the page -
 
              // if it's more than one, re-parse this section of the
 
              // definition form for the subsequent template instances in
 
              // this page; if there's none, don't include fields at all.
 
              // 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.
 
              if (preg_match_all('/\{\{' . $tif->template_name . '(.*?)\}\}/mis', $existing_page_content, $matches)) {
 
                $instance_num++;
 
              } else {
 
                $all_instances_printed = true;
 
              }
 
            }
 
            // get the first instance of this template on the page being edited,
 
            // even if there are more
 
            if (preg_match('/\{\{' . $tif->template_name . '(.*?)\}\}/mis', $existing_page_content, $matches)) {
 
              $existing_template_text = $matches[1];
 
              // create array of contents of this template
 
              // somewhat of a hack - this array starts out with one element,
 
              // so that adding fields with no corresponding key will give them
 
              // an index starting with 1, not 0, to match MediaWiki's counting
 
              // system
 
              $template_contents = array(null);
 
              // cycle through template call, splitting it up by pipes ('|'),
 
              // except when that pipe is part of a piped link
 
              $field = "";
 
              $uncompleted_square_brackets = 0;
 
              for ($i = 0; $i < strlen($existing_template_text); $i++) {
 
                $c = $existing_template_text[$i];
 
                if (($i == strlen($existing_template_text) - 1) ||
 
                    ($c == '|' && $uncompleted_square_brackets == 0)) {
 
                  if ($field != null) {
 
                    // if this was the last character in the template, append
 
                    // this character
 
                    if ($i == strlen($existing_template_text) - 1) {
 
                      $field .= $c;
 
                    }
 
                    // 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) {
 
                      $template_contents[trim($sub_fields[0])] = trim($sub_fields[1]);
 
                    } else {
 
                      $template_contents[] = trim($sub_fields[0]);
 
                    }
 
                    $field = '';
 
                  }
 
                } else {
 
                  $field .= $c;
 
                  if ($c == '[') {
 
                    $uncompleted_square_brackets++;
 
                  } elseif ($c == ']' && $uncompleted_square_brackets > 0) {
 
                    $uncompleted_square_brackets--;
 
                  }
 
                }
 
              }
 
              // now remove this template from the text being edited
 
              $existing_page_content = str_replace($matches[0], '', $existing_page_content);
 
            }
 
          }
 
          // if the input is from the form (meaning the user has hit one
 
          // of the bottom row of buttons), and we're dealing with a
 
          // multiple template, get the values for this instance of this
 
          // template, then delete them from the array, so we can get the
 
          // next group next time - the next() command for arrays doesn't
 
          // seem to work here
 
        if ((! $source_is_page) && $allow_multiple && $wgRequest) {
 
            $all_instances_printed = true;
 
            if ($old_template_name != $template_name) {
 
              $all_values_for_template = $wgRequest->getArray($query_template_name);
 
            }
 
            if ($all_values_for_template) {
 
              $cur_key = key($all_values_for_template);
 
              // skip the input coming in from the "starter" div
 
              if ($cur_key == 'num') {
 
                unset($all_values_for_template[$cur_key]);
 
                $cur_key = key($all_values_for_template);
 
              }
 
              if ($template_instance_query_values = current($all_values_for_template)) {
 
                $all_instances_printed = false;
 
                unset($all_values_for_template[$cur_key]);
 
              }
 
            }
 
          }
 
        } elseif ($tag_title == 'end template') {
 
          // remove this tag, reset some variables, and close off form HTML tag
 
          $section = substr_replace($section, '', $brackets_loc, $brackets_end_loc + 3 - $brackets_loc);
 
          $template_name = null;
 
          if (isset($template_label)) {
 
            $form_text .= "</fieldset>\n";
 
            unset ($template_label);
 
          }
 
          $allow_multiple = false;
 
          $all_instances_printed = false;
 
          $instance_num = 0;
 
        } elseif ($tag_title == 'field') {
 
          $field_name = trim($tag_components[1]);
 
          // cycle through the other components
 
          $is_mandatory = false;
 
          $is_hidden = false;
 
          $is_restricted = false;
 
          $input_type = null;
 
          $no_autocomplete = false;
 
          $autocomplete_category = null;
 
          $size = null;
 
          $num_rows = null;
 
          $num_cols = null;
 
          $default_value = "";
 
          $preload_page = null;
 
          for ($i = 2; $i < count($tag_components); $i++) {
 
            $component = trim($tag_components[$i]);
 
            if ($component == 'mandatory') {
 
              $is_mandatory = true;
 
            } elseif ($component == 'hidden') {
 
              $is_hidden = true;
 
            } elseif ($component == 'restricted') {
 
              $is_restricted = true;
 
            } else {
 
              $sub_components = explode('=', $component);
 
              if (count($sub_components) == 2) {
 
                if ($sub_components[0] == 'input type') {
 
                  $input_type = $sub_components[1];
 
                } elseif ($sub_components[0] == 'autocomplete on') {
 
                  $autocomplete_category = $sub_components[1];
 
                  // this field is overloaded - if it's empty, that indicates
 
                  // no automatic autocompleting should happen
 
                  if ($autocomplete_category == null) {
 
                    $no_autocomplete = true;
 
                  }
 
                } elseif ($sub_components[0] == 'size') {
 
                  $size = $sub_components[1];
 
                } elseif ($sub_components[0] == 'rows') {
 
                  $num_rows = $sub_components[1];
 
                } elseif ($sub_components[0] == 'cols') {
 
                  $num_cols = $sub_components[1];
 
                } elseif ($sub_components[0] == 'default') {
 
                  $default_value = $sub_components[1];
 
                } elseif ($sub_components[0] == 'preload') {
 
                  $preload_page = $sub_components[1];
 
                }
 
              }
 
            }
 
          }
 
          // get the value from the request, if it's there
 
          $cur_value = $template_instance_query_values[$field_name];
 
          if ($cur_value && ! is_array($cur_value)) {
 
            $cur_value = Sanitizer::safeEncodeAttribute($cur_value);
 
          }
 
          if (! $cur_value) {
 
            // set to default value specified in the form, if it's there
 
            $cur_value = $default_value;
 
          }
 
 
          // if the user is starting to edit a page, and that page contains
 
          // the current template being processed, get the current template
 
          // field's value in the existing page
 
          if ($source_is_page && ($existing_template_text != null)) {
 
            $cur_value = $template_contents[$field_name];
 
            if ($cur_value) {
 
              $cur_value = Sanitizer::safeEncodeAttribute($cur_value);
 
            }
 
          }
 
 
          // hack - if this is a 'restricted' field, and the user isn't
 
          // a sysop, set the global $gDisabledText temporarily to
 
          // 'disabled' - set it back at the end of this function
 
          $actual_disabled_text = $gDisabledText;
 
          // determine whether user is a sysop by whether or not they're
 
          // allowed to delete things - if there's a better way, please let
 
          // me know
 
          if ($is_restricted && (! $wgUser || ! $wgUser->isAllowed('delete'))) {
 
            // set background color to get around weird IE behavior (field
 
            // is disabled, but color stays white)
 
            $gDisabledText = "disabled";
 
            //$gDisabledText = "disabled style=\"background-color: #dddddd; border: 1px solid #ccccff;\"";
 
            //$gDisabledText = "disabled readonly";
 
          }
 
 
          // handle non-template fields - 'page title' and 'free text'
 
          if ($template_name == '') {
 
            if ($field_name == 'page title') {
 
              // the actual value should be non-null - stick it in
 
              $new_text = $page_title;
 
            } elseif ($field_name == 'free text') {
 
              // add placeholders for the free text in both the form and
 
              // the page, using <free_text> tags - once all the free text
 
              // is known (at the end), it will get substituted in
 
              if ($is_hidden) {
 
                $new_text = SFFormPrinter::hiddenFieldHTML('free_text', '<free_text>');
 
              } else {
 
                if ($num_rows == null) $num_rows = 5;
 
                if ($num_cols == null) $num_cols = 30;
 
                $new_text = SFFormPrinter::textAreaHTML($num_rows, $num_cols, 'free_text', '<free_text>', null);
 
              }
 
              $free_text_was_included = true;
 
              // add a similar placeholder to the data text
 
              $data_text .= "<free_text>\n\n";
 
            }
 
            $section = substr_replace($section, $new_text, $brackets_loc, $brackets_end_loc + 3 - $brackets_loc);
 
          } else { // this field is part of a template
 
            if (is_array($cur_value)) {
 
              // if it has 1 or 2 elements, assume it's a checkbox; if it has
 
              // 3 elements, assume it's a date
 
              // - this handling will have to get more complex if other
 
              // possibilities get added
 
              if (count($cur_value) == 1) {
 
                $words_for_false = explode(',', wfMsgForContent('smw_false_words'));
 
                // for the various languages, the second word in the 'false'
 
                // series tends to be "no" - go with that one
 
                if (count($words_for_false) > 2) {
 
                  $no = $words_for_false[1];
 
                } else {
 
                  $no = $words_for_false[0];
 
                }
 
                $cur_value_in_template = $no;
 
              } elseif (count($cur_value) == 2) {
 
                $words_for_true = explode(',', wfMsgForContent('smw_true_words'));
 
                // the second word in the 'true' series tends to be "yes" -
 
                // go with that one
 
                if (count($words_for_true) > 2) {
 
                  $yes = $words_for_true[1];
 
                } else {
 
                  $yes = $words_for_true[0];
 
                }
 
                $cur_value_in_template = $yes;
 
              } elseif (count($cur_value) == 3) {
 
                $month = $cur_value['month'];
 
                $day = $cur_value['day'];
 
                $year = $cur_value['year'];
 
                if ($month != '' && $day != '' && $year != '') {
 
                  $cur_value_in_template = "$month $day, $year";
 
                } else {
 
                  $cur_value_in_template = "";
 
                }
 
              }
 
            } else { // value is not an array
 
              $cur_value_in_template = $cur_value;
 
            }
 
            if ($query_template_name == null || $query_template_name == '')
 
              $input_name = $field_name;
 
            elseif ($allow_multiple)
 
              // 'num' will get replaced by an actual index, either in PHP
 
              // or in Javascript, later on
 
              $input_name = $query_template_name . '[num][' . $field_name . ']';
 
            else
 
              $input_name = $query_template_name . '[' . $field_name . ']';
 
            $new_text = SFFormPrinter::formTemplateFieldHTML($field_name, $input_name, $allow_multiple,
 
              $cur_value, $is_mandatory, $is_hidden, $is_restricted, $input_type, $size, $num_rows, $num_cols, $no_autocomplete,
 
              $autocomplete_category, $all_fields, $strict_parsing, $autocomplete_value_array);
 
 
            // if this was field was disabled due to being 'restricted',
 
            // restore $gDisabledText back to its actual value, and add
 
            // a hidden field holding the value of this field, because
 
            // disabled inputs for some reason don't submit their value
 
            if ($is_restricted && ! $wgUser->isAllowed('delete')) {
 
              $gDisabledText = $actual_disabled_text;
 
              if ($field_name == 'free text') {
 
                $new_text .= SFFormPrinter::hiddenFieldHTML('free_text', '<free_text>');
 
              } else {
 
                $new_text .= SFFormPrinter::hiddenFieldHTML($input_name, $cur_value);
 
              }
 
            }
 
 
            if ($new_text) {
 
              if (is_numeric($field_name)) {
 
                // if the value is null, don't include it at all -
 
                // TODO: this isn't quite right
 
                if ($cur_value_in_template != '')
 
                  $template_text .= "|$cur_value_in_template";
 
              } else {
 
                // if the value is null, don't include it at all
 
                if ($cur_value_in_template != '')
 
                  $template_text .= "\n|$field_name=$cur_value_in_template";
 
              }
 
              $section = substr_replace($section, $new_text, $brackets_loc, $brackets_end_loc + 3 - $brackets_loc);
 
              // also add to Javascript validation code
 
              if ($is_mandatory) {
 
$input_id = "input_" . $gTabIndex;
 
$info_id = "info_" . $gTabIndex;
 
                $js_validation_calls[] = "validate_mandatory_field ('$input_id', '$info_id')";
 
              }
 
            } else {
 
              $start_position = $brackets_end_loc;
 
            }
 
          }
 
        } else { // tag is not one of the three allowed values
 
          // ignore tag
 
          $start_position = $brackets_end_loc;
 
        }
 
      } // end while
 
 
      if (! $all_instances_printed && ($template_text != '')) {
 
// add another newline before the final bracket, if this template
 
// call is already more than one line
 
        if (strpos($template_text, "\n")) {
 
          $template_text .= "\n";
 
        }
 
        $template_text .= "}}\n\n";
 
        // TODO - should instances of the same template not be separated
 
        // by a blank line? if so, the following code could be used:
 
        //if (($data_text != "") &&
 
        //    (! $allow_multiple || $old_template_name != $template_name)) {
 
        //  $data_text .= "\n";
 
        //}
 
        $data_text .= $template_text;
 
      }
 
      if ($allow_multiple) {
 
        if (! $all_instances_printed) {
 
          $section = str_replace('[num]', "[{$instance_num}a]", $section);
 
          $remove_text = wfMsg('sf_editdata_remove');
 
          $form_text .=<<<END
 
<div id="wrapper_$gTabIndex" class="multiple_template">
 
        $section
 
        <input type="button" onclick="removeInstance('wrapper_$gTabIndex');" value="$remove_text" tabindex="$gTabIndex" />
 
        </div>
 
 
END;
 
          // this will cause the section to be re-parsed on the next go
 
          $section_num--;
 
        } else {
 
          // this is the last instance of this template - stick an 'add'
 
          // button in the form
 
        $form_text .=<<<END
 
<div id="starter_$query_template_name" class="multiple_template" style="display: none;">
 
        $section
 
        </div>
 
        <div id="main_$query_template_name"></div>
 
 
END;
 
          $add_another = wfMsg('sf_editdata_addanother');
 
          $form_text .=<<<END
 
<p style="margin-left:10px;">
 
<p><input type="button" onclick="addInstance('starter_$query_template_name', 'main_$query_template_name', '$gTabIndex');" value="$add_another" tabindex="$gTabIndex" /></p>
 
 
END;
 
        }
 
      } else {
 
        $form_text .= $section;
 
      }
 
 
    } // end for
 
 
    // if it wasn't already included in the form definition, put the
 
    // 'free text' input at the bottom of the form
 
    if (! $free_text_was_included) {
 
      $form_text .= ' <fieldset><legend>' . wfMsg('sf_editdata_freetextlabel') . "</legend>\n";
 
      $form_text .= SFFormPrinter::textAreaHTML(5, 30, 'free_text', '<free_text>', null);
 
      $form_text .= "    </fieldset>\n";
 
    }
 
    // get free text, and add to page data, as well as retroactively
 
    // inserting it into the form
 
    if ($source_is_page) {
 
      // if the page is the source, free_text will just be whatever in the
 
      // page hasn't already been inserted into the form
 
      $free_text = trim($existing_page_content);
 
    // or get it from a form submission
 
    } elseif ($wgRequest->getCheck('free_text')) {
 
      $free_text = Sanitizer::safeEncodeAttribute($wgRequest->getVal('free_text'));
 
      if (! $free_text_was_included) {
 
        $data_text .= "<free_text>";
 
      }
 
    // of get it from the form definition
 
    } elseif ($preload_page != null) {
 
      $free_text = SFFormPrinter::getPreloadedText($preload_page);
 
    } else {
 
      $free_text = null;
 
    }
 
    // now that we have it, substitute free text into the form and page
 
    $form_text = str_replace('<free_text>', $free_text, $form_text);
 
    $data_text = str_replace('<free_text>', $free_text, $data_text);
 
 
    // add general Javascript code
 
    $blank_error_str = wfMsg('sf_blank_error');
 
    $javascript_text .=<<<END
 
 
function validate_mandatory_field(field_id, info_id) {
 
field = document.getElementById(field_id);
 
if (field.value.replace(/\s+/, '') == '') {
 
infobox = document.getElementById(info_id);
 
infobox.innerHTML = "$blank_error_str";
 
//field.style.border = "1px solid red";
 
return false;
 
} else {
 
return true;
 
}
 
}
 
 
function validate_all() {
 
var num_errors = 0;
 
 
END;
 
    foreach ($js_validation_calls as $function_call) {
 
      $javascript_text .= " if (!$function_call) num_errors += 1;\n";
 
    }
 
    $remove_text = wfMsg('sf_editdata_remove');
 
    $javascript_text .=<<<END
 
return (num_errors == 0);
 
}
 
 
var num_elements = 0;
 
 
function addInstance(starter_div_id, main_div_id, tab_index)
 
{
 
var starter_div = document.getElementById(starter_div_id);
 
var main_div = document.getElementById(main_div_id);
 
num_elements++;
 
 
//Create the new instance
 
var new_div = starter_div.cloneNode(true);
 
var div_id = 'div_gen_' + num_elements;
 
new_div.className = 'multiple_template';
 
new_div.id = div_id;
 
new_div.style.display = 'block';
 
 
// make internal ID unique for the relevant divs and spans, and replace
 
// the [num] index in the element names with an actual unique index
 
var children = new_div.getElementsByTagName('*');
 
var x;
 
for (x=0;x<children.length;x++)
 
{
 
if (children[x].name)
 
children[x].name = children[x].name.replace(/\[num\]/g, '[' + num_elements + ']');
 
if (children[x].id)
 
children[x].id = children[x].id
 
.replace(/input_/g, 'input_' + num_elements + '_')
 
.replace(/info_/g, 'info_' + num_elements + '_')
 
.replace(/div_/g, 'div_' + num_elements + '_');
 
}
 
 
//Create remove button
 
var remove_button = document.createElement('input');
 
remove_button.type = 'button';
 
remove_button.value = '$remove_text';
 
remove_button.tabIndex = tab_index;
 
remove_button.onclick = removeInstanceEventHandler(div_id);
 
new_div.appendChild(remove_button);
 
 
//Add the new instance
 
main_div.appendChild(new_div);
 
attachAutocompleteToAllFields(new_div);
 
}
 
 
function removeInstanceEventHandler(this_div_id)
 
{
 
return function()
 
{
 
removeInstance(this_div_id);
 
};
 
}
 
 
function removeInstance(div_id) {
 
var olddiv = document.getElementById(div_id);
 
olddiv.parentNode.removeChild(olddiv);
 
}
 
 
var autocompletestrings = new Array();
 
var autocompletemappings = new Array();
 
 
//Activate autocomplete functionality for every field on the document
 
function attachAutocompleteToAllDocumentFields()
 
{
 
var forms = document.getElementsByTagName("form");
 
var x;
 
for (x=0;x<forms.length;x++)
 
{
 
if (forms[x].name == "createbox")
 
{
 
attachAutocompleteToAllFields(forms[x]);
 
}
 
}
 
}
 
 
//Activate autocomplete functionality for every field under the specified element
 
function attachAutocompleteToAllFields(base)
 
{
 
var inputs = base.getElementsByTagName("input");
 
var y;
 
for (y=0;y<inputs.length;y++)
 
{
 
attachAutocompleteToField(inputs[y].id);
 
}
 
}
 
 
//Activate autocomplete functionality for the specified field
 
function attachAutocompleteToField(input_id)
 
{
 
//Check input id for the proper format, to ensure this is for SF
 
if (input_id.substr(0,6) == 'input_')
 
{
 
//Extract the field ID number from the input field
 
var field_num = parseInt(input_id.substring(input_id.lastIndexOf('_') + 1, input_id.length),10);
 
//Add the autocomplete string, if a mapping exists.
 
if (autocompletemappings[field_num])
 
{
 
var div_id = input_id.replace(/input_/g, 'div_');
 
new Autocompleter.Local(input_id, div_id, autocompletestrings[autocompletemappings[field_num]], {});
 
}
 
}
 
}
 
 
Event.observe(window, 'load', attachAutocompleteToAllDocumentFields);
 
 
END;
 
 
//Send the autocomplete values to the browser, along with the mappings of which values should apply to which fields
 
foreach ($autocomplete_value_array as $autocomplete_key => $autocomplete_string)
 
{
 
if ($autocomplete_key == "mappings")
 
{
 
foreach ($autocomplete_string as $field_number => $field_key)
 
{
 
$javascript_text .= "autocompletemappings[$field_number] = '" . str_replace("'", "\'", $field_key) . "';\n";
 
}
 
}
 
else
 
{
 
$javascript_text .= "autocompletestrings['" . str_replace("'", "\'", $autocomplete_key) . "'] = $autocomplete_string;\n";
 
}
 
}
 
    return array($form_text, $javascript_text, $title, $data_text);
 
  }
 
 
  function formTemplateFieldHTML($field_name, $input_name, $part_of_multiple, $cur_value, $is_mandatory, $is_hidden, $is_restricted, $input_type, $size, $num_rows, $num_cols, $no_autocomplete, $autocomplete_category, $all_fields, $strict_parsing, &$out_autocomplete_values) {
 
  global $gTabIndex;
 
 
    // see if this field matches one of the fields defined for this template -
 
    // if it is, use all available information about that field; if it's not,
 
    // either include it in the form or not, depending on whether template
 
    // has 'strict' setting in the form definition
 
    $the_field = null;
 
    foreach ($all_fields as $cur_field) {
 
      if ($field_name == $cur_field->field_name) {
 
        $the_field = $cur_field;
 
        break;
 
      }
 
    }
 
    if ($the_field == null) {
 
      if ($strict_parsing)
 
        return null;
 
      $the_field = new SFTemplateField();
 
    }
 
 
    // if this is not part of a 'multiple' template, incrememt the
 
    // global tab index (used for correct tabbing, and for creating
 
    // unique div IDs.)
 
    if (! $part_of_multiple)
 
      $gTabIndex++;
 
 
    // populate field object with settings from the form definition file
 
    $the_field->is_mandatory = $is_mandatory;
 
    $the_field->is_hidden = $is_hidden;
 
    $the_field->is_restricted = $is_restricted;
 
    $the_field->input_type = $input_type;
 
    $the_field->size = $size;
 
    $the_field->num_rows = $num_rows;
 
    $the_field->num_cols = $num_cols;
 
    $the_field->no_autocomplete = $no_autocomplete;
 
    $the_field->autocomplete_category = $autocomplete_category;
 
    $the_field->input_name = $input_name;
 
    $the_field->part_of_multiple = $part_of_multiple;
 
    $text = SFFormPrinter::formFieldHTML($the_field, $cur_value, $out_autocomplete_values);
 
    return $text;
 
  }
 
 
  function formFieldHTML($template_field, $cur_value, &$out_autocomplete_values) {
 
    global $smwgContLang;
 
//When a field is generated that requires autocomplete, the autocomplete string will be added to out_autocomplete_values. Then, at the end,
 
//Javascript will be generated to activate autocomplete field at once.
 
 
    if ($template_field->is_hidden) {
 
      $text = SFFormPrinter::hiddenFieldHTML($template_field->input_name, $cur_value);
 
    } elseif ($template_field->autocomplete_category != null) {
 
      $size = $template_field->size;
 
      if ($size == null) $size = 35;
 
      $text = SFFormPrinter::textEntryWithAutocompleteHTML($size, $template_field->input_name, $template_field->part_of_multiple, $template_field->autocomplete_category, false, $cur_value, $out_autocomplete_values);
 
    } elseif ($template_field->input_type == 'text') {
 
      $size = $template_field->size;
 
      if ($size == null) $size = 35;
 
      $text = SFFormPrinter::textEntryHTML($size, $template_field->input_name, $cur_value);
 
    } elseif ($template_field->input_type == 'textarea') {
 
      $num_rows = $template_field->num_rows;
 
      if ($num_rows == null) $num_rows = 4;
 
      $num_cols = $template_field->num_cols;
 
      if ($num_cols == null) $num_cols = 40;
 
      $text = SFFormPrinter::textAreaHTML($num_rows, $num_cols, $template_field->input_name, $cur_value);
 
    } elseif ($template_field->input_type == 'date') {
 
      $text = SFFormPrinter::dateEntryHTML($template_field->input_name, $cur_value);
 
} elseif ($template_field->input_type == 'coordinatesmap') {
 
      $text = SFFormPrinter::coordinatesmapEntryHTML($template_field->input_name, $cur_value); 
 
    } elseif ($template_field->input_type == 'checkbox') {
 
      $text = SFFormPrinter::checkboxHTML($template_field->input_name, $cur_value);
 
    } elseif ($template_field->attr_or_rel == "relation") {
 
      $size = $template_field->size;
 
      if ($size == null) $size = 35;
 
      if ($template_field->no_autocomplete) {
 
        $text = SFFormPrinter::textEntryHTML($size, $template_field->input_name, $cur_value);
 
      } else {
 
        $text = SFFormPrinter::textEntryWithAutocompleteHTML($size, $template_field->input_name, $template_field->part_of_multiple, $template_field->semantic_field, true, $cur_value, $out_autocomplete_values);
 
      }
 
    } else { // input type not defined in form, and not a relation
 
      $attr_type = $template_field->attribute_type;
 
      $size = $template_field->size;
 
      if ($attr_type == $smwgContLang->smwDatatypeLabels['smw_enum']) {
 
        // prepend the "None" option if it's not a mandatory field
 
        $include_none = ! $template_field->is_mandatory;
 
        $text = SFFormPrinter::dropdownHTML($template_field->input_name, $template_field->possible_values, $include_none, $cur_value);
 
      } elseif ($attr_type == $smwgContLang->smwDatatypeLabels['smw_datetime']) {
 
        $text = SFFormPrinter::dateEntryHTML($template_field->input_name, $cur_value);
 
      } elseif ($attr_type == $smwgContLang->smwDatatypeLabels['smw_float'] || $attr_type == $smwgContLang->smwDatatypeLabels['smw_int']) {
 
        if ($size == null) $size = 10;
 
        $text = SFFormPrinter::textEntryHTML($size, $template_field->input_name, $cur_value);
 
      } elseif ($attr_type == $smwgContLang->smwDatatypeLabels['smw_url']) {
 
        if ($size == null) $size = 100;
 
        $text = SFFormPrinter::textEntryHTML($size, $template_field->input_name, $cur_value);
 
      } elseif ($attr_type == $smwgContLang->smwDatatypeLabels['smw_bool']) {
 
        $text = SFFormPrinter::checkboxHTML($template_field->input_name, $cur_value);
 
      } else { // String or anything else
 
        if ($size == null) $size = 35;
 
        $text = SFFormPrinter::textEntryHTML($size, $template_field->input_name, $cur_value);
 
      }
 
    }
 
    return $text;
 
  }
 
 
  function createAutocompleteValuesString($field_name, $is_relation) {
 
    global $sfgMaxAutocompleteValues;
 
    $fname = 'SFFormPrinter::createAutocompleteValuesString';
 
    $db = & wfGetDB( DB_SLAVE );
 
    $sql_options = array();
 
    $sql_options['LIMIT'] = $sfgMaxAutocompleteValues;
 
    // the query depends on whether this field is a relation or a category
 
    if ($is_relation) {
 
      $conditions = "relation_title = '$field_name'";
 
      $sql_options['ORDER BY'] = 'object_title';
 
      $res = $db->select( $db->tableName('smw_relations'),
 
                          'DISTINCT object_title',
 
                          $conditions, $fname, $sql_options);
 
    } else {
 
      $conditions = "cl_from = page_id AND cl_to = '$field_name'";
 
      $sql_options['ORDER BY'] = 'page_title';
 
      $res = $db->select( $db->tableNames('categorylinks', 'page'),
 
                          'page_title',
 
                          $conditions, $fname, $sql_options);
 
    }
 
    if ($db->numRows( $res ) > 0) {
 
      $names_array = array();
 
      while ($row = $db->fetchRow($res)) {
 
        $cur_value = str_replace("'", "\'", $row[0]);
 
        $names_array[] = str_replace('_', ' ', $cur_value);
 
      }
 
      $array_str = "['" . implode("', '", $names_array) . "']";
 
    }
 
    $db->freeResult($res);
 
    return $array_str;
 
  }
 
 
  function textEntryHTML($size, $input_name, $cur_value) {
 
    global $gTabIndex, $gDisabledText;
 
    $input_id = "input_$gTabIndex";
 
    $info_id = "info_$gTabIndex";
 
 
    $text =<<<END
 
<input id="$input_id" tabindex="$gTabIndex" class="createboxInput" name="$input_name" type="text"
 
        value="$cur_value" size="$size" $gDisabledText/>
 
<span id="$info_id" class="error_message"></span>
 
 
END;
 
    return $text;
 
  }
 
 
  function dropdownHTML($input_name, $possible_values, $include_none, $cur_value) {
 
    global $gTabIndex, $gDisabledText;
 
 
    $text = '    <select tabindex="' . $gTabIndex . '" name="' . $input_name . '" ' . $gDisabledText . '>';
 
    if ($include_none)
 
      $text .= "  <option value=\"\">[None]</option>\n";
 
    foreach ($possible_values as $possible_value) {
 
      $text .= "  <option value=\"$possible_value\"";
 
      if ($possible_value == $cur_value) {$text .= " selected=\"selected\""; }
 
      $text .= ">$possible_value</option>\n";
 
    }
 
    $text .=<<<END
 
</select>
 
<span id="$info_id" class="error_message"></span>
 
 
END;
 
    return $text;
 
  }
 
 
  function textEntryWithAutocompleteHTML($size, $input_name, $part_of_multiple, $semantic_field_name, $is_relation, $cur_value, &$out_autocomplete_values) {
 
    global $gTabIndex, $gDisabledText;
 
    $gTabIndex++;
 
 
    $input_id = "input_" . $gTabIndex;
 
    $info_id = "info_" . $gTabIndex;
 
    $div_name = "div_" . $gTabIndex;
 
    $text =<<<END
 
        <input tabindex="$gTabIndex" id="$input_id" name="$input_name" type="text"
 
value="" size="$size" $gDisabledText/>
 
<span id="$info_id" class="error_message"></span>
 
<div class="page_name_auto_complete" id="$div_name" style="display:none"></div>
 
<script type="text/javascript">
 
 
END;
 
$options_str_key = $semantic_field_name . ',' . $is_relation;
 
if (!$out_autocomplete_values[$options_str_key])
 
$out_autocomplete_values[$options_str_key] = SFFormPrinter::createAutocompleteValuesString(str_replace(' ', '_', $semantic_field_name), $is_relation);
 
$out_autocomplete_values['mappings'][$gTabIndex] = $options_str_key;
 
    if ($cur_value) {
 
      $text .= "document.getElementById('$input_id').value = \"$cur_value\"\n";
 
    }
 
    $text .= "</script>\n";
 
    return $text;
 
  }
 
 
  function textAreaHTML($rows, $cols, $input_name, $cur_value) {
 
    global $gTabIndex, $gDisabledText;
 
    $input_id = "input_$gTabIndex";
 
    $info_id = "info_$gTabIndex";
 
 
    $text =<<<END
 
<textarea tabindex="$gTabIndex" id="$input_id" name="$input_name" rows=$rows cols=$cols $gDisabledText/>$cur_value</textarea>
 
<span id="$info_id" class="error_message"></span>
 
 
END;
 
    return $text;
 
  }
 
 
  function monthDropdownHTML($cur_month, $input_name) {
 
    global $gTabIndex, $gDisabledText;
 
 
    $text = ' <select tabindex="' . $gTabIndex . '" id="input_' . $gTabIndex . '" name="' . $input_name . "[month]\" $gDisabledText>\n";
 
    $month_names = array(
 
      wfMsgForContent('sf_january'),
 
      wfMsgForContent('sf_february'),
 
      wfMsgForContent('sf_march'),
 
      wfMsgForContent('sf_april'),
 
      wfMsgForContent('sf_may'),
 
      wfMsgForContent('sf_june'),
 
      wfMsgForContent('sf_july'),
 
      wfMsgForContent('sf_august'),
 
      wfMsgForContent('sf_september'),
 
      wfMsgForContent('sf_october'),
 
      wfMsgForContent('sf_november'),
 
      wfMsgForContent('sf_december')
 
    );
 
    foreach ($month_names as $name) {
 
      $text .= " <option value=\"$name\"";
 
      if ($name == $cur_month) {$text .= " selected=\"selected\""; }
 
      $text .= ">$name</option>\n";
 
    }
 
    $text .= " </select>\n";
 
    return $text;
 
  }
 
 
  function dateEntryHTML($input_name, $date) {
 
    global $gTabIndex, $gDisabledText;
 
 
    if ($date) {
 
      // can show up here either as an array or a string, depending on
 
      // whether it came from user input or a wiki page
 
      if (is_array($date)) {
 
        $year = $date['year'];
 
        $month = $date['month'];
 
        $day = $date['day'];
 
      } else {
 
        $actual_date = strtotime($date);
 
        $year = date("Y", $actual_date);
 
        $month = date("F", $actual_date);
 
        $day = date("j", $actual_date);
 
      }
 
    } else {
 
      $cur_date = getdate();
 
      $year = $cur_date[year];
 
      $month = $cur_date[month];
 
      $day = null; // no need for day
 
    }
 
    $text .= SFFormPrinter::monthDropdownHTML($month, $input_name);
 
    $text .= '  <input tabindex="' . $gTabIndex . '" id="input_' . $gTabIndex . '" name="' . $input_name . '[day]" type="text" value="' . $day . '" size="2"/ ' . $gDisabledText . '>' . "\n";
 
    $text .= '  <input tabindex="' . $gTabIndex . '" id="input_' . $gTabIndex . '" name="' . $input_name . '[year]" type="text" value="' . $year . '" size="4"/ ' . $gDisabledText . '>' . "\n";
 
    $info_id = "info_$gTabIndex";
 
    $text .= " <span id=\"$info_id\" class=\"error_message\"></span>";
 
    return $text;
 
  }
 
 
 
  function ConvertCoordinates($format,$coordinates) { 
 
 
$coordinates = preg_split("/,/", $coordinates);
 
 
switch ($format) {
 
case 'lat':
 
$lat = floatval($coordinates[0]);
 
if (preg_match("/S/",$coordinates[0])) {$lat *= -1;}
 
return $lat;
 
case 'lon':
 
$lon = floatval($coordinates[1]);
 
if (preg_match("/W/",$coordinates[1])) {$lon *= -1;}
 
return $lon;
 
}
 
 
}
 
 
function coordinatesmapEntryHTML($input_name, $coordinates) {
 
    global $gTabIndex, $gDisabledText, $wgJsMimeType, $wgGoogleMapsKey;;
 
 
    if ($coordinates) {
 
      // can show up here either as an array or a string, depending on
 
      // whether it came from user input or a wiki page
 
      if (is_array($coordinates)) {
 
  $flat = 666; // todo if relevant
 
$flon = 666;       
 
      } else {
 
        $flat = SFFormPrinter::ConvertCoordinates('lat',$coordinates);
 
$flon = SFFormPrinter::ConvertCoordinates('lon',$coordinates);
 
      }
 
    }
 
 
$wgGoogleMapsOnThisPage = 66;
 
$width = '200';
 
$height = '200';
 
$class = 'smwf_map';
 
$zoom = '16';
 
$type = 'G_HYBRID_MAP';
 
$controls = 'GSmallMapControl';
 
if ($flat == 0) { $lat = '50';} else {$lat = $flat;}
 
if ($flon == 0) { $lon = '5';} else {$lon = $flon;}
 
 
// input field
 
$text .= '  <input tabindex="' . $gTabIndex . '" id="input_' . $gTabIndex . '" name="' . $input_name . '[coordinates]" type="text" value="';
 
if (!$flat == 0 && !$flat == 0) { $text .= $flat . ',' . $flon; }
 
$text .= '" size="40"/ ' . $gDisabledText . '>' . "\n";
 
    $info_id = "info_$gTabIndex";
 
$text .= " <span id=\"$info_id\" class=\"error_message\"></span>";
 
 
// map div
 
$text .= '<div id="map'.$wgGoogleMapsOnThisPage.'" class="'.$class.'" ';
 
$text .= 'style="';
 
if ($width) {$text .= 'width: '.$width.'px; '; }
 
if ($height) {$text .= 'height: '.$height.'px; ';}
 
$text .= $style.'" ></div>';
 
 
//geocoder html
 
$text .= '
 
    <p>       
 
      <input size="60" id= "geocode" name="geocode" value="Stationstraat 3 Maastricht Netherlands" type="text">
 
      <a href="#" onClick="showAddress(document.forms[0].geocode.value); return false">Lookup coordinates</a>
 
    </p>
 
  <br />';
 
 
// map javascript
 
$text .= '<script src="http://maps.google.com/maps?file=api&v=2&key='.$wgGoogleMapsKey.'" type="'.$wgJsMimeType.'"></script>';
 
$text .= "<script type=\"text/javascript\">
 
function showAddress(address) {
 
makeMap{$wgGoogleMapsOnThisPage}();
 
if (geocoder) {
 
        geocoder.getLatLng(
 
          address,
 
          function(point) {
 
            if (!point) {
 
              alert(address + \" not found\");
 
            } else {
 
map.clearOverlays()
 
map.setCenter(point, 14);
 
          var marker = new GMarker(point); 
 
    map.addOverlay(marker);
 
document.getElementById(\"input_" . $gTabIndex . "\").value = convertLatToDMS(point.y)+', '+convertLngToDMS(point.x);
 
          }
 
          }
 
        );
 
      }
 
    }
 
function convertLatToDMS (val) {
 
  if (val < 0) {
 
    return Math.abs(val) + \"\u00B0 \" + \"S\";
 
  } else {
 
    return Math.abs(val) + \"\u00B0 \" + \"N\";
 
  }
 
}
 
 
function convertLngToDMS (val) {
 
  if (val < 0) {
 
    return Math.abs(val) + \"\u00B0 \" + \"W\";
 
  } else {
 
    return Math.abs(val) + \"\u00B0 \" + \"E\";
 
  }
 
} function addLoadEvent(func) {  var oldonload = window.onload;  if (typeof oldonload == 'function') {  window.onload = function() {  oldonload();  func();  };  } else {  window.onload = func;  }  }  window.unload = GUnload;</script>";
 
 
$text .= "<script type=\"text/javascript\"> function makeMap{$wgGoogleMapsOnThisPage}() { if (GBrowserIsCompatible()) {window.map = new GMap2(document.getElementById(\"map{$wgGoogleMapsOnThisPage}\")); geocoder = new GClientGeocoder(); map.addControl(new {$controls}()); map.addControl(new GMapTypeControl()); map.setCenter(new GLatLng({$lat}, {$lon}), {$zoom}, {$type}); var point = new GLatLng({$lat}, {$lon}); var marker = new GMarker(point); map.addOverlay(marker); GEvent.addListener(map, \"click\", function(overlay, point) {      place = null;      if (overlay) { map.removeOverlay (overlay);      } else {        var marker = new GMarker (point);        map.clearOverlays();        document.getElementById(\"input_" . $gTabIndex . "\").value = convertLatToDMS(point.y)+', '+convertLngToDMS(point.x);      map.addOverlay(marker);        map.panTo(point);      }    });  } else { document.write('should show map'); } } addLoadEvent(makeMap{$wgGoogleMapsOnThisPage});</script>";
 
   
 
   
 
    return $text;
 
  }
 
 
  function radioButtonHTML($input_name, $cur_value, $options_array) {
 
    global $gTabIndex, $gDisabledText;
 
 
    if ($title)
 
      $text .= "    <strong>$title:</strong>\n";
 
    foreach ($options_array as $i => $option) {
 
      $text .= ' <input type="radio" tabindex="' . $gTabIndex . '" name="' . $input_name . '" value="' . $option . '"';
 
      if ($cur_value == $option || (! $cur_value && $i == 0))
 
        $text .= " checked";
 
      $text .= " $gDisabledText/> $option\n";
 
    }
 
    return $text;
 
  }
 
 
  function checkboxHTML($input_name, $cur_value) {
 
    global $gTabIndex, $gDisabledText;
 
    $info_id = "info_$gTabIndex";
 
 
    // can show up here either as an array or a string, depending on
 
    // whether it came from user input or a wiki page
 
    if (is_array($cur_value)) {
 
      $checked_str = ($cur_value[value] == 'on') ? " checked" : "";
 
    } else {
 
      // default to false - no need to check if it matches a 'false' word
 
      $vlc = strtolower(trim($cur_value));
 
      if (in_array($vlc, explode(',', wfMsgForContent('smw_true_words')), TRUE)) {
 
        $checked_str = " checked";
 
      } else {
 
        $checked_str = "";
 
      }
 
    }
 
    $text =<<<END
 
<input name="{$input_name}[is_checkbox]" type="hidden" value="true" />
 
<input id="input_$gTabIndex" name="{$input_name}[value]" type="checkbox" tabindex="$gTabIndex " $checked_str $gDisabledText/>
 
<span id="$info_id" class="error_message"></span>
 
 
END;
 
    return $text;
 
  }
 
 
  function hiddenFieldHTML($input_name, $cur_value) {
 
    $text =<<<END
 
<input type="hidden" name="$input_name" value="$cur_value" />
 
 
END;
 
    return $text;
 
  }
 
 
  function redirectText($page_name, $data_text) {
 
    global $wgUser, $wgRequest;
 
 
    // first, add various hidden fields that allow for editing
 
    $title = Title::newFromText($page_name);
 
    $article = new Article($title);
 
    $starttime = wfTimestampNow();
 
    $edittime = $article->getTimestamp();
 
    if ( $wgUser->isLoggedIn() )
 
      $token = htmlspecialchars($wgUser->editToken());
 
    else
 
      $token = "";
 
    if ($wgRequest->getCheck('wpSave'))
 
      $page_action = "wpSave";
 
    elseif ($wgRequest->getVal('wpPreview'))
 
      $page_action = "wpPreview";
 
    else # if $wgRequest->getVal('wpDiff')
 
      $page_action = "wpDiff";
 
 
    global $wgScript;
 
    $encoded_page_name = urlencode($page_name);
 
    $text = <<<END
 
        <form id="editform" name="editform" method="post" action="$wgScript?title=$encoded_page_name&amp;action=submit">
 
        <input type="hidden" name="wpTextbox1" id="wpTextbox1" value="$data_text" />
 
    <input type="hidden" name="wpStarttime" value="$starttime" />
 
    <input type="hidden" name="wpEdittime" value="$edittime" />
 
    <input type="hidden" name="wpEditToken" value="$token" />
 
    <input type="hidden" name="wpSummary" value="{$wgRequest->getVal('wpSummary')}" />
 
 
END;
 
    if ($wgRequest->getCheck('wpMinoredit'))
 
      $text .= '    <input type="hidden" name="wpMinoredit">' . "\n";
 
    if ($wgRequest->getCheck('wpWatchthis'))
 
      $text .= '    <input type="hidden" name="wpWatchthis">' . "\n";
 
 
    $text .= <<<END
 
    <input type="hidden" name="$page_action" />
 
        </form>
 
        <script type="text/javascript">
 
        document.editform.submit();
 
        </script>
 
 
END;
 
    return $text;
 
  }
 
 
  // Much of this function is based on MediaWiki's EditPage::showEditForm()
 
  function formBottom($target_title = null) {
 
    global $wgVersion, $wgUser, $wgRightsText, $wgParser;
 
    global $gTabIndex, $gDisabledText;
 
    $sk = $wgUser->getSkin();
 
 
    $copywarn = "<div id=\"editpage-copywarn\">\n" .
 
      wfMsg( $wgRightsText ? 'copyrightwarning' : 'copyrightwarning2',
 
            '[[' . wfMsgForContent( 'copyrightpage' ) . ']]',
 
    null,
 
            $wgRightsText ) . "\n</div>";
 
    $title = new Title();
 
    $options = new ParserOptions();
 
    $copywarn_output = $wgParser->parse($copywarn, $title, $options);
 
    $copywarn_text = $copywarn_output->getText();
 
    $summary_text = wfMsg('summary');
 
    if ($target_title == null)
 
      $cancel = '';
 
    else
 
      $cancel = $sk->makeKnownLink( $target_title->getPrefixedText(),
 
                  wfMsgExt('cancel', array('parseinline')) );
 
   
 
//Fix so extension works with MediaWiki 1.7
 
if (substr_compare($wgVersion, '1.7', 0, 3) == 0)
 
{
 
$edithelpurl = $sk->makeInternalOrExternalUrl( wfMsgForContent( 'edithelppage' ));
 
}
 
else
 
{
 
$edithelpurl = Skin::makeInternalOrExternalUrl( wfMsgForContent( 'edithelppage' ));
 
}
 
   
 
    $edithelp = '<a target="helpwindow" href="'.$edithelpurl.'">'.
 
    htmlspecialchars( wfMsg( 'edithelp' ) ).'</a> '.
 
    htmlspecialchars( wfMsg( 'newwindow' ) );
 
 
    $gTabIndex++;
 
    $text =<<<END
 
$copywarn_text
 
<span id='wpSummaryLabel'><label for='wpSummary'>{$summary_text}:</label></span>
 
<div class='editOptions'>
 
<input tabindex="$gTabIndex" type='text' value="" name='wpSummary' id='wpSummary' maxlength='200' size='60' $gDisabledText/><br />
 
 
END;
 
    $minor  = wfMsgExt('minoredit', array('parseinline'));
 
    if ( $wgUser->isAllowed('minoredit') ) {
 
      $gTabIndex++;
 
      $text .=
 
        "<input tabindex='$gTabIndex' type='checkbox' value='1' name='wpMinoredit'" .
 
        " accesskey='".wfMsg('accesskey-minoredit')."' id='wpMinoredit' $gDisabledText/>\n".
 
        "<label for='wpMinoredit' title='".wfMsg('tooltip-minoredit')."'>{$minor}</label>\n";
 
    }
 
 
    $watchthis = wfMsgExt('watchthis', array('parseinline'));
 
    if ( $wgUser->isLoggedIn() ) {
 
      $gTabIndex++;
 
      $text .= "<input tabindex='$gTabIndex' type='checkbox' name='wpWatchthis'".
 
        " accesskey=\"".htmlspecialchars(wfMsg('accesskey-watch'))."\" id='wpWatchthis' $gDisabledText/>\n".
 
        "<label for='wpWatchthis' title=\"" .
 
        htmlspecialchars(wfMsg('tooltip-watch'))."\">{$watchthis}</label>\n";
 
    }
 
 
    $gTabIndex++;
 
    $temp = array(
 
      'id'        => 'wpSave',
 
      'name'      => 'wpSave',
 
      'type'      => 'submit',
 
      'tabindex'  => $gTabIndex,
 
      'value'    => wfMsg('savearticle'),
 
      'accesskey' => wfMsg('accesskey-save'),
 
      'title'    => wfMsg('tooltip-save'),
 
      $gDisabledText => '',
 
    );
 
    $save_button = wfElement('input', $temp, '');
 
 
    $gTabIndex++;
 
    $temp = array(
 
      'id'        => 'wpPreview',
 
      'name'      => 'wpPreview',
 
      'type'      => 'submit',
 
      'tabindex'  => $gTabIndex,
 
      'value'    => wfMsg('showpreview'),
 
      'accesskey' => wfMsg('accesskey-preview'),
 
      'title'    => wfMsg('tooltip-preview'),
 
      $gDisabledText => '',
 
    );
 
    $preview_button = wfElement('input', $temp, '');
 
 
    $gTabIndex++;
 
    $temp = array(
 
      'id'        => 'wpDiff',
 
      'name'      => 'wpDiff',
 
      'type'      => 'submit',
 
      'tabindex'  => $gTabIndex,
 
      'value'    => wfMsg('showdiff'),
 
      'accesskey' => wfMsg('accesskey-diff'),
 
      'title'    => wfMsg('tooltip-diff'),
 
      $gDisabledText => '',
 
    );
 
    $diff_button = wfElement('input', $temp, '');
 
 
    $text .=<<<END
 
<br />
 
<div class='editButtons'>
 
$save_button
 
$preview_button
 
$diff_button
 
<span class='editHelp'>{$cancel} | {$edithelp}</span>
 
</div><!-- editButtons -->
 
</div><!-- editOptions -->
 
</form>
 
 
END;
 
    return $text;
 
  }
 
 
  // based on MediaWiki's EditPage::getPreloadedText()
 
  function getPreloadedText($preload) {
 
    if ( $preload === '' )
 
      return '';
 
    else {
 
      $preloadTitle = Title::newFromText( $preload );
 
        if ( isset( $preloadTitle ) && $preloadTitle->userCanRead() ) {
 
          $rev=Revision::newFromTitle($preloadTitle);
 
        if ( is_object( $rev ) ) {
 
          $text = $rev->getText();
 
          // Remove <noinclude> sections and <includeonly> tags from text
 
          $text = StringUtils::delimiterReplace('<noinclude>', '</noinclude>', '', $text);
 
          $text = strtr($text, array('<includeonly>' => '', '</includeonly>' => ''));
 
          return $text;
 
        } else
 
          return '';
 
        }
 
    }
 
  }
 
 
}
 
 
?>
 
</source>
 

Revision as of 04:34, 18 March 2008

This webpage is dedicated to publish the results of an intense analysis of the MediaWiki DB backend, which constitutes the core of the Pantha Rei Schema Evolution Benchmark. These results have been presented at ICEIS 2008 [1] in the paper "Schema Evolution in Wikipedia: toward a Web Information System Benchmark" authored by Carlo A. Curino [2], Hyun J. Moon[3], Letizia Tanca[4] and Carlo Zaniolo[5]. The paper is available here: [6]

4.5 year of development have been analyzed and over 170 schema versions compared and studied. In this website we report the results of our analysis and provide the entire dataset we collected, to the purpose of defining a unified Benchmark for Schema Evolution.

Contents


Why Wikipedia

Google Search statistics on wikipedia popularity
Alexa.com statistics on wikipedia popularity

Wikipedia represent one of the 10 most popular websites in the WWW.

As shown by the two following graphs, showing respectively the google search popularity and the percentage of user visiting Wikipedia by http://www.alexa.com. Moreover, the PHP back-end underlying wikipedia is shared by other 30,000 wikis. Both software and content are released under open-source licenses. This make Wikipedia, and thus MediaWiki a perfect starting point for our goals.

MediaWiki Schema Evolution: a short Introduction

Evolving the database that is at the core of an Information System represents a difficult maintenance problem that has only been studied in the framework of traditional information systems. However, the problem is likely to be even more severe in web information systems, where open-source software is often developed through the contributions and collaboration of many groups and individuals. Therefore, in this paper, we present an in- depth analysis of the evolution history of the Wikipedia database and its schema; Wikipedia is the best-known example of a large family of web information systems built using the open-source MediaWiki software. Our study is based on: (i) a set of Schema Modification Operators that provide a simple conceptual representation for complex schema changes, and (ii) simple software tools to automate the analysis. This framework allowed us to dissect and analyze the 4.5 years of Wikipedia history, which was short in time, but intense in terms of growth and evolution. Beyond confirming the initial hunch about the severity of the problem, our analysis suggests the need for developing better methods and tools to support graceful schema evolution. Therefore, we briefly discuss documentation and automation support systems for database evolution, and suggest that the Wikipedia case study can provide the kernel of a benchmark for testing and improving such systems.

MediaWiki Architecture

MediaWiki Architecture

The MediaWiki software is a browser-based web-application, whose architecture is described in details in [Help:MediaWikiarchitecture] and in the MediaWiki Workbook2007 [7]. As shown in Figure, the users interact with the PHP frontend through a standard web browser, submitting a page request (e.g., a search for pages describing ``Paris). The frontend software consists of a simple presentation and management layer (MediaWiki PHP Scripts) interpreted by the Apache PHP engine. The user requests are carried out by generating appropriate SQL queries (or updates), that are then issued against the data stored in the backend DB (e.g., the database is queried looking for article's text containing the term ``Paris). The backend DB can be stored in any DBMS: MySQL, being open-source and scalable, is the default DBMS for the MediaWiki software. The results returned by the DBMS are rendered in XHTML and delivered to the user's browser to be displayed (e.g., a set of of links to pages mentioning ``Paris is rendered as an XHTML list). Due to the heavy load of the Wikipedia installation of this software, much of effort has been devoted to performance optimization, introducing several levels of caching (Rendered Web Page, DB caches, Media caches), which is particularly effective thanks to the very low rate (0.04\%) of updates w.r.t. queries. Obviously, every modification of the DB schema has a strong impact on the queries the frontend can pose. Typically each schema evolution step can require several queries to be modified, and so several PHP scripts (cooperating to interrogate the DB and render a page) to be manually fixed, in order to balance the schema changes.

MediaWiki Growth

Attributes and Tables growth of MediaWiki Schema

In this section, we analyze the schema evolution of MediaWiki based on its 171 schema versions, as committed to SVN between April 2003 (first schema revision) and November 2007 (date of this analysis).

Schema Size Growth In Figures, we report the size of MediaWiki DB schema in history, in terms of the number of tables and columns, respectively. The graphs show an evident trend of growth in sizes, where the number of tables has increased from 17 to 34 (100% increase) and the number of columns from 100 to 242 (142%). Sudden drops in the graphs are due to schema versions with syntax errors, i.e., schema versions that could not be properly installed. In both graphs we observe different rates of growth over time, which seem to be related to the time periods preceding or following official releases of the overall software (see Table in section Available Software Version).

Schema growth is due to three main driving forces as follows:

  • performance improvement, e.g., introduction of dedicated cache tables,
  • addition of new features, e.g., support for logging and content validation,
  • the growing need for preservation of DB content history, i.e., introduction of tables and columns to store outdated multimedia content such as the

'filearchive' table. The Figure shows a histogram representation of the table lifetimes, in terms of number of versions. The lifetimes range from very long ones, e.g., the user table that was alive throughout the entire history, to short ones, e.g., random table that only survived for two revisions. On average, each table lasted 103.3 versions (60.4% of the total DB history). Figure 5 presents lifetimes of columns in histogram, where columns lasted 97.17 versions on average (56.8% of the total DB history). Interestingly, both figures show that there are two main groups of tables and columns: “short-living” and “long-living”. The former might be due to the fact that the schema has been growing lately so a significant portion of tables and columns has been introduced only recently. The latter can be explained noting that the core tables/columns tend to be rather stable throughout the entire history.


Downloads

All the data, schemas, queries, tools are available to be download. Moreover, a couple of useful services are running on our servers providing a sandbox for testing and investingating schema evolution.

Please visit our Downloads section

Personal tools