Shared fields

Its not 100% clean to map one index field to multiple classes (at least in the way the current `SearchIndex` works), but its just too common to leave up to custom implementations. In all three search project I was involved in so far we needed to do this (to combine fields from CMS and DMS). See before/after example below.

 - I'm unsetting `base` and `origin` keys in case the field is shared. Can you think of any side effects from this? All tests pass.
 - It's assumed that the type of all shared fields is the same (up to the developer at the moment). The last type "wins".
 - Its all-or-nothing, you can't share a field on some classes, and have a separate field with the same name on another. Due to the way SS property access works, this will fail silently on unknown properties.
 - I've removed the "get<fieldname>" with type "method", and simply rely on <fieldname> with the type "property". This allows for more flexible overloading, e.g. SiteTree->Content as a native property, but DMSDocument->getContent() as a getter. SS treats them the same internally.
 - Haven't checked if shared fields work with relation lookup chains.

Before:

```
<?php
class PageSolrIndex extends SolrIndex {
	
	function init() {
		$this->addClass('Page');
		$this->addClass('DMSDocument');

		$stored = (Director::isDev()) ? 'true' : 'false';

		// Fields are available on both types
		$this->addFulltextField('Title', 'Text', array('boost' => '3',));
		// Highlighted fields need to be stored
		$this->addFulltextField('Content', 'HTMLText', array('stored' => $stored));
		$this->addFulltextField('Description', 'HTMLText', array('stored' => $stored, 'boost' => '1.25'));

		// Technically, filter and sort fields are the same in Solr/Lucene
		$this->addSortField('ViewCount');
		// Caution: Stores LastEdited for SiteTree, but LastChanged for DMSDocument (see below)
		$this->addSortField('LastEdited', 'SSDatetime');
		//$this->addSortField('Title'); 

		// Aggregate fields for spelling checks
		$this->addCopyField('SiteTree_Title', 'spellcheckData');
		$this->addCopyField('DMSDocument_Title', 'spellcheckData');
		$this->addCopyField('SiteTree_Content', 'spellcheckData');
		$this->addCopyField('DMSDocument_Content', 'spellcheckData');

		// Aggregate fields for highlighting
		$this->addCopyField('SiteTree_Title', 'highlightData', array('maxChars' => 10000));
		$this->addCopyField('DMSDocument_Title', 'highlightData', array('maxChars' => 10000));
		$this->addCopyField('SiteTree_Content', 'highlightData', array('maxChars' => 10000));
		$this->addCopyField('DMSDocument_Content', 'highlightData', array('maxChars' => 10000));

		// Don't support searching staging
		$this->excludeVariantState(array('SearchVariantVersioned' => 'Stage'));
	}

	protected function _addAs($object, $base, $options) {
		$includeSubs = $options['include_children'];

		$doc = new Apache_Solr_Document();

		// Always present fields

		$doc->setField('_documentid', $this->getDocumentID($object, $base, $includeSubs));
		$doc->setField('ID', $object->ID);
		$doc->setField('ClassName', $object->ClassName);

		foreach (SearchIntrospection::hierarchy(get_class($object), false) as $class) $doc->addField('ClassHierarchy', $class);

		// Add the user-specified fields

		foreach ($this->getFieldsIterator() as $name => $field) {
			if ($field['base'] == $base) $this->_addField($doc, $object, $field);
		}

		// CUSTOM Duplicate index combined fields ("Title" rather than "DMSDocument_Title" and "SiteTree_Title").
		// This allows us to sort on these fields without deeper architectural changes to the fulltextsearch module.
		// Note: We can't use <copyField> for this purpose because it only writes into multiValue=true
		// fields, and those can't be (reliably) sorted on.
		$this->_addField($doc, $object, $this->getCustomPropertyFieldData('Title', $object));
		$lastEditedField = ($object instanceof DMSDocument) ? 'LastChanged' : 'LastEdited';
		$this->_addField($doc, $object, $this->getCustomPropertyFieldData('LastEdited', $object, 'SSDatetime', $lastEditedField));	
		// CUSTOM END

		$this->getService()->addDocument($doc);

		return $doc;
	}

	function getFieldDefinitions() {
		$xml = parent::getFieldDefinitions();

		$stored = (Director::isDev()) ? "stored='true'" : "stored='false'";
		
		$xml .= "\n\n\t\t<!-- Additional custom fields for sorting, see PageSolrIndex.php -->";
		$xml .= "\n\t\t<field name='Title' type='alphaOnlySort' indexed='true' stored='true' />";
		$xml .= "\n\t\t<field name='LastEdited' type='tdate' indexed='true' stored='true' />";

		return $xml;
	}

	/**
	 * Return a fake field definition (mainly to stay DRY).
	 */
	protected function getCustomPropertyFieldData($name, $object, $type = null, $customProperty = null) {
		if(!$type) $type = 'Varchar';
		return array(
			'name' => $name,
			'field' => $name,
			'fullfield' => $name,
			'base' => get_class($object),
			'origin' => get_class($object),
			'class' => get_class($object),
			'lookup_chain' => array(
				array('call' => 'property', 'property' => $customProperty ? $customProperty : $name)
			),
			'type' => $type,
			'multi_valued' => false,
			'extra_options' => array()
		);
	}
}	
```

After:

```
<?php
class PageSolrIndex extends SolrIndex {
	
	function init() {
		$this->addClass('Page');
		$this->addClass('DMSDocument');

		// Fields are available on both types
		$this->addFulltextField('Title', 'Text', array('boost' => '3', 'shared' => true));
		// Highlighted fields need to be stored
		$this->addFulltextField('Content', 'HTMLText', array('stored' => $stored, 'shared' => true));
		$this->addFulltextField('Description', 'HTMLText', array('stored' => $stored, 'boost' => '1.25'));

		// Technically, filter and sort fields are the same in Solr/Lucene
		$this->addSortField('ViewCount');
		$this->addSortField('LastEdited', 'SSDatetime', array('shared' => true));

		// Aggregate fields for spelling checks
		$this->addCopyField('Title', 'spellcheckData');
		$this->addCopyField('Content', 'spellcheckData');

		// Aggregate fields for highlighting
		$this->addCopyField('Title', 'highlightData', array('maxChars' => 10000));
		$this->addCopyField('Content', 'highlightData', array('maxChars' => 10000));

		// Don't support searching staging
		$this->excludeVariantState(array('SearchVariantVersioned' => 'Stage'));
	}

}	
```