mirror of
https://github.com/silverstripe/silverstripe-fulltextsearch
synced 2024-10-22 12:05:29 +00:00
Converted to PSR-2
This commit is contained in:
parent
c96d4bf749
commit
314feddd48
@ -3,11 +3,11 @@
|
||||
/**
|
||||
* Base class to manage active search indexes.
|
||||
*/
|
||||
class FullTextSearch {
|
||||
class FullTextSearch
|
||||
{
|
||||
protected static $all_indexes = null;
|
||||
|
||||
static protected $all_indexes = null;
|
||||
|
||||
static protected $indexes_by_subclass = array();
|
||||
protected static $indexes_by_subclass = array();
|
||||
|
||||
/**
|
||||
* Optional list of index names to limit to. If left empty, all subclasses of SearchIndex
|
||||
@ -26,7 +26,8 @@ class FullTextSearch {
|
||||
* @param String $class - Class name to filter indexes by, so that all returned indexes are subclasses of provided class
|
||||
* @param bool $rebuild - If true, don't use cached values
|
||||
*/
|
||||
static function get_indexes($class = null, $rebuild = false) {
|
||||
public static function get_indexes($class = null, $rebuild = false)
|
||||
{
|
||||
if ($rebuild) {
|
||||
self::$all_indexes = null;
|
||||
self::$indexes_by_subclass = array();
|
||||
@ -70,14 +71,15 @@ class FullTextSearch {
|
||||
}
|
||||
|
||||
return self::$all_indexes;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if (!isset(self::$indexes_by_subclass[$class])) {
|
||||
$all = self::get_indexes();
|
||||
|
||||
$valid = array();
|
||||
foreach ($all as $indexclass => $instance) {
|
||||
if (is_subclass_of($indexclass, $class)) $valid[$indexclass] = $instance;
|
||||
if (is_subclass_of($indexclass, $class)) {
|
||||
$valid[$indexclass] = $instance;
|
||||
}
|
||||
}
|
||||
|
||||
self::$indexes_by_subclass[$class] = $valid;
|
||||
@ -99,7 +101,8 @@ class FullTextSearch {
|
||||
*
|
||||
* Alternatively you can use `FullTextSearch.indexes` to configure a list of indexes via config.
|
||||
*/
|
||||
static function force_index_list() {
|
||||
public static function force_index_list()
|
||||
{
|
||||
$indexes = func_get_args();
|
||||
|
||||
// No arguments = back to automatic
|
||||
@ -109,18 +112,25 @@ class FullTextSearch {
|
||||
}
|
||||
|
||||
// Arguments can be a single array
|
||||
if (is_array($indexes[0])) $indexes = $indexes[0];
|
||||
if (is_array($indexes[0])) {
|
||||
$indexes = $indexes[0];
|
||||
}
|
||||
|
||||
// Reset to empty first
|
||||
self::$all_indexes = array(); self::$indexes_by_subclass = array();
|
||||
self::$all_indexes = array();
|
||||
self::$indexes_by_subclass = array();
|
||||
|
||||
// And parse out alternative type combos for arguments and add to allIndexes
|
||||
foreach ($indexes as $class => $index) {
|
||||
if (is_string($index)) { $class = $index; $index = singleton($class); }
|
||||
if (is_numeric($class)) $class = get_class($index);
|
||||
if (is_string($index)) {
|
||||
$class = $index;
|
||||
$index = singleton($class);
|
||||
}
|
||||
if (is_numeric($class)) {
|
||||
$class = get_class($index);
|
||||
}
|
||||
|
||||
self::$all_indexes[$class] = $index;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,8 +27,8 @@
|
||||
* - Specifying update rules that are not extractable from metadata (because the values come from functions for instance)
|
||||
*
|
||||
*/
|
||||
abstract class SearchIndex extends ViewableData {
|
||||
|
||||
abstract class SearchIndex extends ViewableData
|
||||
{
|
||||
/**
|
||||
* Allows this index to hide a parent index. Specifies the name of a parent index to disable
|
||||
*
|
||||
@ -37,7 +37,8 @@ abstract class SearchIndex extends ViewableData {
|
||||
*/
|
||||
private static $hide_ancestor;
|
||||
|
||||
public function __construct() {
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->init();
|
||||
|
||||
@ -48,7 +49,8 @@ abstract class SearchIndex extends ViewableData {
|
||||
$this->buildDependancyList();
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
public function __toString()
|
||||
{
|
||||
return 'Search Index ' . get_class($this);
|
||||
}
|
||||
|
||||
@ -56,7 +58,8 @@ abstract class SearchIndex extends ViewableData {
|
||||
* Examines the classes this index is built on to try and find defined fields in the class hierarchy for those classes.
|
||||
* Looks for db and viewable-data fields, although can't nessecarily find type for viewable-data fields.
|
||||
*/
|
||||
public function fieldData($field, $forceType = null, $extraOptions = array()) {
|
||||
public function fieldData($field, $forceType = null, $extraOptions = array())
|
||||
{
|
||||
$fullfield = str_replace(".", "_", $field);
|
||||
$sources = $this->getClasses();
|
||||
|
||||
@ -86,16 +89,14 @@ abstract class SearchIndex extends ViewableData {
|
||||
'call' => 'method', 'method' => $lookup,
|
||||
'through' => 'has_one', 'class' => $dataclass, 'otherclass' => $class, 'foreignkey' => "{$lookup}ID"
|
||||
);
|
||||
}
|
||||
else if ($hasMany = $singleton->has_many($lookup)) {
|
||||
} elseif ($hasMany = $singleton->has_many($lookup)) {
|
||||
$class = $hasMany;
|
||||
$options['multi_valued'] = true;
|
||||
$options['lookup_chain'][] = array(
|
||||
'call' => 'method', 'method' => $lookup,
|
||||
'through' => 'has_many', 'class' => $dataclass, 'otherclass' => $class, 'foreignkey' => $singleton->getRemoteJoinField($lookup, 'has_many')
|
||||
);
|
||||
}
|
||||
else if ($manyMany = $singleton->many_many($lookup)) {
|
||||
} elseif ($manyMany = $singleton->many_many($lookup)) {
|
||||
$class = $manyMany[1];
|
||||
$options['multi_valued'] = true;
|
||||
$options['lookup_chain'][] = array(
|
||||
@ -105,14 +106,18 @@ abstract class SearchIndex extends ViewableData {
|
||||
}
|
||||
|
||||
if ($class) {
|
||||
if (!isset($options['origin'])) $options['origin'] = $dataclass;
|
||||
if (!isset($options['origin'])) {
|
||||
$options['origin'] = $dataclass;
|
||||
}
|
||||
$next[$class] = $options;
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$next) return $next; // Early out to avoid excessive empty looping
|
||||
if (!$next) {
|
||||
return $next;
|
||||
} // Early out to avoid excessive empty looping
|
||||
$sources = $next;
|
||||
}
|
||||
}
|
||||
@ -122,23 +127,28 @@ abstract class SearchIndex extends ViewableData {
|
||||
|
||||
while (count($dataclasses)) {
|
||||
$dataclass = array_shift($dataclasses);
|
||||
$type = null; $fieldoptions = $options;
|
||||
$type = null;
|
||||
$fieldoptions = $options;
|
||||
|
||||
$fields = DataObject::database_fields($dataclass);
|
||||
|
||||
if (isset($fields[$field])) {
|
||||
$type = $fields[$field];
|
||||
$fieldoptions['lookup_chain'][] = array('call' => 'property', 'property' => $field);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$singleton = singleton($dataclass);
|
||||
|
||||
if ($singleton->hasMethod("get$field") || $singleton->hasField($field)) {
|
||||
$type = $singleton->castingClass($field);
|
||||
if (!$type) $type = 'String';
|
||||
if (!$type) {
|
||||
$type = 'String';
|
||||
}
|
||||
|
||||
if ($singleton->hasMethod("get$field")) $fieldoptions['lookup_chain'][] = array('call' => 'method', 'method' => "get$field");
|
||||
else $fieldoptions['lookup_chain'][] = array('call' => 'property', 'property' => $field);
|
||||
if ($singleton->hasMethod("get$field")) {
|
||||
$fieldoptions['lookup_chain'][] = array('call' => 'method', 'method' => "get$field");
|
||||
} else {
|
||||
$fieldoptions['lookup_chain'][] = array('call' => 'property', 'property' => $field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,7 +156,9 @@ abstract class SearchIndex extends ViewableData {
|
||||
// Don't search through child classes of a class we matched on. TODO: Should we?
|
||||
$dataclasses = array_diff($dataclasses, array_values(ClassInfo::subclassesFor($dataclass)));
|
||||
// Trim arguments off the type string
|
||||
if (preg_match('/^(\w+)\(/', $type, $match)) $type = $match[1];
|
||||
if (preg_match('/^(\w+)\(/', $type, $match)) {
|
||||
$type = $match[1];
|
||||
}
|
||||
// Get the origin
|
||||
$origin = isset($fieldoptions['origin']) ? $fieldoptions['origin'] : $dataclass;
|
||||
|
||||
@ -191,7 +203,8 @@ abstract class SearchIndex extends ViewableData {
|
||||
* @param String $class - The class to include
|
||||
* @param array $options - TODO: Remove
|
||||
*/
|
||||
public function addClass($class, $options = array()) {
|
||||
public function addClass($class, $options = array())
|
||||
{
|
||||
if ($this->fulltextFields || $this->filterFields || $this->sortFields) {
|
||||
throw new Exception('Can\'t add class to Index after fields have already been added');
|
||||
}
|
||||
@ -210,7 +223,10 @@ abstract class SearchIndex extends ViewableData {
|
||||
/**
|
||||
* Get the classes added by addClass
|
||||
*/
|
||||
public function getClasses() { return $this->classes; }
|
||||
public function getClasses()
|
||||
{
|
||||
return $this->classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a field that should be fulltext searchable
|
||||
@ -218,11 +234,15 @@ abstract class SearchIndex extends ViewableData {
|
||||
* @param String $forceType - The type to force this field as (required in some cases, when not detectable from metadata)
|
||||
* @param String $extraOptions - Dependent on search implementation
|
||||
*/
|
||||
public function addFulltextField($field, $forceType = null, $extraOptions = array()) {
|
||||
public function addFulltextField($field, $forceType = null, $extraOptions = array())
|
||||
{
|
||||
$this->fulltextFields = array_merge($this->fulltextFields, $this->fieldData($field, $forceType, $extraOptions));
|
||||
}
|
||||
|
||||
public function getFulltextFields() { return $this->fulltextFields; }
|
||||
public function getFulltextFields()
|
||||
{
|
||||
return $this->fulltextFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a field that should be filterable
|
||||
@ -230,11 +250,15 @@ abstract class SearchIndex extends ViewableData {
|
||||
* @param String $forceType - The type to force this field as (required in some cases, when not detectable from metadata)
|
||||
* @param String $extraOptions - Dependent on search implementation
|
||||
*/
|
||||
public function addFilterField($field, $forceType = null, $extraOptions = array()) {
|
||||
public function addFilterField($field, $forceType = null, $extraOptions = array())
|
||||
{
|
||||
$this->filterFields = array_merge($this->filterFields, $this->fieldData($field, $forceType, $extraOptions));
|
||||
}
|
||||
|
||||
public function getFilterFields() { return $this->filterFields; }
|
||||
public function getFilterFields()
|
||||
{
|
||||
return $this->filterFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a field that should be sortable
|
||||
@ -242,11 +266,15 @@ abstract class SearchIndex extends ViewableData {
|
||||
* @param String $forceType - The type to force this field as (required in some cases, when not detectable from metadata)
|
||||
* @param String $extraOptions - Dependent on search implementation
|
||||
*/
|
||||
public function addSortField($field, $forceType = null, $extraOptions = array()) {
|
||||
public function addSortField($field, $forceType = null, $extraOptions = array())
|
||||
{
|
||||
$this->sortFields = array_merge($this->sortFields, $this->fieldData($field, $forceType, $extraOptions));
|
||||
}
|
||||
|
||||
public function getSortFields() { return $this->sortFields; }
|
||||
public function getSortFields()
|
||||
{
|
||||
return $this->sortFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all database-backed text fields as fulltext searchable fields.
|
||||
@ -254,14 +282,19 @@ abstract class SearchIndex extends ViewableData {
|
||||
* For every class included in the index, examines those classes and all subclasses looking for "Text" database
|
||||
* fields (Varchar, Text, HTMLText, etc) and adds them all as fulltext searchable fields.
|
||||
*/
|
||||
public function addAllFulltextFields($includeSubclasses = true) {
|
||||
public function addAllFulltextFields($includeSubclasses = true)
|
||||
{
|
||||
foreach ($this->getClasses() as $class => $options) {
|
||||
foreach (SearchIntrospection::hierarchy($class, $includeSubclasses, true) as $dataclass) {
|
||||
$fields = DataObject::database_fields($dataclass);
|
||||
|
||||
foreach ($fields as $field => $type) {
|
||||
if (preg_match('/^(\w+)\(/', $type, $match)) $type = $match[1];
|
||||
if (is_subclass_of($type, 'StringField')) $this->addFulltextField($field);
|
||||
if (preg_match('/^(\w+)\(/', $type, $match)) {
|
||||
$type = $match[1];
|
||||
}
|
||||
if (is_subclass_of($type, 'StringField')) {
|
||||
$this->addFulltextField($field);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -273,34 +306,45 @@ abstract class SearchIndex extends ViewableData {
|
||||
*
|
||||
* @return MultipleArrayIterator
|
||||
*/
|
||||
public function getFieldsIterator() {
|
||||
public function getFieldsIterator()
|
||||
{
|
||||
return new MultipleArrayIterator($this->fulltextFields, $this->filterFields, $this->sortFields);
|
||||
}
|
||||
|
||||
public function excludeVariantState($state) {
|
||||
public function excludeVariantState($state)
|
||||
{
|
||||
$this->excludedVariantStates[] = $state;
|
||||
}
|
||||
|
||||
/** Returns true if some variant state should be ignored */
|
||||
public function variantStateExcluded($state) {
|
||||
public function variantStateExcluded($state)
|
||||
{
|
||||
foreach ($this->excludedVariantStates as $excludedstate) {
|
||||
$matches = true;
|
||||
|
||||
foreach ($excludedstate as $variant => $variantstate) {
|
||||
if (!isset($state[$variant]) || $state[$variant] != $variantstate) { $matches = false; break; }
|
||||
if (!isset($state[$variant]) || $state[$variant] != $variantstate) {
|
||||
$matches = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($matches) return true;
|
||||
if ($matches) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public $dependancyList = array();
|
||||
|
||||
public function buildDependancyList() {
|
||||
public function buildDependancyList()
|
||||
{
|
||||
$this->dependancyList = array_keys($this->getClasses());
|
||||
|
||||
foreach ($this->getFieldsIterator() as $name => $field) {
|
||||
if (!isset($field['class'])) continue;
|
||||
if (!isset($field['class'])) {
|
||||
continue;
|
||||
}
|
||||
SearchIntrospection::add_unique_by_ancestor($this->dependancyList, $field['class']);
|
||||
}
|
||||
}
|
||||
@ -311,12 +355,15 @@ abstract class SearchIndex extends ViewableData {
|
||||
* Returns an array where each member is all the fields and the classes that are at the end of some
|
||||
* specific lookup chain from one of the base classes
|
||||
*/
|
||||
public function getDerivedFields() {
|
||||
public function getDerivedFields()
|
||||
{
|
||||
if ($this->derivedFields === null) {
|
||||
$this->derivedFields = array();
|
||||
|
||||
foreach ($this->getFieldsIterator() as $name => $field) {
|
||||
if (count($field['lookup_chain']) < 2) continue;
|
||||
if (count($field['lookup_chain']) < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = sha1($field['base'].serialize($field['lookup_chain']));
|
||||
$fieldname = "{$field['class']}:{$field['field']}";
|
||||
@ -324,8 +371,7 @@ abstract class SearchIndex extends ViewableData {
|
||||
if (isset($this->derivedFields[$key])) {
|
||||
$this->derivedFields[$key]['fields'][$fieldname] = $fieldname;
|
||||
SearchIntrospection::add_unique_by_ancestor($this->derivedFields['classes'], $field['class']);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$chain = array_reverse($field['lookup_chain']);
|
||||
array_shift($chain);
|
||||
|
||||
@ -350,7 +396,8 @@ abstract class SearchIndex extends ViewableData {
|
||||
* @param Array $state - The variant state of the object
|
||||
* @return string - The document ID as a string
|
||||
*/
|
||||
public function getDocumentIDForState($base, $id, $state) {
|
||||
public function getDocumentIDForState($base, $id, $state)
|
||||
{
|
||||
ksort($state);
|
||||
$parts = array('id' => $id, 'base' => $base, 'state' => json_encode($state));
|
||||
return implode('-', array_values($parts));
|
||||
@ -364,7 +411,8 @@ abstract class SearchIndex extends ViewableData {
|
||||
* @param Boolean $includesubs - TODO: Probably going away
|
||||
* @return string - The document ID as a string
|
||||
*/
|
||||
public function getDocumentID($object, $base, $includesubs) {
|
||||
public function getDocumentID($object, $base, $includesubs)
|
||||
{
|
||||
return $this->getDocumentIDForState($base, $object->ID, SearchVariant::current_state($base, $includesubs));
|
||||
}
|
||||
|
||||
@ -375,13 +423,16 @@ abstract class SearchIndex extends ViewableData {
|
||||
* @param Array $field - The field definition to use
|
||||
* @return Mixed - The value of the field, or null if we couldn't look it up for some reason
|
||||
*/
|
||||
protected function _getFieldValue($object, $field) {
|
||||
protected function _getFieldValue($object, $field)
|
||||
{
|
||||
set_error_handler(create_function('$no, $str', 'throw new Exception("HTML Parse Error: ".$str);'), E_ALL);
|
||||
|
||||
try {
|
||||
foreach ($field['lookup_chain'] as $step) {
|
||||
// Just fail if we've fallen off the end of the chain
|
||||
if (!$object) return null;
|
||||
if (!$object) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If we're looking up this step on an array or SS_List, do the step on every item, merge result
|
||||
if (is_array($object) || $object instanceof SS_List) {
|
||||
@ -391,15 +442,18 @@ abstract class SearchIndex extends ViewableData {
|
||||
if ($step['call'] == 'method') {
|
||||
$method = $step['method'];
|
||||
$item = $item->$method();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$property = $step['property'];
|
||||
$item = $item->$property;
|
||||
}
|
||||
|
||||
if ($item instanceof SS_List) $next = array_merge($next, $item->toArray());
|
||||
elseif (is_array($item)) $next = array_merge($next, $item);
|
||||
else $next[] = $item;
|
||||
if ($item instanceof SS_List) {
|
||||
$next = array_merge($next, $item->toArray());
|
||||
} elseif (is_array($item)) {
|
||||
$next = array_merge($next, $item);
|
||||
} else {
|
||||
$next[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
$object = $next;
|
||||
@ -409,20 +463,18 @@ abstract class SearchIndex extends ViewableData {
|
||||
if ($step['call'] == 'method') {
|
||||
$method = $step['method'];
|
||||
$object = $object->$method();
|
||||
}
|
||||
elseif ($step['call'] == 'variant') {
|
||||
} elseif ($step['call'] == 'variant') {
|
||||
$variants = SearchVariant::variants($field['base'], true);
|
||||
$variant = $variants[$step['variant']]; $method = $step['method'];
|
||||
$variant = $variants[$step['variant']];
|
||||
$method = $step['method'];
|
||||
$object = $variant->$method($object);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$property = $step['property'];
|
||||
$object = $object->$property;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
$object = null;
|
||||
}
|
||||
|
||||
@ -442,13 +494,13 @@ abstract class SearchIndex extends ViewableData {
|
||||
* @param $fields
|
||||
* @return array
|
||||
*/
|
||||
public function getDirtyIDs($class, $id, $statefulids, $fields) {
|
||||
public function getDirtyIDs($class, $id, $statefulids, $fields)
|
||||
{
|
||||
$dirty = array();
|
||||
|
||||
// First, if this object is directly contained in the index, add it
|
||||
foreach ($this->classes as $searchclass => $options) {
|
||||
if ($searchclass == $class || ($options['include_children'] && is_subclass_of($class, $searchclass))) {
|
||||
|
||||
$base = ClassInfo::baseDataClass($searchclass);
|
||||
$dirty[$base] = array();
|
||||
foreach ($statefulids as $statefulid) {
|
||||
@ -464,8 +516,12 @@ abstract class SearchIndex extends ViewableData {
|
||||
// Then, for every derived field
|
||||
foreach ($this->getDerivedFields() as $derivation) {
|
||||
// If the this object is a subclass of any of the classes we want a field from
|
||||
if (!SearchIntrospection::is_subclass_of($class, $derivation['classes'])) continue;
|
||||
if (!array_intersect_key($fields, $derivation['fields'])) continue;
|
||||
if (!SearchIntrospection::is_subclass_of($class, $derivation['classes'])) {
|
||||
continue;
|
||||
}
|
||||
if (!array_intersect_key($fields, $derivation['fields'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (SearchVariant::reindex_states($class, false) as $state) {
|
||||
SearchVariant::activate_state($state);
|
||||
@ -478,8 +534,7 @@ abstract class SearchIndex extends ViewableData {
|
||||
singleton($step['class'])->extend('augmentSQL', $sql);
|
||||
|
||||
$ids = $sql->execute()->column();
|
||||
}
|
||||
else if ($step['through'] == 'has_many') {
|
||||
} elseif ($step['through'] == 'has_many') {
|
||||
$sql = new SQLQuery('"'.$step['class'].'"."ID"', '"'.$step['class'].'"', '"'.$step['otherclass'].'"."ID" IN ('.implode(',', $ids).')');
|
||||
$sql->addInnerJoin($step['otherclass'], '"'.$step['class'].'"."ID" = "'.$step['otherclass'].'"."'.$step['foreignkey'].'"');
|
||||
singleton($step['class'])->extend('augmentSQL', $sql);
|
||||
@ -492,7 +547,9 @@ abstract class SearchIndex extends ViewableData {
|
||||
|
||||
if ($ids) {
|
||||
$base = $derivation['base'];
|
||||
if (!isset($dirty[$base])) $dirty[$base] = array();
|
||||
if (!isset($dirty[$base])) {
|
||||
$dirty[$base] = array();
|
||||
}
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$statefulid = array('id' => $id, 'state' => $state);
|
||||
@ -526,32 +583,39 @@ abstract class SearchIndex extends ViewableData {
|
||||
/**
|
||||
* A search index that does nothing. Useful for testing
|
||||
*/
|
||||
abstract class SearchIndex_Null extends SearchIndex {
|
||||
abstract class SearchIndex_Null extends SearchIndex
|
||||
{
|
||||
public function add($object)
|
||||
{
|
||||
}
|
||||
|
||||
public function add($object) { }
|
||||
|
||||
public function delete($base, $id, $state) { }
|
||||
|
||||
public function commit() { }
|
||||
public function delete($base, $id, $state)
|
||||
{
|
||||
}
|
||||
|
||||
public function commit()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A search index that just records actions. Useful for testing
|
||||
*/
|
||||
abstract class SearchIndex_Recording extends SearchIndex {
|
||||
|
||||
abstract class SearchIndex_Recording extends SearchIndex
|
||||
{
|
||||
public $added = array();
|
||||
public $deleted = array();
|
||||
public $committed = false;
|
||||
|
||||
public function reset() {
|
||||
public function reset()
|
||||
{
|
||||
$this->added = array();
|
||||
$this->deleted = array();
|
||||
$this->committed = false;
|
||||
}
|
||||
|
||||
public function add($object) {
|
||||
public function add($object)
|
||||
{
|
||||
$res = array();
|
||||
|
||||
$res['ID'] = $object->ID;
|
||||
@ -564,13 +628,16 @@ abstract class SearchIndex_Recording extends SearchIndex {
|
||||
$this->added[] = $res;
|
||||
}
|
||||
|
||||
public function getAdded($fields = array()) {
|
||||
public function getAdded($fields = array())
|
||||
{
|
||||
$res = array();
|
||||
|
||||
foreach ($this->added as $added) {
|
||||
$filtered = array();
|
||||
foreach ($fields as $field) {
|
||||
if (isset($added[$field])) $filtered[$field] = $added[$field];
|
||||
if (isset($added[$field])) {
|
||||
$filtered[$field] = $added[$field];
|
||||
}
|
||||
}
|
||||
$res[] = $filtered;
|
||||
}
|
||||
@ -578,25 +645,29 @@ abstract class SearchIndex_Recording extends SearchIndex {
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function delete($base, $id, $state) {
|
||||
public function delete($base, $id, $state)
|
||||
{
|
||||
$this->deleted[] = array('base' => $base, 'id' => $id, 'state' => $state);
|
||||
}
|
||||
|
||||
public function commit() {
|
||||
public function commit()
|
||||
{
|
||||
$this->committed = true;
|
||||
}
|
||||
|
||||
public function getIndexName() {
|
||||
public function getIndexName()
|
||||
{
|
||||
return get_class($this);
|
||||
}
|
||||
|
||||
public function getIsCommitted() {
|
||||
public function getIsCommitted()
|
||||
{
|
||||
return $this->committed;
|
||||
}
|
||||
|
||||
public function getService() {
|
||||
public function getService()
|
||||
{
|
||||
// Causes commits to the service to be redirected back to the same object
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,8 +3,8 @@
|
||||
/**
|
||||
* Some additional introspection tools that are used often by the fulltext search code
|
||||
*/
|
||||
class SearchIntrospection {
|
||||
|
||||
class SearchIntrospection
|
||||
{
|
||||
protected static $ancestry = array();
|
||||
|
||||
/**
|
||||
@ -14,7 +14,8 @@ class SearchIntrospection {
|
||||
* @param $of
|
||||
* @return bool
|
||||
*/
|
||||
static function is_subclass_of ($class, $of) {
|
||||
public static function is_subclass_of($class, $of)
|
||||
{
|
||||
$ancestry = isset(self::$ancestry[$class]) ? self::$ancestry[$class] : (self::$ancestry[$class] = ClassInfo::ancestry($class));
|
||||
return is_array($of) ? (bool)array_intersect($of, $ancestry) : array_key_exists($of, $ancestry);
|
||||
}
|
||||
@ -30,18 +31,27 @@ class SearchIntrospection {
|
||||
* @param bool $dataOnly - True to only return classes that have tables
|
||||
* @return Array - Integer keys, String values as classes sorted by depth (most super first)
|
||||
*/
|
||||
static function hierarchy ($class, $includeSubclasses = true, $dataOnly = false) {
|
||||
public static function hierarchy($class, $includeSubclasses = true, $dataOnly = false)
|
||||
{
|
||||
$key = "$class!" . ($includeSubclasses ? 'sc' : 'an') . '!' . ($dataOnly ? 'do' : 'al');
|
||||
|
||||
if (!isset(self::$hierarchy[$key])) {
|
||||
$classes = array_values(ClassInfo::ancestry($class));
|
||||
if ($includeSubclasses) $classes = array_unique(array_merge($classes, array_values(ClassInfo::subclassesFor($class))));
|
||||
if ($includeSubclasses) {
|
||||
$classes = array_unique(array_merge($classes, array_values(ClassInfo::subclassesFor($class))));
|
||||
}
|
||||
|
||||
$idx = array_search('DataObject', $classes);
|
||||
if ($idx !== false) array_splice($classes, 0, $idx+1);
|
||||
if ($idx !== false) {
|
||||
array_splice($classes, 0, $idx+1);
|
||||
}
|
||||
|
||||
if ($dataOnly) foreach($classes as $i => $class) {
|
||||
if (!DataObject::has_own_table($class)) unset($classes[$i]);
|
||||
if ($dataOnly) {
|
||||
foreach ($classes as $i => $class) {
|
||||
if (!DataObject::has_own_table($class)) {
|
||||
unset($classes[$i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self::$hierarchy[$key] = $classes;
|
||||
@ -53,9 +63,12 @@ class SearchIntrospection {
|
||||
/**
|
||||
* Add classes to list, keeping only the parent when parent & child are both in list after add
|
||||
*/
|
||||
static function add_unique_by_ancestor(&$list, $class) {
|
||||
public static function add_unique_by_ancestor(&$list, $class)
|
||||
{
|
||||
// If class already has parent in list, just ignore
|
||||
if (self::is_subclass_of($class, $list)) return;
|
||||
if (self::is_subclass_of($class, $list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Strip out any subclasses of $class already in the list
|
||||
$children = ClassInfo::subclassesFor($class);
|
||||
@ -68,11 +81,13 @@ class SearchIntrospection {
|
||||
/**
|
||||
* Does this class, it's parent (or optionally one of it's children) have the passed extension attached?
|
||||
*/
|
||||
static function has_extension($class, $extension, $includeSubclasses = true) {
|
||||
public static function has_extension($class, $extension, $includeSubclasses = true)
|
||||
{
|
||||
foreach (self::hierarchy($class, $includeSubclasses) as $relatedclass) {
|
||||
if ($relatedclass::has_extension($extension)) return true;
|
||||
if ($relatedclass::has_extension($extension)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,12 +5,12 @@
|
||||
*
|
||||
* API very much still in flux.
|
||||
*/
|
||||
class SearchQuery extends ViewableData {
|
||||
class SearchQuery extends ViewableData
|
||||
{
|
||||
public static $missing = null;
|
||||
public static $present = null;
|
||||
|
||||
static $missing = null;
|
||||
static $present = null;
|
||||
|
||||
static $default_page_size = 10;
|
||||
public static $default_page_size = 10;
|
||||
|
||||
/** These are public, but only for index & variant access - API users should not manually access these */
|
||||
|
||||
@ -26,9 +26,14 @@ class SearchQuery extends ViewableData {
|
||||
|
||||
/** These are the API functions */
|
||||
|
||||
function __construct() {
|
||||
if (self::$missing === null) self::$missing = new stdClass();
|
||||
if (self::$present === null) self::$present = new stdClass();
|
||||
public function __construct()
|
||||
{
|
||||
if (self::$missing === null) {
|
||||
self::$missing = new stdClass();
|
||||
}
|
||||
if (self::$present === null) {
|
||||
self::$present = new stdClass();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -37,7 +42,8 @@ class SearchQuery extends ViewableData {
|
||||
* @param array $boost Map of composite field names to float values. The higher the value,
|
||||
* the more important the field gets for relevancy.
|
||||
*/
|
||||
function search($text, $fields = null, $boost = array()) {
|
||||
public function search($text, $fields = null, $boost = array())
|
||||
{
|
||||
$this->search[] = array('text' => $text, 'fields' => $fields ? (array)$fields : null, 'boost' => $boost, 'fuzzy' => false);
|
||||
}
|
||||
|
||||
@ -50,11 +56,13 @@ class SearchQuery extends ViewableData {
|
||||
* @param array $fields See {@link search()}
|
||||
* @param array $boost See {@link search()}
|
||||
*/
|
||||
function fuzzysearch($text, $fields = null, $boost = array()) {
|
||||
public function fuzzysearch($text, $fields = null, $boost = array())
|
||||
{
|
||||
$this->search[] = array('text' => $text, 'fields' => $fields ? (array)$fields : null, 'boost' => $boost, 'fuzzy' => true);
|
||||
}
|
||||
|
||||
function inClass($class, $includeSubclasses = true) {
|
||||
public function inClass($class, $includeSubclasses = true)
|
||||
{
|
||||
$this->classes[] = array('class' => $class, 'includeSubclasses' => $includeSubclasses);
|
||||
}
|
||||
|
||||
@ -65,7 +73,8 @@ class SearchQuery extends ViewableData {
|
||||
* @param String $field Composite name of the field
|
||||
* @param Mixed $values Scalar value, array of values, or an instance of SearchQuery_Range
|
||||
*/
|
||||
function filter($field, $values) {
|
||||
public function filter($field, $values)
|
||||
{
|
||||
$requires = isset($this->require[$field]) ? $this->require[$field] : array();
|
||||
$values = is_array($values) ? $values : array($values);
|
||||
$this->require[$field] = array_merge($requires, $values);
|
||||
@ -77,30 +86,36 @@ class SearchQuery extends ViewableData {
|
||||
* @param String $field
|
||||
* @param mixed $values
|
||||
*/
|
||||
function exclude($field, $values) {
|
||||
public function exclude($field, $values)
|
||||
{
|
||||
$excludes = isset($this->exclude[$field]) ? $this->exclude[$field] : array();
|
||||
$values = is_array($values) ? $values : array($values);
|
||||
$this->exclude[$field] = array_merge($excludes, $values);
|
||||
}
|
||||
|
||||
function start($start) {
|
||||
public function start($start)
|
||||
{
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
function limit($limit) {
|
||||
public function limit($limit)
|
||||
{
|
||||
$this->limit = $limit;
|
||||
}
|
||||
|
||||
function page($page) {
|
||||
public function page($page)
|
||||
{
|
||||
$this->start = $page * self::$default_page_size;
|
||||
$this->limit = self::$default_page_size;
|
||||
}
|
||||
|
||||
function isfiltered() {
|
||||
public function isfiltered()
|
||||
{
|
||||
return $this->search || $this->classes || $this->require || $this->exclude;
|
||||
}
|
||||
|
||||
function __toString() {
|
||||
public function __toString()
|
||||
{
|
||||
return "Search Query\n";
|
||||
}
|
||||
}
|
||||
@ -109,25 +124,29 @@ class SearchQuery extends ViewableData {
|
||||
* Create one of these and pass as one of the values in filter or exclude to filter or exclude by a (possibly
|
||||
* open ended) range
|
||||
*/
|
||||
class SearchQuery_Range {
|
||||
|
||||
class SearchQuery_Range
|
||||
{
|
||||
public $start = null;
|
||||
public $end = null;
|
||||
|
||||
function __construct($start = null, $end = null) {
|
||||
public function __construct($start = null, $end = null)
|
||||
{
|
||||
$this->start = $start;
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
function start($start) {
|
||||
public function start($start)
|
||||
{
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
function end($end) {
|
||||
public function end($end)
|
||||
{
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
function isfiltered() {
|
||||
public function isfiltered()
|
||||
{
|
||||
return $this->start !== null || $this->end !== null;
|
||||
}
|
||||
}
|
@ -12,16 +12,19 @@
|
||||
*
|
||||
* TODO: The way we bind in is awful hacky.
|
||||
*/
|
||||
class SearchUpdater extends Object {
|
||||
|
||||
class SearchUpdater extends Object
|
||||
{
|
||||
/**
|
||||
* Replace the database object with a subclass that captures all manipulations and passes them to us
|
||||
*/
|
||||
static function bind_manipulation_capture() {
|
||||
public static function bind_manipulation_capture()
|
||||
{
|
||||
global $databaseConfig;
|
||||
|
||||
$current = DB::getConn();
|
||||
if (!$current || @$current->isManipulationCapture) return; // If not yet set, or its already captured, just return
|
||||
if (!$current || @$current->isManipulationCapture) {
|
||||
return;
|
||||
} // If not yet set, or its already captured, just return
|
||||
|
||||
$type = get_class($current);
|
||||
$file = TEMP_FOLDER."/.cache.SMC.$type";
|
||||
@ -58,9 +61,9 @@ class SearchUpdater extends Object {
|
||||
DB::setConn($captured);
|
||||
}
|
||||
|
||||
static $registered = false;
|
||||
public static $registered = false;
|
||||
/** @var SearchUpdateProcessor */
|
||||
static $processor = null;
|
||||
public static $processor = null;
|
||||
|
||||
/**
|
||||
* Called by the SearchManiplateCapture database adapter with every manipulation made against the database.
|
||||
@ -72,7 +75,8 @@ class SearchUpdater extends Object {
|
||||
* it expects the actual class of the object to be present as a table, regardless of if any fields changed in that table
|
||||
* (so a class => array( 'fields' => array() ) item), in order to find the actual class for a set of table manipulations
|
||||
*/
|
||||
static function handle_manipulation($manipulation) {
|
||||
public static function handle_manipulation($manipulation)
|
||||
{
|
||||
// First, extract any state that is in the manipulation itself
|
||||
foreach ($manipulation as $table => $details) {
|
||||
$manipulation[$table]['class'] = $table;
|
||||
@ -86,7 +90,9 @@ class SearchUpdater extends Object {
|
||||
$writes = array();
|
||||
|
||||
foreach ($manipulation as $table => $details) {
|
||||
if (!isset($details['id']) || !isset($details['fields'])) continue;
|
||||
if (!isset($details['id']) || !isset($details['fields'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$id = $details['id'];
|
||||
$state = $details['state'];
|
||||
@ -133,7 +139,8 @@ class SearchUpdater extends Object {
|
||||
*
|
||||
* @param array $writes
|
||||
*/
|
||||
public static function process_writes($writes) {
|
||||
public static function process_writes($writes)
|
||||
{
|
||||
foreach ($writes as $write) {
|
||||
// For every index
|
||||
foreach (FullTextSearch::get_indexes() as $index => $instance) {
|
||||
@ -170,7 +177,8 @@ class SearchUpdater extends Object {
|
||||
/**
|
||||
* Throw away the recorded dirty IDs without doing anything with them.
|
||||
*/
|
||||
static function clear_dirty_indexes() {
|
||||
public static function clear_dirty_indexes()
|
||||
{
|
||||
self::$processor = null;
|
||||
}
|
||||
|
||||
@ -179,19 +187,25 @@ class SearchUpdater extends Object {
|
||||
* either immediately update the indexes, queue a messsage to update the indexes at some point in the future, or
|
||||
* just throw the dirty IDs away.
|
||||
*/
|
||||
static function flush_dirty_indexes() {
|
||||
if (!self::$processor) return;
|
||||
public static function flush_dirty_indexes()
|
||||
{
|
||||
if (!self::$processor) {
|
||||
return;
|
||||
}
|
||||
self::$processor->triggerProcessing();
|
||||
self::$processor = null;
|
||||
}
|
||||
}
|
||||
|
||||
class SearchUpdater_BindManipulationCaptureFilter implements RequestFilter {
|
||||
public function preRequest(SS_HTTPRequest $request, Session $session, DataModel $model) {
|
||||
class SearchUpdater_BindManipulationCaptureFilter implements RequestFilter
|
||||
{
|
||||
public function preRequest(SS_HTTPRequest $request, Session $session, DataModel $model)
|
||||
{
|
||||
SearchUpdater::bind_manipulation_capture();
|
||||
}
|
||||
|
||||
public function postRequest(SS_HTTPRequest $request, SS_HTTPResponse $response, DataModel $model) {
|
||||
public function postRequest(SS_HTTPRequest $request, SS_HTTPResponse $response, DataModel $model)
|
||||
{
|
||||
/* NOP */
|
||||
}
|
||||
}
|
||||
@ -203,11 +217,14 @@ class SearchUpdater_BindManipulationCaptureFilter implements RequestFilter {
|
||||
* indexed. This causes the object to be marked for deletion from the index.
|
||||
*/
|
||||
|
||||
class SearchUpdater_ObjectHandler extends DataExtension {
|
||||
|
||||
public function onAfterDelete() {
|
||||
class SearchUpdater_ObjectHandler extends DataExtension
|
||||
{
|
||||
public function onAfterDelete()
|
||||
{
|
||||
// Calling delete() on empty objects does nothing
|
||||
if (!$this->owner->ID) return;
|
||||
if (!$this->owner->ID) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Force SearchUpdater to mark this record as dirty
|
||||
$manipulation = array(
|
||||
@ -224,7 +241,8 @@ class SearchUpdater_ObjectHandler extends DataExtension {
|
||||
/**
|
||||
* Forces this object to trigger a re-index in the current state
|
||||
*/
|
||||
public function triggerReindex() {
|
||||
public function triggerReindex()
|
||||
{
|
||||
if (!$this->owner->ID) {
|
||||
return;
|
||||
}
|
||||
@ -252,5 +270,4 @@ class SearchUpdater_ObjectHandler extends DataExtension {
|
||||
|
||||
SearchUpdater::process_writes($writes);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,9 +4,11 @@
|
||||
* A Search Variant handles decorators and other situations where the items to reindex or search through are modified
|
||||
* from the default state - for instance, dealing with Versioned or Subsite
|
||||
*/
|
||||
abstract class SearchVariant {
|
||||
|
||||
function __construct() {}
|
||||
abstract class SearchVariant
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/*** OVERRIDES start here */
|
||||
|
||||
@ -19,25 +21,25 @@ abstract class SearchVariant {
|
||||
* Return false if there is something missing from the environment (probably a
|
||||
* not installed module) that means this variant can't apply to any class
|
||||
*/
|
||||
abstract function appliesToEnvironment();
|
||||
abstract public function appliesToEnvironment();
|
||||
|
||||
/**
|
||||
* Return true if this variant applies to the passed class & subclass
|
||||
*/
|
||||
abstract function appliesTo($class, $includeSubclasses);
|
||||
abstract public function appliesTo($class, $includeSubclasses);
|
||||
|
||||
/**
|
||||
* Return the current state
|
||||
*/
|
||||
abstract function currentState();
|
||||
abstract public function currentState();
|
||||
/**
|
||||
* Return all states to step through to reindex all items
|
||||
*/
|
||||
abstract function reindexStates();
|
||||
abstract public function reindexStates();
|
||||
/**
|
||||
* Activate the passed state
|
||||
*/
|
||||
abstract function activateState($state);
|
||||
abstract public function activateState($state);
|
||||
|
||||
/**
|
||||
* Apply this variant to a search query
|
||||
@ -67,7 +69,8 @@ abstract class SearchVariant {
|
||||
* @param bool $includeSubclasses - True if variants should be included if they apply to at least one subclass of $class
|
||||
* @return array - An array of (string)$variantClassName => (Object)$variantInstance pairs
|
||||
*/
|
||||
public static function variants($class = null, $includeSubclasses = true) {
|
||||
public static function variants($class = null, $includeSubclasses = true)
|
||||
{
|
||||
if (!$class) {
|
||||
if (self::$variants === null) {
|
||||
$classes = ClassInfo::subclassesFor('SearchVariant');
|
||||
@ -77,7 +80,9 @@ abstract class SearchVariant {
|
||||
$ref = new ReflectionClass($variantclass);
|
||||
if ($ref->isInstantiable()) {
|
||||
$variant = singleton($variantclass);
|
||||
if ($variant->appliesToEnvironment()) $concrete[$variantclass] = $variant;
|
||||
if ($variant->appliesToEnvironment()) {
|
||||
$concrete[$variantclass] = $variant;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,15 +90,16 @@ abstract class SearchVariant {
|
||||
}
|
||||
|
||||
return self::$variants;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$key = $class . '!' . $includeSubclasses;
|
||||
|
||||
if (!isset(self::$class_variants[$key])) {
|
||||
self::$class_variants[$key] = array();
|
||||
|
||||
foreach (self::variants() as $variantclass => $instance) {
|
||||
if ($instance->appliesTo($class, $includeSubclasses)) self::$class_variants[$key][$variantclass] = $instance;
|
||||
if ($instance->appliesTo($class, $includeSubclasses)) {
|
||||
self::$class_variants[$key][$variantclass] = $instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,11 +125,14 @@ abstract class SearchVariant {
|
||||
*
|
||||
* @return An object with one method, call()
|
||||
*/
|
||||
static function with($class = null, $includeSubclasses = true) {
|
||||
public static function with($class = null, $includeSubclasses = true)
|
||||
{
|
||||
// Make the cache key
|
||||
$key = $class ? $class . '!' . $includeSubclasses : '!';
|
||||
// If no SearchVariant_Caller instance yet, create it
|
||||
if (!isset(self::$call_instances[$key])) self::$call_instances[$key] = new SearchVariant_Caller(self::variants($class, $includeSubclasses));
|
||||
if (!isset(self::$call_instances[$key])) {
|
||||
self::$call_instances[$key] = new SearchVariant_Caller(self::variants($class, $includeSubclasses));
|
||||
}
|
||||
// Then return it
|
||||
return self::$call_instances[$key];
|
||||
}
|
||||
@ -133,7 +142,8 @@ abstract class SearchVariant {
|
||||
*
|
||||
* SearchVariant::call(...) ==== SearchVariant::with()->call(...);
|
||||
*/
|
||||
static function call($method, &$a1=null, &$a2=null, &$a3=null, &$a4=null, &$a5=null, &$a6=null, &$a7=null) {
|
||||
public static function call($method, &$a1=null, &$a2=null, &$a3=null, &$a4=null, &$a5=null, &$a6=null, &$a7=null)
|
||||
{
|
||||
return self::with()->call($method, $a1, $a2, $a3, $a4, $a5, $a6, $a7);
|
||||
}
|
||||
|
||||
@ -142,7 +152,8 @@ abstract class SearchVariant {
|
||||
* @static
|
||||
* @return array
|
||||
*/
|
||||
static function current_state($class = null, $includeSubclasses = true) {
|
||||
public static function current_state($class = null, $includeSubclasses = true)
|
||||
{
|
||||
$state = array();
|
||||
foreach (self::variants($class, $includeSubclasses) as $variant => $instance) {
|
||||
$state[$variant] = $instance->currentState();
|
||||
@ -157,9 +168,12 @@ abstract class SearchVariant {
|
||||
* SearchVariant::current_state()
|
||||
* @return void
|
||||
*/
|
||||
static function activate_state($state) {
|
||||
public static function activate_state($state)
|
||||
{
|
||||
foreach (self::variants() as $variant => $instance) {
|
||||
if (isset($state[$variant])) $instance->activateState($state[$variant]);
|
||||
if (isset($state[$variant])) {
|
||||
$instance->activateState($state[$variant]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,11 +185,14 @@ abstract class SearchVariant {
|
||||
* @param bool $includeSubclasses - True if variants should be included if they apply to at least one subclass of $class
|
||||
* @return SearchVariant_ReindexStateIteratorRet - The iterator to foreach loop over
|
||||
*/
|
||||
static function reindex_states($class = null, $includeSubclasses = true) {
|
||||
public static function reindex_states($class = null, $includeSubclasses = true)
|
||||
{
|
||||
$allstates = array();
|
||||
|
||||
foreach (self::variants($class, $includeSubclasses) as $variant => $instance) {
|
||||
if ($states = $instance->reindexStates()) $allstates[$variant] = $states;
|
||||
if ($states = $instance->reindexStates()) {
|
||||
$allstates[$variant] = $states;
|
||||
}
|
||||
}
|
||||
|
||||
return $allstates ? new CombinationsArrayIterator($allstates) : array(array());
|
||||
@ -185,24 +202,28 @@ abstract class SearchVariant {
|
||||
/**
|
||||
* Internal utility class used to hold the state of the SearchVariant::with call
|
||||
*/
|
||||
class SearchVariant_Caller {
|
||||
class SearchVariant_Caller
|
||||
{
|
||||
protected $variants = null;
|
||||
|
||||
function __construct($variants) {
|
||||
public function __construct($variants)
|
||||
{
|
||||
$this->variants = $variants;
|
||||
}
|
||||
|
||||
function call($method, &$a1=null, &$a2=null, &$a3=null, &$a4=null, &$a5=null, &$a6=null, &$a7=null) {
|
||||
public function call($method, &$a1=null, &$a2=null, &$a3=null, &$a4=null, &$a5=null, &$a6=null, &$a7=null)
|
||||
{
|
||||
$values = array();
|
||||
|
||||
foreach ($this->variants as $variant) {
|
||||
if (method_exists($variant, $method)) {
|
||||
$value = $variant->$method($a1, $a2, $a3, $a4, $a5, $a6, $a7);
|
||||
if ($value !== null) $values[] = $value;
|
||||
if ($value !== null) {
|
||||
$values[] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,40 +1,47 @@
|
||||
<?php
|
||||
|
||||
class SearchVariantSiteTreeSubsitesPolyhome extends SearchVariant {
|
||||
|
||||
function appliesToEnvironment() {
|
||||
class SearchVariantSiteTreeSubsitesPolyhome extends SearchVariant
|
||||
{
|
||||
public function appliesToEnvironment()
|
||||
{
|
||||
return class_exists('Subsite') && class_exists('SubsitePolyhome');
|
||||
}
|
||||
|
||||
function appliesTo($class, $includeSubclasses) {
|
||||
public function appliesTo($class, $includeSubclasses)
|
||||
{
|
||||
return SearchIntrospection::has_extension($class, 'SiteTreeSubsitesPolyhome', $includeSubclasses);
|
||||
}
|
||||
|
||||
function currentState() {
|
||||
public function currentState()
|
||||
{
|
||||
return Subsite::currentSubsiteID();
|
||||
}
|
||||
function reindexStates() {
|
||||
public function reindexStates()
|
||||
{
|
||||
static $ids = null;
|
||||
|
||||
if ($ids === null) {
|
||||
$ids = array(0);
|
||||
foreach (DataObject::get('Subsite') as $subsite) $ids[] = $subsite->ID;
|
||||
foreach (DataObject::get('Subsite') as $subsite) {
|
||||
$ids[] = $subsite->ID;
|
||||
}
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
function activateState($state) {
|
||||
public function activateState($state)
|
||||
{
|
||||
if (Controller::has_curr()) {
|
||||
Subsite::changeSubsite($state);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// TODO: This is a nasty hack - calling Subsite::changeSubsite after request ends
|
||||
// throws error because no current controller to access session on
|
||||
$_REQUEST['SubsiteID'] = $state;
|
||||
}
|
||||
}
|
||||
|
||||
function alterDefinition($base, $index) {
|
||||
public function alterDefinition($base, $index)
|
||||
{
|
||||
$self = get_class($this);
|
||||
|
||||
$index->filterFields['_subsite'] = array(
|
||||
@ -48,22 +55,26 @@ class SearchVariantSiteTreeSubsitesPolyhome extends SearchVariant {
|
||||
);
|
||||
}
|
||||
|
||||
public function alterQuery($query, $index) {
|
||||
public function alterQuery($query, $index)
|
||||
{
|
||||
$subsite = Subsite::currentSubsiteID();
|
||||
$query->filter('_subsite', array($subsite, SearchQuery::$missing));
|
||||
}
|
||||
|
||||
static $subsites = null;
|
||||
public static $subsites = null;
|
||||
|
||||
/**
|
||||
* We need _really_ complicated logic to find just the changed subsites (because we use versions there's no explicit
|
||||
* deletes, just new versions with different members) so just always use all of them
|
||||
*/
|
||||
function extractManipulationWriteState(&$writes) {
|
||||
public function extractManipulationWriteState(&$writes)
|
||||
{
|
||||
$self = get_class($this);
|
||||
|
||||
foreach ($writes as $key => $write) {
|
||||
if (!$this->appliesTo($write['class'], true)) continue;
|
||||
if (!$this->appliesTo($write['class'], true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (self::$subsites === null) {
|
||||
$query = new SQLQuery('ID', 'Subsite');
|
||||
@ -81,5 +92,4 @@ class SearchVariantSiteTreeSubsitesPolyhome extends SearchVariant {
|
||||
$writes[$key]['statefulids'] = $next;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,12 +1,14 @@
|
||||
<?php
|
||||
|
||||
class SearchVariantSubsites extends SearchVariant {
|
||||
|
||||
function appliesToEnvironment() {
|
||||
class SearchVariantSubsites extends SearchVariant
|
||||
{
|
||||
public function appliesToEnvironment()
|
||||
{
|
||||
return class_exists('Subsite');
|
||||
}
|
||||
|
||||
function appliesTo($class, $includeSubclasses) {
|
||||
public function appliesTo($class, $includeSubclasses)
|
||||
{
|
||||
// Include all DataExtensions that contain a SubsiteID.
|
||||
// TODO: refactor subsites to inherit a common interface, so we can run introspection once only.
|
||||
return SearchIntrospection::has_extension($class, 'SiteTreeSubsites', $includeSubclasses) ||
|
||||
@ -15,29 +17,35 @@ class SearchVariantSubsites extends SearchVariant {
|
||||
SearchIntrospection::has_extension($class, 'SiteConfigSubsites', $includeSubclasses);
|
||||
}
|
||||
|
||||
function currentState() {
|
||||
public function currentState()
|
||||
{
|
||||
return (string)Subsite::currentSubsiteID();
|
||||
}
|
||||
|
||||
function reindexStates() {
|
||||
public function reindexStates()
|
||||
{
|
||||
static $ids = null;
|
||||
|
||||
if ($ids === null) {
|
||||
$ids = array('0');
|
||||
foreach (DataObject::get('Subsite') as $subsite) $ids[] = (string)$subsite->ID;
|
||||
foreach (DataObject::get('Subsite') as $subsite) {
|
||||
$ids[] = (string)$subsite->ID;
|
||||
}
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
function activateState($state) {
|
||||
public function activateState($state)
|
||||
{
|
||||
// We always just set the $_GET variable rather than store in Session - this always works, has highest priority
|
||||
// in Subsite::currentSubsiteID() and doesn't persist unlike Subsite::changeSubsite
|
||||
$_GET['SubsiteID'] = $state;
|
||||
Permission::flush_permission_cache();
|
||||
}
|
||||
|
||||
function alterDefinition($base, $index) {
|
||||
public function alterDefinition($base, $index)
|
||||
{
|
||||
$self = get_class($this);
|
||||
|
||||
$index->filterFields['_subsite'] = array(
|
||||
@ -51,22 +59,26 @@ class SearchVariantSubsites extends SearchVariant {
|
||||
);
|
||||
}
|
||||
|
||||
function alterQuery($query, $index) {
|
||||
public function alterQuery($query, $index)
|
||||
{
|
||||
$subsite = Subsite::currentSubsiteID();
|
||||
$query->filter('_subsite', array($subsite, SearchQuery::$missing));
|
||||
}
|
||||
|
||||
static $subsites = null;
|
||||
public static $subsites = null;
|
||||
|
||||
/**
|
||||
* We need _really_ complicated logic to find just the changed subsites (because we use versions there's no explicit
|
||||
* deletes, just new versions with different members) so just always use all of them
|
||||
*/
|
||||
function extractManipulationWriteState(&$writes) {
|
||||
public function extractManipulationWriteState(&$writes)
|
||||
{
|
||||
$self = get_class($this);
|
||||
|
||||
foreach ($writes as $key => $write) {
|
||||
if (!$this->appliesTo($write['class'], true)) continue;
|
||||
if (!$this->appliesTo($write['class'], true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (self::$subsites === null) {
|
||||
$query = new SQLQuery('"ID"', '"Subsite"');
|
||||
@ -84,5 +96,4 @@ class SearchVariantSubsites extends SearchVariant {
|
||||
$writes[$key]['statefulids'] = $next;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,20 +1,32 @@
|
||||
<?php
|
||||
|
||||
class SearchVariantVersioned extends SearchVariant {
|
||||
|
||||
function appliesToEnvironment() {
|
||||
class SearchVariantVersioned extends SearchVariant
|
||||
{
|
||||
public function appliesToEnvironment()
|
||||
{
|
||||
return class_exists('Versioned');
|
||||
}
|
||||
|
||||
function appliesTo($class, $includeSubclasses) {
|
||||
public function appliesTo($class, $includeSubclasses)
|
||||
{
|
||||
return SearchIntrospection::has_extension($class, 'Versioned', $includeSubclasses);
|
||||
}
|
||||
|
||||
function currentState() { return Versioned::current_stage(); }
|
||||
function reindexStates() { return array('Stage', 'Live'); }
|
||||
function activateState($state) { Versioned::reading_stage($state); }
|
||||
public function currentState()
|
||||
{
|
||||
return Versioned::current_stage();
|
||||
}
|
||||
public function reindexStates()
|
||||
{
|
||||
return array('Stage', 'Live');
|
||||
}
|
||||
public function activateState($state)
|
||||
{
|
||||
Versioned::reading_stage($state);
|
||||
}
|
||||
|
||||
function alterDefinition($base, $index) {
|
||||
public function alterDefinition($base, $index)
|
||||
{
|
||||
$self = get_class($this);
|
||||
|
||||
$index->filterFields['_versionedstage'] = array(
|
||||
@ -28,12 +40,14 @@ class SearchVariantVersioned extends SearchVariant {
|
||||
);
|
||||
}
|
||||
|
||||
public function alterQuery($query, $index) {
|
||||
public function alterQuery($query, $index)
|
||||
{
|
||||
$stage = Versioned::current_stage();
|
||||
$query->filter('_versionedstage', array($stage, SearchQuery::$missing));
|
||||
}
|
||||
|
||||
function extractManipulationState(&$manipulation) {
|
||||
public function extractManipulationState(&$manipulation)
|
||||
{
|
||||
$self = get_class($this);
|
||||
|
||||
foreach ($manipulation as $table => $details) {
|
||||
@ -52,7 +66,8 @@ class SearchVariantVersioned extends SearchVariant {
|
||||
}
|
||||
}
|
||||
|
||||
function extractStates(&$table, &$ids, &$fields) {
|
||||
public function extractStates(&$table, &$ids, &$fields)
|
||||
{
|
||||
$class = $table;
|
||||
$suffix = null;
|
||||
|
||||
@ -66,5 +81,4 @@ class SearchVariantVersioned extends SearchVariant {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,8 +3,8 @@
|
||||
/**
|
||||
* Provides batching of search updates
|
||||
*/
|
||||
abstract class SearchUpdateBatchedProcessor extends SearchUpdateProcessor {
|
||||
|
||||
abstract class SearchUpdateBatchedProcessor extends SearchUpdateProcessor
|
||||
{
|
||||
/**
|
||||
* List of batches to be processed
|
||||
*
|
||||
@ -45,7 +45,8 @@ abstract class SearchUpdateBatchedProcessor extends SearchUpdateProcessor {
|
||||
*/
|
||||
private static $batch_soft_cap = 10;
|
||||
|
||||
public function __construct() {
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->batches = array();
|
||||
@ -57,11 +58,13 @@ abstract class SearchUpdateBatchedProcessor extends SearchUpdateProcessor {
|
||||
*
|
||||
* @param int $batch Index of the batch
|
||||
*/
|
||||
protected function setBatch($batch) {
|
||||
protected function setBatch($batch)
|
||||
{
|
||||
$this->currentBatch = $batch;
|
||||
}
|
||||
|
||||
protected function getSource() {
|
||||
protected function getSource()
|
||||
{
|
||||
if (isset($this->batches[$this->currentBatch])) {
|
||||
return $this->batches[$this->currentBatch];
|
||||
}
|
||||
@ -72,12 +75,17 @@ abstract class SearchUpdateBatchedProcessor extends SearchUpdateProcessor {
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function process() {
|
||||
public function process()
|
||||
{
|
||||
// Skip blank queues
|
||||
if(empty($this->batches)) return true;
|
||||
if (empty($this->batches)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Don't re-process completed queue
|
||||
if($this->currentBatch >= count($this->batches)) return true;
|
||||
if ($this->currentBatch >= count($this->batches)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Send current patch to indexes
|
||||
$this->prepareIndexes();
|
||||
@ -93,10 +101,13 @@ abstract class SearchUpdateBatchedProcessor extends SearchUpdateProcessor {
|
||||
* @param array $source Source input
|
||||
* @return array Batches
|
||||
*/
|
||||
protected function segmentBatches($source) {
|
||||
protected function segmentBatches($source)
|
||||
{
|
||||
// Measure batch_size
|
||||
$batchSize = Config::inst()->get(get_class(), 'batch_size');
|
||||
if($batchSize === 0) return array($source);
|
||||
if ($batchSize === 0) {
|
||||
return array($source);
|
||||
}
|
||||
$softCap = Config::inst()->get(get_class(), 'batch_soft_cap');
|
||||
|
||||
// Clear batches
|
||||
@ -106,18 +117,24 @@ abstract class SearchUpdateBatchedProcessor extends SearchUpdateProcessor {
|
||||
|
||||
// Build batches from data
|
||||
foreach ($source as $base => $statefulids) {
|
||||
if (!$statefulids) continue;
|
||||
if (!$statefulids) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($statefulids as $stateKey => $statefulid) {
|
||||
$state = $statefulid['state'];
|
||||
$ids = $statefulid['ids'];
|
||||
if(!$ids) continue;
|
||||
if (!$ids) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract items from $ids until empty
|
||||
while ($ids) {
|
||||
// Estimate maximum number of items to take for this iteration, allowing for the soft cap
|
||||
$take = $batchSize - $currentSize;
|
||||
if(count($ids) <= $take + $softCap) $take += $softCap;
|
||||
if (count($ids) <= $take + $softCap) {
|
||||
$take += $softCap;
|
||||
}
|
||||
$items = array_slice($ids, 0, $take, true);
|
||||
$ids = array_slice($ids, count($items), null, true);
|
||||
|
||||
@ -141,17 +158,21 @@ abstract class SearchUpdateBatchedProcessor extends SearchUpdateProcessor {
|
||||
}
|
||||
}
|
||||
// Add incomplete batch
|
||||
if($currentSize) $batches[] = $current;
|
||||
if ($currentSize) {
|
||||
$batches[] = $current;
|
||||
}
|
||||
|
||||
return $batches;
|
||||
}
|
||||
|
||||
public function batchData() {
|
||||
public function batchData()
|
||||
{
|
||||
$this->batches = $this->segmentBatches($this->dirty);
|
||||
$this->setBatch(0);
|
||||
}
|
||||
|
||||
public function triggerProcessing() {
|
||||
public function triggerProcessing()
|
||||
{
|
||||
$this->batchData();
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
<?php
|
||||
|
||||
if(!interface_exists('QueuedJob')) return;
|
||||
|
||||
class SearchUpdateCommitJobProcessor implements QueuedJob {
|
||||
if (!interface_exists('QueuedJob')) {
|
||||
return;
|
||||
}
|
||||
|
||||
class SearchUpdateCommitJobProcessor implements QueuedJob
|
||||
{
|
||||
/**
|
||||
* The QueuedJob queue to use when processing commits
|
||||
*
|
||||
@ -76,7 +78,8 @@ class SearchUpdateCommitJobProcessor implements QueuedJob {
|
||||
* @param string $startAfter Start date
|
||||
* @return int The ID of the next queuedjob to run. This could be a new one or an existing one.
|
||||
*/
|
||||
public static function queue($dirty = true, $startAfter = null) {
|
||||
public static function queue($dirty = true, $startAfter = null)
|
||||
{
|
||||
$commit = Injector::inst()->create(__CLASS__);
|
||||
$id = singleton('QueuedJobService')->queueJob($commit, $startAfter);
|
||||
|
||||
@ -87,17 +90,20 @@ class SearchUpdateCommitJobProcessor implements QueuedJob {
|
||||
return $id;
|
||||
}
|
||||
|
||||
public function getJobType() {
|
||||
public function getJobType()
|
||||
{
|
||||
return Config::inst()->get(__CLASS__, 'commit_queue');
|
||||
}
|
||||
|
||||
public function getSignature() {
|
||||
public function getSignature()
|
||||
{
|
||||
// There is only ever one commit job on the queue so the signature is consistent
|
||||
// See QueuedJobService::queueJob() for the code that prevents duplication
|
||||
return __CLASS__;
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
public function getTitle()
|
||||
{
|
||||
return "FullTextSearch Commit Job";
|
||||
}
|
||||
|
||||
@ -106,7 +112,8 @@ class SearchUpdateCommitJobProcessor implements QueuedJob {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAllIndexes() {
|
||||
public function getAllIndexes()
|
||||
{
|
||||
if (empty($this->indexes)) {
|
||||
$indexes = FullTextSearch::get_indexes();
|
||||
$this->indexes = array_keys($indexes);
|
||||
@ -114,24 +121,28 @@ class SearchUpdateCommitJobProcessor implements QueuedJob {
|
||||
return $this->indexes;
|
||||
}
|
||||
|
||||
public function jobFinished() {
|
||||
public function jobFinished()
|
||||
{
|
||||
// If we've indexed exactly as many as we would like, we are done
|
||||
return $this->skipped
|
||||
|| (count($this->getAllIndexes()) <= count($this->completed));
|
||||
}
|
||||
|
||||
public function prepareForRestart() {
|
||||
public function prepareForRestart()
|
||||
{
|
||||
// NOOP
|
||||
}
|
||||
|
||||
public function afterComplete() {
|
||||
public function afterComplete()
|
||||
{
|
||||
// NOOP
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort this job, potentially rescheduling a replacement if there is still work to do
|
||||
*/
|
||||
protected function discardJob() {
|
||||
protected function discardJob()
|
||||
{
|
||||
$this->skipped = true;
|
||||
|
||||
// If we do not have dirty records, then assume that these dirty records were committed
|
||||
@ -158,7 +169,8 @@ class SearchUpdateCommitJobProcessor implements QueuedJob {
|
||||
static::queue(false, $runat);
|
||||
}
|
||||
|
||||
public function process() {
|
||||
public function process()
|
||||
{
|
||||
// If we have already run an instance of SearchUpdateCommitJobProcessor this request, immediately
|
||||
// quit this job to prevent hitting warming search limits in Solr
|
||||
if (static::$has_run) {
|
||||
@ -187,7 +199,8 @@ class SearchUpdateCommitJobProcessor implements QueuedJob {
|
||||
* @param SolrIndex $index
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function commitIndex($index) {
|
||||
protected function commitIndex($index)
|
||||
{
|
||||
// Skip index if this is already complete
|
||||
$name = get_class($index);
|
||||
if (in_array($name, $this->completed)) {
|
||||
@ -209,11 +222,13 @@ class SearchUpdateCommitJobProcessor implements QueuedJob {
|
||||
$this->completed[] = $name;
|
||||
}
|
||||
|
||||
public function setup() {
|
||||
public function setup()
|
||||
{
|
||||
// NOOP
|
||||
}
|
||||
|
||||
public function getJobData() {
|
||||
public function getJobData()
|
||||
{
|
||||
$data = new stdClass();
|
||||
$data->totalSteps = count($this->getAllIndexes());
|
||||
$data->currentStep = count($this->completed);
|
||||
@ -228,7 +243,8 @@ class SearchUpdateCommitJobProcessor implements QueuedJob {
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function setJobData($totalSteps, $currentStep, $isComplete, $jobData, $messages) {
|
||||
public function setJobData($totalSteps, $currentStep, $isComplete, $jobData, $messages)
|
||||
{
|
||||
$this->isComplete = $isComplete;
|
||||
$this->messages = $messages;
|
||||
|
||||
@ -237,13 +253,14 @@ class SearchUpdateCommitJobProcessor implements QueuedJob {
|
||||
$this->indexes = $jobData->indexes;
|
||||
}
|
||||
|
||||
public function addMessage($message, $severity='INFO') {
|
||||
public function addMessage($message, $severity='INFO')
|
||||
{
|
||||
$severity = strtoupper($severity);
|
||||
$this->messages[] = '[' . date('Y-m-d H:i:s') . "][$severity] $message";
|
||||
}
|
||||
|
||||
public function getMessages() {
|
||||
public function getMessages()
|
||||
{
|
||||
return $this->messages;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
<?php
|
||||
|
||||
class SearchUpdateImmediateProcessor extends SearchUpdateProcessor {
|
||||
public function triggerProcessing() {
|
||||
class SearchUpdateImmediateProcessor extends SearchUpdateProcessor
|
||||
{
|
||||
public function triggerProcessing()
|
||||
{
|
||||
$this->process();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
class SearchUpdateMessageQueueProcessor extends SearchUpdateProcessor {
|
||||
class SearchUpdateMessageQueueProcessor extends SearchUpdateProcessor
|
||||
{
|
||||
/**
|
||||
* The MessageQueue to use when processing updates
|
||||
* @config
|
||||
@ -8,7 +9,8 @@ class SearchUpdateMessageQueueProcessor extends SearchUpdateProcessor {
|
||||
*/
|
||||
private static $reindex_queue = "search_indexing";
|
||||
|
||||
public function triggerProcessing() {
|
||||
public function triggerProcessing()
|
||||
{
|
||||
MessageQueue::send(
|
||||
Config::inst()->get('SearchMessageQueueUpdater', 'reindex_queue'),
|
||||
new MethodInvocationMessage($this, "process")
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
abstract class SearchUpdateProcessor {
|
||||
|
||||
abstract class SearchUpdateProcessor
|
||||
{
|
||||
/**
|
||||
* List of dirty records to process in format
|
||||
*
|
||||
@ -26,25 +26,26 @@ abstract class SearchUpdateProcessor {
|
||||
*/
|
||||
protected $dirty;
|
||||
|
||||
public function __construct() {
|
||||
public function __construct()
|
||||
{
|
||||
$this->dirty = array();
|
||||
}
|
||||
|
||||
public function addDirtyIDs($class, $statefulids, $index) {
|
||||
public function addDirtyIDs($class, $statefulids, $index)
|
||||
{
|
||||
$base = ClassInfo::baseDataClass($class);
|
||||
$forclass = isset($this->dirty[$base]) ? $this->dirty[$base] : array();
|
||||
|
||||
foreach ($statefulids as $statefulid) {
|
||||
$id = $statefulid['id'];
|
||||
$state = $statefulid['state']; $statekey = serialize($state);
|
||||
$state = $statefulid['state'];
|
||||
$statekey = serialize($state);
|
||||
|
||||
if (!isset($forclass[$statekey])) {
|
||||
$forclass[$statekey] = array('state' => $state, 'ids' => array($id => array($index)));
|
||||
}
|
||||
else if (!isset($forclass[$statekey]['ids'][$id])) {
|
||||
} elseif (!isset($forclass[$statekey]['ids'][$id])) {
|
||||
$forclass[$statekey]['ids'][$id] = array($index);
|
||||
}
|
||||
else if (array_search($index, $forclass[$statekey]['ids'][$id]) === false) {
|
||||
} elseif (array_search($index, $forclass[$statekey]['ids'][$id]) === false) {
|
||||
$forclass[$statekey]['ids'][$id][] = $index;
|
||||
// dirty count stays the same
|
||||
}
|
||||
@ -58,13 +59,16 @@ abstract class SearchUpdateProcessor {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function prepareIndexes() {
|
||||
protected function prepareIndexes()
|
||||
{
|
||||
$originalState = SearchVariant::current_state();
|
||||
$dirtyIndexes = array();
|
||||
$dirty = $this->getSource();
|
||||
$indexes = FullTextSearch::get_indexes();
|
||||
foreach ($dirty as $base => $statefulids) {
|
||||
if (!$statefulids) continue;
|
||||
if (!$statefulids) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($statefulids as $statefulid) {
|
||||
$state = $statefulid['state'];
|
||||
@ -106,7 +110,8 @@ abstract class SearchUpdateProcessor {
|
||||
* @param SolrIndex $index Index object
|
||||
* @return bool Flag indicating success
|
||||
*/
|
||||
protected function commitIndex($index) {
|
||||
protected function commitIndex($index)
|
||||
{
|
||||
return $index->commit() !== false;
|
||||
}
|
||||
|
||||
@ -115,7 +120,8 @@ abstract class SearchUpdateProcessor {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getSource() {
|
||||
protected function getSource()
|
||||
{
|
||||
return $this->dirty;
|
||||
}
|
||||
|
||||
@ -124,11 +130,14 @@ abstract class SearchUpdateProcessor {
|
||||
*
|
||||
* @return bool Flag indicating success
|
||||
*/
|
||||
public function process() {
|
||||
public function process()
|
||||
{
|
||||
// Generate and commit all instances
|
||||
$indexes = $this->prepareIndexes();
|
||||
foreach ($indexes as $index) {
|
||||
if(!$this->commitIndex($index)) return false;
|
||||
if (!$this->commitIndex($index)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
<?php
|
||||
|
||||
if(!interface_exists('QueuedJob')) return;
|
||||
|
||||
class SearchUpdateQueuedJobProcessor extends SearchUpdateBatchedProcessor implements QueuedJob {
|
||||
if (!interface_exists('QueuedJob')) {
|
||||
return;
|
||||
}
|
||||
|
||||
class SearchUpdateQueuedJobProcessor extends SearchUpdateBatchedProcessor implements QueuedJob
|
||||
{
|
||||
/**
|
||||
* The QueuedJob queue to use when processing updates
|
||||
* @config
|
||||
@ -13,42 +15,51 @@ class SearchUpdateQueuedJobProcessor extends SearchUpdateBatchedProcessor implem
|
||||
|
||||
protected $messages = array();
|
||||
|
||||
public function triggerProcessing() {
|
||||
public function triggerProcessing()
|
||||
{
|
||||
parent::triggerProcessing();
|
||||
singleton('QueuedJobService')->queueJob($this);
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
public function getTitle()
|
||||
{
|
||||
return "FullTextSearch Update Job";
|
||||
}
|
||||
|
||||
public function getSignature() {
|
||||
public function getSignature()
|
||||
{
|
||||
return md5(get_class($this) . time() . mt_rand(0, 100000));
|
||||
}
|
||||
|
||||
public function getJobType() {
|
||||
public function getJobType()
|
||||
{
|
||||
return Config::inst()->get('SearchUpdateQueuedJobProcessor', 'reindex_queue');
|
||||
}
|
||||
|
||||
public function jobFinished() {
|
||||
public function jobFinished()
|
||||
{
|
||||
return $this->currentBatch >= count($this->batches);
|
||||
}
|
||||
|
||||
public function setup() {
|
||||
public function setup()
|
||||
{
|
||||
// NOP
|
||||
}
|
||||
|
||||
public function prepareForRestart() {
|
||||
public function prepareForRestart()
|
||||
{
|
||||
// NOP
|
||||
}
|
||||
|
||||
public function afterComplete() {
|
||||
public function afterComplete()
|
||||
{
|
||||
// Once indexing is complete, commit later in order to avoid solr limits
|
||||
// see http://stackoverflow.com/questions/7512945/how-to-fix-exceeded-limit-of-maxwarmingsearchers
|
||||
SearchUpdateCommitJobProcessor::queue();
|
||||
}
|
||||
|
||||
public function getJobData() {
|
||||
public function getJobData()
|
||||
{
|
||||
$data = new stdClass();
|
||||
$data->totalSteps = count($this->batches);
|
||||
$data->currentStep = $this->currentBatch;
|
||||
@ -62,7 +73,8 @@ class SearchUpdateQueuedJobProcessor extends SearchUpdateBatchedProcessor implem
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function setJobData($totalSteps, $currentStep, $isComplete, $jobData, $messages) {
|
||||
public function setJobData($totalSteps, $currentStep, $isComplete, $jobData, $messages)
|
||||
{
|
||||
$this->isComplete = $isComplete;
|
||||
$this->messages = $messages;
|
||||
|
||||
@ -70,12 +82,14 @@ class SearchUpdateQueuedJobProcessor extends SearchUpdateBatchedProcessor implem
|
||||
$this->currentBatch = $jobData->currentBatch;
|
||||
}
|
||||
|
||||
public function addMessage($message, $severity='INFO') {
|
||||
public function addMessage($message, $severity='INFO')
|
||||
{
|
||||
$severity = strtoupper($severity);
|
||||
$this->messages[] = '[' . date('Y-m-d H:i:s') . "][$severity] $message";
|
||||
}
|
||||
|
||||
public function process() {
|
||||
public function process()
|
||||
{
|
||||
$result = parent::process();
|
||||
|
||||
if ($this->jobFinished()) {
|
||||
|
@ -5,8 +5,8 @@ use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Logger;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class Solr {
|
||||
|
||||
class Solr
|
||||
{
|
||||
/**
|
||||
* Configuration on where to find the solr server and how to get new index configurations into it.
|
||||
*
|
||||
@ -47,7 +47,8 @@ class Solr {
|
||||
* Update the configuration for Solr. See $solr_options for a discussion of the accepted array keys
|
||||
* @param array $options - The options to update
|
||||
*/
|
||||
static function configure_server($options = array()) {
|
||||
public static function configure_server($options = array())
|
||||
{
|
||||
self::$solr_options = array_merge(self::$solr_options, $options);
|
||||
self::$merged_solr_options = null;
|
||||
|
||||
@ -59,8 +60,11 @@ class Solr {
|
||||
* Get the configured Solr options with the defaults all merged in
|
||||
* @return array - The merged options
|
||||
*/
|
||||
static function solr_options() {
|
||||
if (self::$merged_solr_options) return self::$merged_solr_options;
|
||||
public static function solr_options()
|
||||
{
|
||||
if (self::$merged_solr_options) {
|
||||
return self::$merged_solr_options;
|
||||
}
|
||||
|
||||
$defaults = array(
|
||||
'host' => 'localhost',
|
||||
@ -78,8 +82,7 @@ class Solr {
|
||||
'extraspath' => Director::baseFolder().'/fulltextsearch/conf/solr/4/extras/',
|
||||
'templatespath' => Director::baseFolder().'/fulltextsearch/conf/solr/4/templates/',
|
||||
);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$versionDefaults = array(
|
||||
'service' => 'Solr3Service',
|
||||
'extraspath' => Director::baseFolder().'/fulltextsearch/conf/solr/3/extras/',
|
||||
@ -91,15 +94,16 @@ class Solr {
|
||||
}
|
||||
|
||||
|
||||
static function set_service_class($class) {
|
||||
public static function set_service_class($class)
|
||||
{
|
||||
user_error('set_service_class is deprecated - pass as part of $options to configure_server', E_USER_WARNING);
|
||||
self::configure_server(array('service' => $class));
|
||||
}
|
||||
|
||||
/** @var SolrService | null - The instance of SolrService for core management */
|
||||
static protected $service_singleton = null;
|
||||
protected static $service_singleton = null;
|
||||
/** @var [SolrService_Core] - The instances of SolrService_Core for each core */
|
||||
static protected $service_core_singletons = array();
|
||||
protected static $service_core_singletons = array();
|
||||
|
||||
/**
|
||||
* Get a SolrService
|
||||
@ -107,7 +111,8 @@ class Solr {
|
||||
* @param string $core Optional core name
|
||||
* @return SolrService_Core
|
||||
*/
|
||||
static function service($core = null) {
|
||||
public static function service($core = null)
|
||||
{
|
||||
$options = self::solr_options();
|
||||
|
||||
if (!self::$service_singleton) {
|
||||
@ -129,7 +134,8 @@ class Solr {
|
||||
}
|
||||
}
|
||||
|
||||
static function get_indexes() {
|
||||
public static function get_indexes()
|
||||
{
|
||||
return FullTextSearch::get_indexes('SolrIndex');
|
||||
}
|
||||
|
||||
@ -137,7 +143,8 @@ class Solr {
|
||||
* Include the thirdparty Solr client api library. Done this way to avoid issues where code is called in
|
||||
* mysite/_config before fulltextsearch/_config has a change to update the include path.
|
||||
*/
|
||||
static function include_client_api() {
|
||||
public static function include_client_api()
|
||||
{
|
||||
static $included = false;
|
||||
|
||||
if (!$included) {
|
||||
@ -153,8 +160,8 @@ class Solr {
|
||||
/**
|
||||
* Abstract class for build tasks
|
||||
*/
|
||||
class Solr_BuildTask extends BuildTask {
|
||||
|
||||
class Solr_BuildTask extends BuildTask
|
||||
{
|
||||
protected $enabled = false;
|
||||
|
||||
/**
|
||||
@ -169,7 +176,8 @@ class Solr_BuildTask extends BuildTask {
|
||||
*
|
||||
* @return LoggerInterface
|
||||
*/
|
||||
public function getLogger() {
|
||||
public function getLogger()
|
||||
{
|
||||
return $this->logger;
|
||||
}
|
||||
|
||||
@ -178,14 +186,16 @@ class Solr_BuildTask extends BuildTask {
|
||||
*
|
||||
* @param LoggerInterface $logger
|
||||
*/
|
||||
public function setLogger(LoggerInterface $logger) {
|
||||
public function setLogger(LoggerInterface $logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SearchLogFactory
|
||||
*/
|
||||
protected function getLoggerFactory() {
|
||||
protected function getLoggerFactory()
|
||||
{
|
||||
return Injector::inst()->get('SearchLogFactory');
|
||||
}
|
||||
|
||||
@ -194,7 +204,8 @@ class Solr_BuildTask extends BuildTask {
|
||||
*
|
||||
* @param SS_HTTPReqest $request
|
||||
*/
|
||||
public function run($request) {
|
||||
public function run($request)
|
||||
{
|
||||
$name = get_class($this);
|
||||
$verbose = $request->getVar('verbose');
|
||||
|
||||
@ -207,18 +218,18 @@ class Solr_BuildTask extends BuildTask {
|
||||
}
|
||||
|
||||
|
||||
class Solr_Configure extends Solr_BuildTask {
|
||||
|
||||
class Solr_Configure extends Solr_BuildTask
|
||||
{
|
||||
protected $enabled = true;
|
||||
|
||||
public function run($request) {
|
||||
public function run($request)
|
||||
{
|
||||
parent::run($request);
|
||||
|
||||
// Find the IndexStore handler, which will handle uploading config files to Solr
|
||||
$store = $this->getSolrConfigStore();
|
||||
$indexes = Solr::get_indexes();
|
||||
foreach ($indexes as $instance) {
|
||||
|
||||
try {
|
||||
$this->updateIndex($instance, $store);
|
||||
} catch (Exception $e) {
|
||||
@ -236,7 +247,8 @@ class Solr_Configure extends Solr_BuildTask {
|
||||
* @param SolrIndex $instance Instance
|
||||
* @param SolrConfigStore $store
|
||||
*/
|
||||
protected function updateIndex($instance, $store) {
|
||||
protected function updateIndex($instance, $store)
|
||||
{
|
||||
$index = $instance->getIndexName();
|
||||
$this->getLogger()->info("Configuring $index.");
|
||||
|
||||
@ -262,7 +274,8 @@ class Solr_Configure extends Solr_BuildTask {
|
||||
*
|
||||
* @return SolrConfigStore
|
||||
*/
|
||||
protected function getSolrConfigStore() {
|
||||
protected function getSolrConfigStore()
|
||||
{
|
||||
$options = Solr::solr_options();
|
||||
|
||||
if (!isset($options['indexstore']) || !($indexstore = $options['indexstore'])) {
|
||||
@ -281,7 +294,6 @@ class Solr_Configure extends Solr_BuildTask {
|
||||
} else {
|
||||
user_error('Unknown Solr index mode '.$indexstore['mode'], E_USER_ERROR);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -300,8 +312,8 @@ class Solr_Configure extends Solr_BuildTask {
|
||||
* - variantstate
|
||||
* - verbose (optional)
|
||||
*/
|
||||
class Solr_Reindex extends Solr_BuildTask {
|
||||
|
||||
class Solr_Reindex extends Solr_BuildTask
|
||||
{
|
||||
protected $enabled = true;
|
||||
|
||||
/**
|
||||
@ -317,14 +329,16 @@ class Solr_Reindex extends Solr_BuildTask {
|
||||
*
|
||||
* @return SolrReindexHandler
|
||||
*/
|
||||
protected function getHandler() {
|
||||
protected function getHandler()
|
||||
{
|
||||
return Injector::inst()->get('SolrReindexHandler');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SS_HTTPRequest $request
|
||||
*/
|
||||
public function run($request) {
|
||||
public function run($request)
|
||||
{
|
||||
parent::run($request);
|
||||
|
||||
// Reset state
|
||||
@ -336,7 +350,8 @@ class Solr_Reindex extends Solr_BuildTask {
|
||||
/**
|
||||
* @param SS_HTTPRequest $request
|
||||
*/
|
||||
protected function doReindex($request) {
|
||||
protected function doReindex($request)
|
||||
{
|
||||
$class = $request->getVar('class');
|
||||
|
||||
// Deprecated reindex mechanism
|
||||
@ -372,7 +387,8 @@ class Solr_Reindex extends Solr_BuildTask {
|
||||
/**
|
||||
* @deprecated since version 2.0.0
|
||||
*/
|
||||
protected function runFrom($index, $class, $start, $variantstate) {
|
||||
protected function runFrom($index, $class, $start, $variantstate)
|
||||
{
|
||||
DeprecationTest_Deprecation::notice('2.0.0', 'Solr_Reindex now uses a new grouping mechanism');
|
||||
|
||||
// Set time limit and state
|
||||
|
@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
class Solr3Service_Core extends SolrService_Core {
|
||||
class Solr3Service_Core extends SolrService_Core
|
||||
{
|
||||
}
|
||||
|
||||
class Solr3Service extends SolrService {
|
||||
class Solr3Service extends SolrService
|
||||
{
|
||||
private static $core_class = 'Solr3Service_Core';
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
<?php
|
||||
|
||||
class Solr4Service_Core extends SolrService_Core {
|
||||
|
||||
class Solr4Service_Core extends SolrService_Core
|
||||
{
|
||||
/**
|
||||
* Replace underlying commit function to remove waitFlush in 4.0+, since it's been deprecated and 4.4 throws errors
|
||||
* if you pass it
|
||||
*/
|
||||
public function commit($expungeDeletes = false, $waitFlush = null, $waitSearcher = true, $timeout = 3600) {
|
||||
public function commit($expungeDeletes = false, $waitFlush = null, $waitSearcher = true, $timeout = 3600)
|
||||
{
|
||||
if ($waitFlush) {
|
||||
user_error('waitFlush must be false when using Solr 4.0+' . E_USER_ERROR);
|
||||
}
|
||||
@ -51,7 +52,7 @@ class Solr4Service_Core extends SolrService_Core {
|
||||
}
|
||||
}
|
||||
|
||||
class Solr4Service extends SolrService {
|
||||
class Solr4Service extends SolrService
|
||||
{
|
||||
private static $core_class = 'Solr4Service_Core';
|
||||
}
|
||||
|
||||
|
@ -5,14 +5,15 @@
|
||||
*
|
||||
* The interface Solr_Configure uses to upload configuration files to Solr
|
||||
*/
|
||||
interface SolrConfigStore {
|
||||
interface SolrConfigStore
|
||||
{
|
||||
/**
|
||||
* Upload a file to Solr for index $index
|
||||
* @param $index string - The name of an index (which is also used as the name of the Solr core for the index)
|
||||
* @param $file string - A path to a file to upload. The base name of the file will be used on the remote side
|
||||
* @return null
|
||||
*/
|
||||
function uploadFile($index, $file);
|
||||
public function uploadFile($index, $file);
|
||||
|
||||
/**
|
||||
* Upload a file to Solr from a string for index $index
|
||||
@ -21,13 +22,13 @@ interface SolrConfigStore {
|
||||
* @param $strong string - The contents of the file
|
||||
* @return null
|
||||
*/
|
||||
function uploadString($index, $filename, $string);
|
||||
public function uploadString($index, $filename, $string);
|
||||
|
||||
/**
|
||||
* Get the instanceDir to tell Solr to use for index $index
|
||||
* @param $index string - The name of an index (which is also used as the name of the Solr core for the index)
|
||||
*/
|
||||
function instanceDir($index);
|
||||
public function instanceDir($index);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -36,13 +37,16 @@ interface SolrConfigStore {
|
||||
* A ConfigStore that uploads files to a Solr instance on a locally accessible filesystem
|
||||
* by just using file copies
|
||||
*/
|
||||
class SolrConfigStore_File implements SolrConfigStore {
|
||||
function __construct($config) {
|
||||
class SolrConfigStore_File implements SolrConfigStore
|
||||
{
|
||||
public function __construct($config)
|
||||
{
|
||||
$this->local = $config['path'];
|
||||
$this->remote = isset($config['remotepath']) ? $config['remotepath'] : $config['path'];
|
||||
}
|
||||
|
||||
function getTargetDir($index) {
|
||||
public function getTargetDir($index)
|
||||
{
|
||||
$targetDir = "{$this->local}/{$index}/conf";
|
||||
|
||||
if (!is_dir($targetDir)) {
|
||||
@ -58,17 +62,20 @@ class SolrConfigStore_File implements SolrConfigStore {
|
||||
return $targetDir;
|
||||
}
|
||||
|
||||
function uploadFile($index, $file) {
|
||||
public function uploadFile($index, $file)
|
||||
{
|
||||
$targetDir = $this->getTargetDir($index);
|
||||
copy($file, $targetDir.'/'.basename($file));
|
||||
}
|
||||
|
||||
function uploadString($index, $filename, $string) {
|
||||
public function uploadString($index, $filename, $string)
|
||||
{
|
||||
$targetDir = $this->getTargetDir($index);
|
||||
file_put_contents("$targetDir/$filename", $string);
|
||||
}
|
||||
|
||||
function instanceDir($index) {
|
||||
public function instanceDir($index)
|
||||
{
|
||||
return $this->remote.'/'.$index;
|
||||
}
|
||||
}
|
||||
@ -78,8 +85,10 @@ class SolrConfigStore_File implements SolrConfigStore {
|
||||
*
|
||||
* A ConfigStore that uploads files to a Solr instance via a WebDAV server
|
||||
*/
|
||||
class SolrConfigStore_WebDAV implements SolrConfigStore {
|
||||
function __construct($config) {
|
||||
class SolrConfigStore_WebDAV implements SolrConfigStore
|
||||
{
|
||||
public function __construct($config)
|
||||
{
|
||||
$options = Solr::solr_options();
|
||||
|
||||
$this->url = implode('', array(
|
||||
@ -91,27 +100,35 @@ class SolrConfigStore_WebDAV implements SolrConfigStore {
|
||||
$this->remote = $config['remotepath'];
|
||||
}
|
||||
|
||||
function getTargetDir($index) {
|
||||
public function getTargetDir($index)
|
||||
{
|
||||
$indexdir = "{$this->url}/$index";
|
||||
if (!WebDAV::exists($indexdir)) WebDAV::mkdir($indexdir);
|
||||
if (!WebDAV::exists($indexdir)) {
|
||||
WebDAV::mkdir($indexdir);
|
||||
}
|
||||
|
||||
$targetDir = "{$this->url}/$index/conf";
|
||||
if (!WebDAV::exists($targetDir)) WebDAV::mkdir($targetDir);
|
||||
if (!WebDAV::exists($targetDir)) {
|
||||
WebDAV::mkdir($targetDir);
|
||||
}
|
||||
|
||||
return $targetDir;
|
||||
}
|
||||
|
||||
function uploadFile($index, $file) {
|
||||
public function uploadFile($index, $file)
|
||||
{
|
||||
$targetDir = $this->getTargetDir($index);
|
||||
WebDAV::upload_from_file($file, $targetDir.'/'.basename($file));
|
||||
}
|
||||
|
||||
function uploadString($index, $filename, $string) {
|
||||
public function uploadString($index, $filename, $string)
|
||||
{
|
||||
$targetDir = $this->getTargetDir($index);
|
||||
WebDAV::upload_from_string($string, "$targetDir/$filename");
|
||||
}
|
||||
|
||||
function instanceDir($index) {
|
||||
public function instanceDir($index)
|
||||
{
|
||||
return $this->remote ? "{$this->remote}/$index" : $index;
|
||||
}
|
||||
}
|
||||
|
@ -2,15 +2,15 @@
|
||||
|
||||
Solr::include_client_api();
|
||||
|
||||
abstract class SolrIndex extends SearchIndex {
|
||||
|
||||
static $fulltextTypeMap = array(
|
||||
abstract class SolrIndex extends SearchIndex
|
||||
{
|
||||
public static $fulltextTypeMap = array(
|
||||
'*' => 'text',
|
||||
'HTMLVarchar' => 'htmltext',
|
||||
'HTMLText' => 'htmltext'
|
||||
);
|
||||
|
||||
static $filterTypeMap = array(
|
||||
public static $filterTypeMap = array(
|
||||
'*' => 'string',
|
||||
'Boolean' => 'boolean',
|
||||
'Date' => 'tdate',
|
||||
@ -22,7 +22,7 @@ abstract class SolrIndex extends SearchIndex {
|
||||
'Double' => 'tdouble'
|
||||
);
|
||||
|
||||
static $sortTypeMap = array();
|
||||
public static $sortTypeMap = array();
|
||||
|
||||
protected $analyzerFields = array();
|
||||
|
||||
@ -59,7 +59,8 @@ abstract class SolrIndex extends SearchIndex {
|
||||
* @return String Absolute path to the folder containing
|
||||
* templates which are used for generating the schema and field definitions.
|
||||
*/
|
||||
function getTemplatesPath() {
|
||||
public function getTemplatesPath()
|
||||
{
|
||||
$globalOptions = Solr::solr_options();
|
||||
return $this->templatesPath ? $this->templatesPath : $globalOptions['templatespath'];
|
||||
}
|
||||
@ -68,20 +69,24 @@ abstract class SolrIndex extends SearchIndex {
|
||||
* @return String Absolute path to the configuration default files,
|
||||
* e.g. solrconfig.xml.
|
||||
*/
|
||||
function getExtrasPath() {
|
||||
public function getExtrasPath()
|
||||
{
|
||||
$globalOptions = Solr::solr_options();
|
||||
return $this->extrasPath ? $this->extrasPath : $globalOptions['extraspath'];
|
||||
}
|
||||
|
||||
function generateSchema() {
|
||||
public function generateSchema()
|
||||
{
|
||||
return $this->renderWith($this->getTemplatesPath() . '/schema.ss');
|
||||
}
|
||||
|
||||
function getIndexName() {
|
||||
public function getIndexName()
|
||||
{
|
||||
return get_class($this);
|
||||
}
|
||||
|
||||
function getTypes() {
|
||||
public function getTypes()
|
||||
{
|
||||
return $this->renderWith($this->getTemplatesPath() . '/types.ss');
|
||||
}
|
||||
|
||||
@ -95,20 +100,26 @@ abstract class SolrIndex extends SearchIndex {
|
||||
* @param String $type
|
||||
* @param Array $params Parameters for the analyzer, usually at least a "class"
|
||||
*/
|
||||
function addAnalyzer($field, $type, $params) {
|
||||
public function addAnalyzer($field, $type, $params)
|
||||
{
|
||||
$fullFields = $this->fieldData($field);
|
||||
if($fullFields) foreach($fullFields as $fullField => $spec) {
|
||||
if(!isset($this->analyzerFields[$fullField])) $this->analyzerFields[$fullField] = array();
|
||||
if ($fullFields) {
|
||||
foreach ($fullFields as $fullField => $spec) {
|
||||
if (!isset($this->analyzerFields[$fullField])) {
|
||||
$this->analyzerFields[$fullField] = array();
|
||||
}
|
||||
$this->analyzerFields[$fullField][$type] = $params;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default text field, normally '_text'
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDefaultField() {
|
||||
public function getDefaultField()
|
||||
{
|
||||
return $this->config()->default_field;
|
||||
}
|
||||
|
||||
@ -118,7 +129,8 @@ abstract class SolrIndex extends SearchIndex {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getCopyDestinations() {
|
||||
protected function getCopyDestinations()
|
||||
{
|
||||
$copyFields = $this->config()->copy_fields;
|
||||
if ($copyFields) {
|
||||
return $copyFields;
|
||||
@ -128,7 +140,8 @@ abstract class SolrIndex extends SearchIndex {
|
||||
return array($df);
|
||||
}
|
||||
|
||||
public function getFieldDefinitions() {
|
||||
public function getFieldDefinitions()
|
||||
{
|
||||
$xml = array();
|
||||
$stored = $this->getStoredDefault();
|
||||
|
||||
@ -154,12 +167,16 @@ abstract class SolrIndex extends SearchIndex {
|
||||
}
|
||||
|
||||
foreach ($this->filterFields as $name => $field) {
|
||||
if ($field['fullfield'] == 'ID' || $field['fullfield'] == 'ClassName') continue;
|
||||
if ($field['fullfield'] == 'ID' || $field['fullfield'] == 'ClassName') {
|
||||
continue;
|
||||
}
|
||||
$xml[] = $this->getFieldDefinition($name, $field);
|
||||
}
|
||||
|
||||
foreach ($this->sortFields as $name => $field) {
|
||||
if ($field['fullfield'] == 'ID' || $field['fullfield'] == 'ClassName') continue;
|
||||
if ($field['fullfield'] == 'ID' || $field['fullfield'] == 'ClassName') {
|
||||
continue;
|
||||
}
|
||||
$xml[] = $this->getFieldDefinition($name, $field);
|
||||
}
|
||||
|
||||
@ -172,7 +189,8 @@ abstract class SolrIndex extends SearchIndex {
|
||||
* @param mixed $collation
|
||||
* @return string
|
||||
*/
|
||||
protected function getCollatedSuggestion($collation = '') {
|
||||
protected function getCollatedSuggestion($collation = '')
|
||||
{
|
||||
if (is_string($collation)) {
|
||||
return $collation;
|
||||
}
|
||||
@ -191,7 +209,8 @@ abstract class SolrIndex extends SearchIndex {
|
||||
* @param String $collation
|
||||
* @return String
|
||||
*/
|
||||
protected function getNiceSuggestion($collation = '') {
|
||||
protected function getNiceSuggestion($collation = '')
|
||||
{
|
||||
$collationParts = explode(' ', $collation);
|
||||
|
||||
// Remove advanced query params from the beginning of each collation part.
|
||||
@ -209,7 +228,8 @@ abstract class SolrIndex extends SearchIndex {
|
||||
* @param String $collation
|
||||
* @return String
|
||||
*/
|
||||
protected function getSuggestionQueryString($collation = '') {
|
||||
protected function getSuggestionQueryString($collation = '')
|
||||
{
|
||||
return str_replace(' ', '+', $this->getNiceSuggestion($collation));
|
||||
}
|
||||
|
||||
@ -221,7 +241,8 @@ abstract class SolrIndex extends SearchIndex {
|
||||
* detectable from metadata)
|
||||
* @param array $extraOptions Dependent on search implementation
|
||||
*/
|
||||
public function addStoredField($field, $forceType = null, $extraOptions = array()) {
|
||||
public function addStoredField($field, $forceType = null, $extraOptions = array())
|
||||
{
|
||||
$options = array_merge($extraOptions, array('stored' => 'true'));
|
||||
$this->addFulltextField($field, $forceType, $options);
|
||||
}
|
||||
@ -235,13 +256,15 @@ abstract class SolrIndex extends SearchIndex {
|
||||
* @param array $extraOptions Dependent on search implementation
|
||||
* @param float $boost Numeric boosting value (defaults to 2)
|
||||
*/
|
||||
public function addBoostedField($field, $forceType = null, $extraOptions = array(), $boost = 2) {
|
||||
public function addBoostedField($field, $forceType = null, $extraOptions = array(), $boost = 2)
|
||||
{
|
||||
$options = array_merge($extraOptions, array('boost' => $boost));
|
||||
$this->addFulltextField($field, $forceType, $options);
|
||||
}
|
||||
|
||||
|
||||
public function fieldData($field, $forceType = null, $extraOptions = array()) {
|
||||
public function fieldData($field, $forceType = null, $extraOptions = array())
|
||||
{
|
||||
// Ensure that 'boost' is recorded here without being captured by solr
|
||||
$boost = null;
|
||||
if (array_key_exists('boost', $extraOptions)) {
|
||||
@ -269,7 +292,8 @@ abstract class SolrIndex extends SearchIndex {
|
||||
* @param string $field Full field key (Model_Field)
|
||||
* @param float|null $level Numeric boosting value. Set to null to clear boost
|
||||
*/
|
||||
public function setFieldBoosting($field, $level) {
|
||||
public function setFieldBoosting($field, $level)
|
||||
{
|
||||
if (!isset($this->fulltextFields[$field])) {
|
||||
throw new InvalidArgumentException("No fulltext field $field exists on ".$this->getIndexName());
|
||||
}
|
||||
@ -285,7 +309,8 @@ abstract class SolrIndex extends SearchIndex {
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getBoostedFields() {
|
||||
public function getBoostedFields()
|
||||
{
|
||||
return $this->boostedFields;
|
||||
}
|
||||
|
||||
@ -294,7 +319,8 @@ abstract class SolrIndex extends SearchIndex {
|
||||
*
|
||||
* @return array|null List of query fields, or null if not specified
|
||||
*/
|
||||
public function getQueryFields() {
|
||||
public function getQueryFields()
|
||||
{
|
||||
// Not necessary to specify this unless boosting
|
||||
if (empty($this->boostedFields)) {
|
||||
return null;
|
||||
@ -318,7 +344,8 @@ abstract class SolrIndex extends SearchIndex {
|
||||
*
|
||||
* @return string A default value for the 'stored' field option, either 'true' or 'false'
|
||||
*/
|
||||
protected function getStoredDefault() {
|
||||
protected function getStoredDefault()
|
||||
{
|
||||
return Director::isDev() ? 'true' : 'false';
|
||||
}
|
||||
|
||||
@ -328,8 +355,11 @@ abstract class SolrIndex extends SearchIndex {
|
||||
* @param Array $typeMap
|
||||
* @return String XML
|
||||
*/
|
||||
protected function getFieldDefinition($name, $spec, $typeMap = null) {
|
||||
if(!$typeMap) $typeMap = self::$filterTypeMap;
|
||||
protected function getFieldDefinition($name, $spec, $typeMap = null)
|
||||
{
|
||||
if (!$typeMap) {
|
||||
$typeMap = self::$filterTypeMap;
|
||||
}
|
||||
$multiValued = (isset($spec['multi_valued']) && $spec['multi_valued']) ? "true" : '';
|
||||
$type = isset($typeMap[$spec['type']]) ? $typeMap[$spec['type']] : $typeMap['*'];
|
||||
|
||||
@ -366,11 +396,14 @@ abstract class SolrIndex extends SearchIndex {
|
||||
* @param String $content Inner content
|
||||
* @return String XML tag
|
||||
*/
|
||||
protected function toXmlTag($tag, $attrs, $content = null) {
|
||||
protected function toXmlTag($tag, $attrs, $content = null)
|
||||
{
|
||||
$xml = "<$tag ";
|
||||
if ($attrs) {
|
||||
$attrStrs = array();
|
||||
foreach($attrs as $attrName => $attrVal) $attrStrs[] = "$attrName='$attrVal'";
|
||||
foreach ($attrs as $attrName => $attrVal) {
|
||||
$attrStrs[] = "$attrName='$attrVal'";
|
||||
}
|
||||
$xml .= $attrStrs ? implode(' ', $attrStrs) : '';
|
||||
}
|
||||
$xml .= $content ? ">$content</$tag>" : '/>';
|
||||
@ -381,8 +414,11 @@ abstract class SolrIndex extends SearchIndex {
|
||||
* @param String $source Composite field name (<class>_<fieldname>)
|
||||
* @param String $dest
|
||||
*/
|
||||
function addCopyField($source, $dest, $extraOptions = array()) {
|
||||
if(!isset($this->copyFields[$source])) $this->copyFields[$source] = array();
|
||||
public function addCopyField($source, $dest, $extraOptions = array())
|
||||
{
|
||||
if (!isset($this->copyFields[$source])) {
|
||||
$this->copyFields[$source] = array();
|
||||
}
|
||||
$this->copyFields[$source][] = array_merge(
|
||||
array('source' => $source, 'dest' => $dest),
|
||||
$extraOptions
|
||||
@ -394,7 +430,8 @@ abstract class SolrIndex extends SearchIndex {
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCopyFieldDefinitions() {
|
||||
public function getCopyFieldDefinitions()
|
||||
{
|
||||
$xml = array();
|
||||
|
||||
// Default copy fields
|
||||
@ -414,42 +451,54 @@ abstract class SolrIndex extends SearchIndex {
|
||||
return implode("\n\t", $xml);
|
||||
}
|
||||
|
||||
protected function _addField($doc, $object, $field) {
|
||||
protected function _addField($doc, $object, $field)
|
||||
{
|
||||
$class = get_class($object);
|
||||
if ($class != $field['origin'] && !is_subclass_of($class, $field['origin'])) return;
|
||||
if ($class != $field['origin'] && !is_subclass_of($class, $field['origin'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$value = $this->_getFieldValue($object, $field);
|
||||
|
||||
$type = isset(self::$filterTypeMap[$field['type']]) ? self::$filterTypeMap[$field['type']] : self::$filterTypeMap['*'];
|
||||
|
||||
if (is_array($value)) foreach($value as $sub) {
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $sub) {
|
||||
/* Solr requires dates in the form 1995-12-31T23:59:59Z */
|
||||
if ($type == 'tdate') {
|
||||
if(!$sub) continue;
|
||||
if (!$sub) {
|
||||
continue;
|
||||
}
|
||||
$sub = gmdate('Y-m-d\TH:i:s\Z', strtotime($sub));
|
||||
}
|
||||
|
||||
/* Solr requires numbers to be valid if presented, not just empty */
|
||||
if (($type == 'tint' || $type == 'tfloat' || $type == 'tdouble') && !is_numeric($sub)) continue;
|
||||
if (($type == 'tint' || $type == 'tfloat' || $type == 'tdouble') && !is_numeric($sub)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$doc->addField($field['name'], $sub);
|
||||
}
|
||||
|
||||
else {
|
||||
} else {
|
||||
/* Solr requires dates in the form 1995-12-31T23:59:59Z */
|
||||
if ($type == 'tdate') {
|
||||
if(!$value) return;
|
||||
if (!$value) {
|
||||
return;
|
||||
}
|
||||
$value = gmdate('Y-m-d\TH:i:s\Z', strtotime($value));
|
||||
}
|
||||
|
||||
/* Solr requires numbers to be valid if presented, not just empty */
|
||||
if (($type == 'tint' || $type == 'tfloat' || $type == 'tdouble') && !is_numeric($value)) return;
|
||||
if (($type == 'tint' || $type == 'tfloat' || $type == 'tdouble') && !is_numeric($value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$doc->setField($field['name'], $value);
|
||||
}
|
||||
}
|
||||
|
||||
protected function _addAs($object, $base, $options) {
|
||||
protected function _addAs($object, $base, $options)
|
||||
{
|
||||
$includeSubs = $options['include_children'];
|
||||
|
||||
$doc = new Apache_Solr_Document();
|
||||
@ -460,12 +509,16 @@ abstract class SolrIndex extends SearchIndex {
|
||||
$doc->setField('ID', $object->ID);
|
||||
$doc->setField('ClassName', $object->ClassName);
|
||||
|
||||
foreach (SearchIntrospection::hierarchy(get_class($object), false) as $class) $doc->addField('ClassHierarchy', $class);
|
||||
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);
|
||||
if ($field['base'] == $base) {
|
||||
$this->_addField($doc, $object, $field);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@ -478,7 +531,8 @@ abstract class SolrIndex extends SearchIndex {
|
||||
return $doc;
|
||||
}
|
||||
|
||||
function add($object) {
|
||||
public function add($object)
|
||||
{
|
||||
$class = get_class($object);
|
||||
$docs = array();
|
||||
|
||||
@ -492,15 +546,19 @@ abstract class SolrIndex extends SearchIndex {
|
||||
return $docs;
|
||||
}
|
||||
|
||||
function canAdd($class) {
|
||||
public function canAdd($class)
|
||||
{
|
||||
foreach ($this->classes as $searchclass => $options) {
|
||||
if ($searchclass == $class || ($options['include_children'] && is_subclass_of($class, $searchclass))) return true;
|
||||
if ($searchclass == $class || ($options['include_children'] && is_subclass_of($class, $searchclass))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function delete($base, $id, $state) {
|
||||
public function delete($base, $id, $state)
|
||||
{
|
||||
$documentID = $this->getDocumentIDForState($base, $id, $state);
|
||||
|
||||
try {
|
||||
@ -521,7 +579,8 @@ abstract class SolrIndex extends SearchIndex {
|
||||
* @param array $classes List of non-obsolete classes in the same format as SolrIndex::getClasses()
|
||||
* @return bool Flag if successful
|
||||
*/
|
||||
public function clearObsoleteClasses($classes) {
|
||||
public function clearObsoleteClasses($classes)
|
||||
{
|
||||
if (empty($classes)) {
|
||||
return false;
|
||||
}
|
||||
@ -544,7 +603,8 @@ abstract class SolrIndex extends SearchIndex {
|
||||
return true;
|
||||
}
|
||||
|
||||
function commit() {
|
||||
public function commit()
|
||||
{
|
||||
try {
|
||||
$this->getService()->commit(false, false, false);
|
||||
} catch (Exception $e) {
|
||||
@ -561,7 +621,8 @@ abstract class SolrIndex extends SearchIndex {
|
||||
* @return ArrayData Map with the following keys:
|
||||
* - 'Matches': ArrayList of the matched object instances
|
||||
*/
|
||||
public function search(SearchQuery $query, $offset = -1, $limit = -1, $params = array()) {
|
||||
public function search(SearchQuery $query, $offset = -1, $limit = -1, $params = array())
|
||||
{
|
||||
$service = $this->getService();
|
||||
|
||||
$searchClass = count($query->classes) == 1
|
||||
@ -589,10 +650,13 @@ abstract class SolrIndex extends SearchIndex {
|
||||
foreach ($query->classes as $class) {
|
||||
if (!empty($class['includeSubclasses'])) {
|
||||
$classq[] = 'ClassHierarchy:'.$class['class'];
|
||||
} else {
|
||||
$classq[] = 'ClassName:'.$class['class'];
|
||||
}
|
||||
else $classq[] = 'ClassName:'.$class['class'];
|
||||
}
|
||||
if ($classq) $fq[] = '+('.implode(' ', $classq).')';
|
||||
if ($classq) {
|
||||
$fq[] = '+('.implode(' ', $classq).')';
|
||||
}
|
||||
|
||||
// Filter by filters
|
||||
$fq = array_merge($fq, $this->getFiltersComponent($query));
|
||||
@ -611,14 +675,26 @@ abstract class SolrIndex extends SearchIndex {
|
||||
}
|
||||
|
||||
if (!headers_sent() && !Director::isLive()) {
|
||||
if ($q) header('X-Query: '.implode(' ', $q));
|
||||
if ($fq) header('X-Filters: "'.implode('", "', $fq).'"');
|
||||
if ($qf) header('X-QueryFields: '.$qf);
|
||||
if ($q) {
|
||||
header('X-Query: '.implode(' ', $q));
|
||||
}
|
||||
if ($fq) {
|
||||
header('X-Filters: "'.implode('", "', $fq).'"');
|
||||
}
|
||||
if ($qf) {
|
||||
header('X-QueryFields: '.$qf);
|
||||
}
|
||||
}
|
||||
|
||||
if ($offset == -1) $offset = $query->start;
|
||||
if ($limit == -1) $limit = $query->limit;
|
||||
if ($limit == -1) $limit = SearchQuery::$default_page_size;
|
||||
if ($offset == -1) {
|
||||
$offset = $query->start;
|
||||
}
|
||||
if ($limit == -1) {
|
||||
$limit = $query->limit;
|
||||
}
|
||||
if ($limit == -1) {
|
||||
$limit = SearchQuery::$default_page_size;
|
||||
}
|
||||
|
||||
$params = array_merge($params, array('fq' => implode(' ', $fq)));
|
||||
|
||||
@ -709,7 +785,8 @@ abstract class SolrIndex extends SearchIndex {
|
||||
* @param array &$hlq Highlight query returned by reference
|
||||
* @return array
|
||||
*/
|
||||
protected function getQueryComponent(SearchQuery $searchQuery, &$hlq = array()) {
|
||||
protected function getQueryComponent(SearchQuery $searchQuery, &$hlq = array())
|
||||
{
|
||||
$q = array();
|
||||
foreach ($searchQuery->search as $search) {
|
||||
$text = $search['text'];
|
||||
@ -729,8 +806,7 @@ abstract class SolrIndex extends SearchIndex {
|
||||
$searchq[] = "{$field}:".$part.$fuzzy.$boost;
|
||||
}
|
||||
$q[] = '+('.implode(' OR ', $searchq).')';
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$q[] = '+'.$part.$fuzzy;
|
||||
}
|
||||
$hlq[] = $part;
|
||||
@ -745,7 +821,8 @@ abstract class SolrIndex extends SearchIndex {
|
||||
* @param SearchQuery $searchQuery
|
||||
* @return array List of parsed string values for each require
|
||||
*/
|
||||
protected function getRequireFiltersComponent(SearchQuery $searchQuery) {
|
||||
protected function getRequireFiltersComponent(SearchQuery $searchQuery)
|
||||
{
|
||||
$fq = array();
|
||||
foreach ($searchQuery->require as $field => $values) {
|
||||
$requireq = array();
|
||||
@ -753,11 +830,9 @@ abstract class SolrIndex extends SearchIndex {
|
||||
foreach ($values as $value) {
|
||||
if ($value === SearchQuery::$missing) {
|
||||
$requireq[] = "(*:* -{$field}:[* TO *])";
|
||||
}
|
||||
else if ($value === SearchQuery::$present) {
|
||||
} elseif ($value === SearchQuery::$present) {
|
||||
$requireq[] = "{$field}:[* TO *]";
|
||||
}
|
||||
else if ($value instanceof SearchQuery_Range) {
|
||||
} elseif ($value instanceof SearchQuery_Range) {
|
||||
$start = $value->start;
|
||||
if ($start === null) {
|
||||
$start = '*';
|
||||
@ -767,8 +842,7 @@ abstract class SolrIndex extends SearchIndex {
|
||||
$end = '*';
|
||||
}
|
||||
$requireq[] = "$field:[$start TO $end]";
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$requireq[] = $field.':"'.$value.'"';
|
||||
}
|
||||
}
|
||||
@ -784,7 +858,8 @@ abstract class SolrIndex extends SearchIndex {
|
||||
* @param SearchQuery $searchQuery
|
||||
* @return array List of parsed string values for each exclusion
|
||||
*/
|
||||
protected function getExcludeFiltersComponent(SearchQuery $searchQuery) {
|
||||
protected function getExcludeFiltersComponent(SearchQuery $searchQuery)
|
||||
{
|
||||
$fq = array();
|
||||
foreach ($searchQuery->exclude as $field => $values) {
|
||||
$excludeq = array();
|
||||
@ -793,11 +868,9 @@ abstract class SolrIndex extends SearchIndex {
|
||||
foreach ($values as $value) {
|
||||
if ($value === SearchQuery::$missing) {
|
||||
$missing = true;
|
||||
}
|
||||
else if ($value === SearchQuery::$present) {
|
||||
} elseif ($value === SearchQuery::$present) {
|
||||
$excludeq[] = "{$field}:[* TO *]";
|
||||
}
|
||||
else if ($value instanceof SearchQuery_Range) {
|
||||
} elseif ($value instanceof SearchQuery_Range) {
|
||||
$start = $value->start;
|
||||
if ($start === null) {
|
||||
$start = '*';
|
||||
@ -807,8 +880,7 @@ abstract class SolrIndex extends SearchIndex {
|
||||
$end = '*';
|
||||
}
|
||||
$excludeq[] = "$field:[$start TO $end]";
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$excludeq[] = $field.':"'.$value.'"';
|
||||
}
|
||||
}
|
||||
@ -824,7 +896,8 @@ abstract class SolrIndex extends SearchIndex {
|
||||
* @param SearchQuery $searchQuery
|
||||
* @return array
|
||||
*/
|
||||
public function getFiltersComponent(SearchQuery $searchQuery) {
|
||||
public function getFiltersComponent(SearchQuery $searchQuery)
|
||||
{
|
||||
return array_merge(
|
||||
$this->getRequireFiltersComponent($searchQuery),
|
||||
$this->getExcludeFiltersComponent($searchQuery)
|
||||
@ -836,12 +909,16 @@ abstract class SolrIndex extends SearchIndex {
|
||||
/**
|
||||
* @return SolrService
|
||||
*/
|
||||
public function getService() {
|
||||
if(!$this->service) $this->service = Solr::service(get_class($this));
|
||||
public function getService()
|
||||
{
|
||||
if (!$this->service) {
|
||||
$this->service = Solr::service(get_class($this));
|
||||
}
|
||||
return $this->service;
|
||||
}
|
||||
|
||||
public function setService(SolrService $service) {
|
||||
public function setService(SolrService $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
return $this;
|
||||
}
|
||||
@ -851,7 +928,8 @@ abstract class SolrIndex extends SearchIndex {
|
||||
*
|
||||
* @param SolrConfigStore $store
|
||||
*/
|
||||
public function uploadConfig($store) {
|
||||
public function uploadConfig($store)
|
||||
{
|
||||
// Upload the config files for this index
|
||||
$store->uploadString(
|
||||
$this->getIndexName(),
|
||||
|
@ -5,7 +5,8 @@ Solr::include_client_api();
|
||||
/**
|
||||
* The API for accessing a specific core of a Solr server. Exactly the same as Apache_Solr_Service for now.
|
||||
*/
|
||||
class SolrService_Core extends Apache_Solr_Service {
|
||||
class SolrService_Core extends Apache_Solr_Service
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
@ -13,13 +14,15 @@ class SolrService_Core extends Apache_Solr_Service {
|
||||
* plus extra methods for interrogating, creating, reloading and getting SolrService_Core instances
|
||||
* for Solr cores.
|
||||
*/
|
||||
class SolrService extends SolrService_Core {
|
||||
class SolrService extends SolrService_Core
|
||||
{
|
||||
private static $core_class = 'SolrService_Core';
|
||||
|
||||
/**
|
||||
* Handle encoding the GET parameters and making the HTTP call to execute a core command
|
||||
*/
|
||||
protected function coreCommand($command, $core, $params=array()) {
|
||||
protected function coreCommand($command, $core, $params=array())
|
||||
{
|
||||
$command = strtoupper($command);
|
||||
|
||||
$params = array_merge($params, array('action' => $command, 'wt' => 'json'));
|
||||
@ -33,7 +36,8 @@ class SolrService extends SolrService_Core {
|
||||
* @param $core string - The name of the core
|
||||
* @return boolean - True if that core exists & is active
|
||||
*/
|
||||
public function coreIsActive($core) {
|
||||
public function coreIsActive($core)
|
||||
{
|
||||
$result = $this->coreCommand('STATUS', $core);
|
||||
return isset($result->status->$core->uptime);
|
||||
}
|
||||
@ -47,11 +51,18 @@ class SolrService extends SolrService_Core {
|
||||
* @param $datadir string - The path to store data for this core on the server. Default depends on solrconfig.xml
|
||||
* @return Apache_Solr_Response
|
||||
*/
|
||||
public function coreCreate($core, $instancedir, $config=null, $schema=null, $datadir=null) {
|
||||
public function coreCreate($core, $instancedir, $config=null, $schema=null, $datadir=null)
|
||||
{
|
||||
$args = array('instanceDir' => $instancedir);
|
||||
if ($config) $args['config'] = $config;
|
||||
if ($schema) $args['schema'] = $schema;
|
||||
if ($datadir) $args['dataDir'] = $datadir;
|
||||
if ($config) {
|
||||
$args['config'] = $config;
|
||||
}
|
||||
if ($schema) {
|
||||
$args['schema'] = $schema;
|
||||
}
|
||||
if ($datadir) {
|
||||
$args['dataDir'] = $datadir;
|
||||
}
|
||||
|
||||
return $this->coreCommand('CREATE', $core, $args);
|
||||
}
|
||||
@ -61,7 +72,8 @@ class SolrService extends SolrService_Core {
|
||||
* @param $core string - The name of the core
|
||||
* @return Apache_Solr_Response
|
||||
*/
|
||||
public function coreReload($core) {
|
||||
public function coreReload($core)
|
||||
{
|
||||
return $this->coreCommand('RELOAD', $core);
|
||||
}
|
||||
|
||||
@ -70,7 +82,8 @@ class SolrService extends SolrService_Core {
|
||||
* @param $core string - The name of the core
|
||||
* @return Solr3Service_Core
|
||||
*/
|
||||
public function serviceForCore($core) {
|
||||
public function serviceForCore($core)
|
||||
{
|
||||
$klass = Config::inst()->get(get_called_class(), 'core_class');
|
||||
return new $klass($this->_host, $this->_port, $this->_path.$core, $this->_httpTransport);
|
||||
}
|
||||
|
@ -5,9 +5,10 @@ use Psr\Log\LoggerInterface;
|
||||
/**
|
||||
* Base class for re-indexing of solr content
|
||||
*/
|
||||
abstract class SolrReindexBase implements SolrReindexHandler {
|
||||
|
||||
public function runReindex(LoggerInterface $logger, $batchSize, $taskName, $classes = null) {
|
||||
abstract class SolrReindexBase implements SolrReindexHandler
|
||||
{
|
||||
public function runReindex(LoggerInterface $logger, $batchSize, $taskName, $classes = null)
|
||||
{
|
||||
foreach (Solr::get_indexes() as $indexInstance) {
|
||||
$this->processIndex($logger, $indexInstance, $batchSize, $taskName, $classes);
|
||||
}
|
||||
@ -49,7 +50,8 @@ abstract class SolrReindexBase implements SolrReindexHandler {
|
||||
* @param string|array $filterClasses Optional class or classes to limit to
|
||||
* @return array List of classes, where the key is the classname and value is list of options
|
||||
*/
|
||||
protected function getClassesForIndex(SolrIndex $index, $filterClasses = null) {
|
||||
protected function getClassesForIndex(SolrIndex $index, $filterClasses = null)
|
||||
{
|
||||
// Get base classes
|
||||
$classes = $index->getClasses();
|
||||
if (!$filterClasses) {
|
||||
@ -172,7 +174,8 @@ abstract class SolrReindexBase implements SolrReindexHandler {
|
||||
* @param int $group
|
||||
* @return DataList
|
||||
*/
|
||||
protected function getRecordsInGroup(SolrIndex $indexInstance, $class, $groups, $group) {
|
||||
protected function getRecordsInGroup(SolrIndex $indexInstance, $class, $groups, $group)
|
||||
{
|
||||
// Generate filtered list of local records
|
||||
$baseClass = ClassInfo::baseDataClass($class);
|
||||
$items = DataList::create($class)
|
||||
@ -204,7 +207,8 @@ abstract class SolrReindexBase implements SolrReindexHandler {
|
||||
* @param int $groups Number of groups, if clearing from a striped group
|
||||
* @param int $group Group number, if clearing from a striped group
|
||||
*/
|
||||
protected function clearRecords(SolrIndex $indexInstance, $class, $groups = null, $group = null) {
|
||||
protected function clearRecords(SolrIndex $indexInstance, $class, $groups = null, $group = null)
|
||||
{
|
||||
// Clear by classname
|
||||
$conditions = array("+(ClassHierarchy:{$class})");
|
||||
|
||||
|
@ -5,8 +5,8 @@ use Psr\Log\LoggerInterface;
|
||||
/**
|
||||
* Provides interface for queueing a solr reindex
|
||||
*/
|
||||
interface SolrReindexHandler {
|
||||
|
||||
interface SolrReindexHandler
|
||||
{
|
||||
/**
|
||||
* Trigger a solr-reindex
|
||||
*
|
||||
|
@ -7,9 +7,10 @@ use Psr\Log\LoggerInterface;
|
||||
*
|
||||
* Internally batches of records will be invoked via shell tasks in the background
|
||||
*/
|
||||
class SolrReindexImmediateHandler extends SolrReindexBase {
|
||||
|
||||
public function triggerReindex(LoggerInterface $logger, $batchSize, $taskName, $classes = null) {
|
||||
class SolrReindexImmediateHandler extends SolrReindexBase
|
||||
{
|
||||
public function triggerReindex(LoggerInterface $logger, $batchSize, $taskName, $classes = null)
|
||||
{
|
||||
$this->runReindex($logger, $batchSize, $taskName, $classes);
|
||||
}
|
||||
|
||||
|
@ -2,10 +2,12 @@
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
if(!class_exists('MessageQueue')) return;
|
||||
|
||||
class SolrReindexMessageHandler extends SolrReindexImmediateHandler {
|
||||
if (!class_exists('MessageQueue')) {
|
||||
return;
|
||||
}
|
||||
|
||||
class SolrReindexMessageHandler extends SolrReindexImmediateHandler
|
||||
{
|
||||
/**
|
||||
* The MessageQueue to use when processing updates
|
||||
* @config
|
||||
@ -13,7 +15,8 @@ class SolrReindexMessageHandler extends SolrReindexImmediateHandler {
|
||||
*/
|
||||
private static $reindex_queue = "search_indexing";
|
||||
|
||||
public function triggerReindex(LoggerInterface $logger, $batchSize, $taskName, $classes = null) {
|
||||
public function triggerReindex(LoggerInterface $logger, $batchSize, $taskName, $classes = null)
|
||||
{
|
||||
$queue = Config::inst()->get(__CLASS__, 'reindex_queue');
|
||||
|
||||
$logger->info('Queuing message');
|
||||
@ -30,7 +33,8 @@ class SolrReindexMessageHandler extends SolrReindexImmediateHandler {
|
||||
* @param string $taskName
|
||||
* @param array|string|null $classes
|
||||
*/
|
||||
public static function run_reindex($batchSize, $taskName, $classes = null) {
|
||||
public static function run_reindex($batchSize, $taskName, $classes = null)
|
||||
{
|
||||
// @todo Logger for message queue?
|
||||
$logger = Injector::inst()->createWithArgs('Monolog\Logger', array(strtolower(get_class())));
|
||||
|
||||
|
@ -2,17 +2,20 @@
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
if(!interface_exists('QueuedJob')) return;
|
||||
if (!interface_exists('QueuedJob')) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a queued task to start the reindex job
|
||||
*/
|
||||
class SolrReindexQueuedHandler extends SolrReindexBase {
|
||||
|
||||
class SolrReindexQueuedHandler extends SolrReindexBase
|
||||
{
|
||||
/**
|
||||
* @return QueuedJobService
|
||||
*/
|
||||
protected function getQueuedJobService() {
|
||||
protected function getQueuedJobService()
|
||||
{
|
||||
return singleton('QueuedJobService');
|
||||
}
|
||||
|
||||
@ -22,7 +25,8 @@ class SolrReindexQueuedHandler extends SolrReindexBase {
|
||||
* @param string $type Type of job to cancel
|
||||
* @return int Number of jobs cleared
|
||||
*/
|
||||
protected function cancelExistingJobs($type) {
|
||||
protected function cancelExistingJobs($type)
|
||||
{
|
||||
$clearable = array(
|
||||
// Paused jobs need to be discarded
|
||||
QueuedJob::STATUS_PAUSED,
|
||||
@ -47,7 +51,8 @@ class SolrReindexQueuedHandler extends SolrReindexBase {
|
||||
return DB::affectedRows();
|
||||
}
|
||||
|
||||
public function triggerReindex(LoggerInterface $logger, $batchSize, $taskName, $classes = null) {
|
||||
public function triggerReindex(LoggerInterface $logger, $batchSize, $taskName, $classes = null)
|
||||
{
|
||||
// Cancel existing jobs
|
||||
$queues = $this->cancelExistingJobs('SolrReindexQueuedJob');
|
||||
$groups = $this->cancelExistingJobs('SolrReindexGroupQueuedJob');
|
||||
@ -90,5 +95,4 @@ class SolrReindexQueuedHandler extends SolrReindexBase {
|
||||
$logger->info("Queuing commit on all changes");
|
||||
SearchUpdateCommitJobProcessor::queue();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
<?php
|
||||
|
||||
if(!interface_exists('QueuedJob')) return;
|
||||
if (!interface_exists('QueuedJob')) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queuedjob to re-index a small group within an index.
|
||||
@ -11,8 +13,8 @@ if(!interface_exists('QueuedJob')) return;
|
||||
* list of IDs. Instead groups are segmented by ID. Additionally, this task does incremental
|
||||
* deletions of records.
|
||||
*/
|
||||
class SolrReindexGroupQueuedJob extends SolrReindexQueuedJobBase {
|
||||
|
||||
class SolrReindexGroupQueuedJob extends SolrReindexQueuedJobBase
|
||||
{
|
||||
/**
|
||||
* Name of index to reindex
|
||||
*
|
||||
@ -48,7 +50,8 @@ class SolrReindexGroupQueuedJob extends SolrReindexQueuedJobBase {
|
||||
*/
|
||||
protected $group;
|
||||
|
||||
public function __construct($indexName = null, $state = null, $class = null, $groups = null, $group = null) {
|
||||
public function __construct($indexName = null, $state = null, $class = null, $groups = null, $group = null)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->indexName = $indexName;
|
||||
$this->state = $state;
|
||||
@ -57,7 +60,8 @@ class SolrReindexGroupQueuedJob extends SolrReindexQueuedJobBase {
|
||||
$this->group = $group;
|
||||
}
|
||||
|
||||
public function getJobData() {
|
||||
public function getJobData()
|
||||
{
|
||||
$data = parent::getJobData();
|
||||
|
||||
// Custom data
|
||||
@ -70,7 +74,8 @@ class SolrReindexGroupQueuedJob extends SolrReindexQueuedJobBase {
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function setJobData($totalSteps, $currentStep, $isComplete, $jobData, $messages) {
|
||||
public function setJobData($totalSteps, $currentStep, $isComplete, $jobData, $messages)
|
||||
{
|
||||
parent::setJobData($totalSteps, $currentStep, $isComplete, $jobData, $messages);
|
||||
|
||||
// Custom data
|
||||
@ -81,11 +86,13 @@ class SolrReindexGroupQueuedJob extends SolrReindexQueuedJobBase {
|
||||
$this->group = $jobData->group;
|
||||
}
|
||||
|
||||
public function getSignature() {
|
||||
public function getSignature()
|
||||
{
|
||||
return md5(get_class($this) . time() . mt_rand(0, 100000));
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
public function getTitle()
|
||||
{
|
||||
return sprintf(
|
||||
'Solr Reindex Group (%d/%d) of %s in %s',
|
||||
($this->group+1),
|
||||
@ -95,7 +102,8 @@ class SolrReindexGroupQueuedJob extends SolrReindexQueuedJobBase {
|
||||
);
|
||||
}
|
||||
|
||||
public function process() {
|
||||
public function process()
|
||||
{
|
||||
$logger = $this->getLogger();
|
||||
if ($this->jobFinished()) {
|
||||
$logger->notice("reindex group already complete");
|
||||
@ -113,5 +121,4 @@ class SolrReindexGroupQueuedJob extends SolrReindexQueuedJobBase {
|
||||
$logger->info("Completed reindex group");
|
||||
$this->isComplete = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,12 +1,14 @@
|
||||
<?php
|
||||
|
||||
if(!interface_exists('QueuedJob')) return;
|
||||
if (!interface_exists('QueuedJob')) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a queuedjob which invokes a reindex
|
||||
*/
|
||||
class SolrReindexQueuedJob extends SolrReindexQueuedJobBase {
|
||||
|
||||
class SolrReindexQueuedJob extends SolrReindexQueuedJobBase
|
||||
{
|
||||
/**
|
||||
* Size of each batch to run
|
||||
*
|
||||
@ -29,14 +31,16 @@ class SolrReindexQueuedJob extends SolrReindexQueuedJobBase {
|
||||
*/
|
||||
protected $classes;
|
||||
|
||||
public function __construct($batchSize = null, $taskName = null, $classes = null) {
|
||||
public function __construct($batchSize = null, $taskName = null, $classes = null)
|
||||
{
|
||||
$this->batchSize = $batchSize;
|
||||
$this->taskName = $taskName;
|
||||
$this->classes = $classes;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function getJobData() {
|
||||
public function getJobData()
|
||||
{
|
||||
$data = parent::getJobData();
|
||||
|
||||
// Custom data
|
||||
@ -47,7 +51,8 @@ class SolrReindexQueuedJob extends SolrReindexQueuedJobBase {
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function setJobData($totalSteps, $currentStep, $isComplete, $jobData, $messages) {
|
||||
public function setJobData($totalSteps, $currentStep, $isComplete, $jobData, $messages)
|
||||
{
|
||||
parent::setJobData($totalSteps, $currentStep, $isComplete, $jobData, $messages);
|
||||
|
||||
// Custom data
|
||||
@ -56,15 +61,18 @@ class SolrReindexQueuedJob extends SolrReindexQueuedJobBase {
|
||||
$this->classes = $jobData->classes;
|
||||
}
|
||||
|
||||
public function getSignature() {
|
||||
public function getSignature()
|
||||
{
|
||||
return __CLASS__;
|
||||
}
|
||||
|
||||
public function getTitle() {
|
||||
public function getTitle()
|
||||
{
|
||||
return 'Solr Reindex Job';
|
||||
}
|
||||
|
||||
public function process() {
|
||||
public function process()
|
||||
{
|
||||
$logger = $this->getLogger();
|
||||
if ($this->jobFinished()) {
|
||||
$logger->notice("reindex already complete");
|
||||
@ -85,7 +93,8 @@ class SolrReindexQueuedJob extends SolrReindexQueuedJobBase {
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getBatchSize() {
|
||||
public function getBatchSize()
|
||||
{
|
||||
return $this->batchSize;
|
||||
}
|
||||
}
|
||||
|
@ -3,13 +3,15 @@
|
||||
use Monolog\Logger;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
if(!interface_exists('QueuedJob')) return;
|
||||
if (!interface_exists('QueuedJob')) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for jobs which perform re-index
|
||||
*/
|
||||
abstract class SolrReindexQueuedJobBase implements QueuedJob {
|
||||
|
||||
abstract class SolrReindexQueuedJobBase implements QueuedJob
|
||||
{
|
||||
/**
|
||||
* Flag whether this job is done
|
||||
*
|
||||
@ -31,7 +33,8 @@ abstract class SolrReindexQueuedJobBase implements QueuedJob {
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
public function __construct() {
|
||||
public function __construct()
|
||||
{
|
||||
$this->isComplete = false;
|
||||
$this->messages = array();
|
||||
}
|
||||
@ -39,7 +42,8 @@ abstract class SolrReindexQueuedJobBase implements QueuedJob {
|
||||
/**
|
||||
* @return SearchLogFactory
|
||||
*/
|
||||
protected function getLoggerFactory() {
|
||||
protected function getLoggerFactory()
|
||||
{
|
||||
return Injector::inst()->get('SearchLogFactory');
|
||||
}
|
||||
|
||||
@ -48,7 +52,8 @@ abstract class SolrReindexQueuedJobBase implements QueuedJob {
|
||||
*
|
||||
* @return LoggerInterface
|
||||
*/
|
||||
protected function getLogger() {
|
||||
protected function getLogger()
|
||||
{
|
||||
if ($this->logger) {
|
||||
return $this->logger;
|
||||
}
|
||||
@ -65,11 +70,13 @@ abstract class SolrReindexQueuedJobBase implements QueuedJob {
|
||||
*
|
||||
* @param LoggerInterface $logger
|
||||
*/
|
||||
public function setLogger($logger) {
|
||||
public function setLogger($logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function getJobData() {
|
||||
public function getJobData()
|
||||
{
|
||||
$data = new stdClass();
|
||||
|
||||
// Standard fields
|
||||
@ -83,7 +90,8 @@ abstract class SolrReindexQueuedJobBase implements QueuedJob {
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function setJobData($totalSteps, $currentStep, $isComplete, $jobData, $messages) {
|
||||
public function setJobData($totalSteps, $currentStep, $isComplete, $jobData, $messages)
|
||||
{
|
||||
$this->isComplete = $isComplete;
|
||||
$this->messages = $messages;
|
||||
}
|
||||
@ -93,31 +101,38 @@ abstract class SolrReindexQueuedJobBase implements QueuedJob {
|
||||
*
|
||||
* @return SolrReindexHandler
|
||||
*/
|
||||
protected function getHandler() {
|
||||
protected function getHandler()
|
||||
{
|
||||
return Injector::inst()->get('SolrReindexHandler');
|
||||
}
|
||||
|
||||
public function jobFinished() {
|
||||
public function jobFinished()
|
||||
{
|
||||
return $this->isComplete;
|
||||
}
|
||||
|
||||
public function prepareForRestart() {
|
||||
public function prepareForRestart()
|
||||
{
|
||||
// NOOP
|
||||
}
|
||||
|
||||
public function setup() {
|
||||
public function setup()
|
||||
{
|
||||
// NOOP
|
||||
}
|
||||
|
||||
public function afterComplete() {
|
||||
public function afterComplete()
|
||||
{
|
||||
// NOOP
|
||||
}
|
||||
|
||||
public function getJobType() {
|
||||
public function getJobType()
|
||||
{
|
||||
return QueuedJob::QUEUED;
|
||||
}
|
||||
|
||||
public function addMessage($message) {
|
||||
public function addMessage($message)
|
||||
{
|
||||
$this->messages[] = $message;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
class CombinationsArrayIterator implements Iterator {
|
||||
class CombinationsArrayIterator implements Iterator
|
||||
{
|
||||
protected $arrays;
|
||||
protected $keys;
|
||||
protected $numArrays;
|
||||
@ -8,7 +9,8 @@ class CombinationsArrayIterator implements Iterator {
|
||||
protected $isValid = false;
|
||||
protected $k = 0;
|
||||
|
||||
function __construct($args) {
|
||||
public function __construct($args)
|
||||
{
|
||||
$this->arrays = array();
|
||||
$this->keys = array();
|
||||
|
||||
@ -26,41 +28,53 @@ class CombinationsArrayIterator implements Iterator {
|
||||
$this->rewind();
|
||||
}
|
||||
|
||||
function rewind() {
|
||||
public function rewind()
|
||||
{
|
||||
if (!$this->numArrays) {
|
||||
$this->isValid = false;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$this->isValid = true;
|
||||
$this->k = 0;
|
||||
|
||||
for ($i = 0; $i < $this->numArrays; $i++) reset($this->arrays[$i]);
|
||||
for ($i = 0; $i < $this->numArrays; $i++) {
|
||||
reset($this->arrays[$i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function valid() {
|
||||
public function valid()
|
||||
{
|
||||
return $this->isValid;
|
||||
}
|
||||
|
||||
function next() {
|
||||
public function next()
|
||||
{
|
||||
$this->k++;
|
||||
|
||||
for ($i = 0; $i < $this->numArrays; $i++) {
|
||||
if (next($this->arrays[$i]) === false) {
|
||||
if ($i == $this->numArrays-1) $this->isValid = false;
|
||||
else reset($this->arrays[$i]);
|
||||
if ($i == $this->numArrays-1) {
|
||||
$this->isValid = false;
|
||||
} else {
|
||||
reset($this->arrays[$i]);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
else break;
|
||||
}
|
||||
}
|
||||
|
||||
function current() {
|
||||
public function current()
|
||||
{
|
||||
$res = array();
|
||||
for ($i = 0; $i < $this->numArrays; $i++) $res[$this->keys[$i]] = current($this->arrays[$i]);
|
||||
for ($i = 0; $i < $this->numArrays; $i++) {
|
||||
$res[$this->keys[$i]] = current($this->arrays[$i]);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
function key() {
|
||||
public function key()
|
||||
{
|
||||
return $this->k;
|
||||
}
|
||||
}
|
@ -1,44 +1,58 @@
|
||||
<?php
|
||||
|
||||
class MultipleArrayIterator implements Iterator {
|
||||
|
||||
class MultipleArrayIterator implements Iterator
|
||||
{
|
||||
protected $arrays;
|
||||
protected $active;
|
||||
|
||||
function __construct() {
|
||||
public function __construct()
|
||||
{
|
||||
$args = func_get_args();
|
||||
|
||||
$this->arrays = array();
|
||||
foreach ($args as $arg) {
|
||||
if (is_array($arg) && count($arg)) $this->arrays[] = $arg;
|
||||
if (is_array($arg) && count($arg)) {
|
||||
$this->arrays[] = $arg;
|
||||
}
|
||||
}
|
||||
|
||||
$this->rewind();
|
||||
}
|
||||
|
||||
function rewind() {
|
||||
public function rewind()
|
||||
{
|
||||
$this->active = $this->arrays;
|
||||
if ($this->active) reset($this->active[0]);
|
||||
if ($this->active) {
|
||||
reset($this->active[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function current() {
|
||||
public function current()
|
||||
{
|
||||
return $this->active ? current($this->active[0]) : false;
|
||||
}
|
||||
|
||||
function key() {
|
||||
public function key()
|
||||
{
|
||||
return $this->active ? key($this->active[0]) : false;
|
||||
}
|
||||
|
||||
function next() {
|
||||
if (!$this->active) return;
|
||||
public function next()
|
||||
{
|
||||
if (!$this->active) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (next($this->active[0]) === false) {
|
||||
array_shift($this->active);
|
||||
if ($this->active) reset($this->active[0]);
|
||||
if ($this->active) {
|
||||
reset($this->active[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function valid() {
|
||||
public function valid()
|
||||
{
|
||||
return $this->active && (current($this->active[0]) !== false);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
<?php
|
||||
|
||||
class WebDAV {
|
||||
|
||||
static function curl_init($url, $method) {
|
||||
class WebDAV
|
||||
{
|
||||
public static function curl_init($url, $method)
|
||||
{
|
||||
$ch = curl_init($url);
|
||||
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
@ -12,7 +13,8 @@ class WebDAV {
|
||||
return $ch;
|
||||
}
|
||||
|
||||
static function exists($url) {
|
||||
public static function exists($url)
|
||||
{
|
||||
// WebDAV expects that checking a directory exists has a trailing slash
|
||||
if (substr($url, -1) != '/') {
|
||||
$url .= '/';
|
||||
@ -23,13 +25,18 @@ class WebDAV {
|
||||
$res = curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
if ($code == 404) return false;
|
||||
if ($code == 200 || $code == 207) return true;
|
||||
if ($code == 404) {
|
||||
return false;
|
||||
}
|
||||
if ($code == 200 || $code == 207) {
|
||||
return true;
|
||||
}
|
||||
|
||||
user_error("Got error from webdav server - ".$code, E_USER_ERROR);
|
||||
}
|
||||
|
||||
static function mkdir($url) {
|
||||
public static function mkdir($url)
|
||||
{
|
||||
$ch = self::curl_init(rtrim($url, '/').'/', 'MKCOL');
|
||||
|
||||
$res = curl_exec($ch);
|
||||
@ -38,7 +45,8 @@ class WebDAV {
|
||||
return $code == 201;
|
||||
}
|
||||
|
||||
static function put($handle, $url) {
|
||||
public static function put($handle, $url)
|
||||
{
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
|
||||
|
||||
@ -53,16 +61,16 @@ class WebDAV {
|
||||
return curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
}
|
||||
|
||||
static function upload_from_string($string, $url) {
|
||||
public static function upload_from_string($string, $url)
|
||||
{
|
||||
$fh = tmpfile();
|
||||
fwrite($fh, $string);
|
||||
fseek($fh, 0);
|
||||
return self::put($fh, $url);
|
||||
}
|
||||
|
||||
static function upload_from_file($string, $url) {
|
||||
public static function upload_from_file($string, $url)
|
||||
{
|
||||
return self::put(fopen($string, 'rb'), $url);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -8,9 +8,10 @@ use Monolog\Logger;
|
||||
/**
|
||||
* Provides logging based on monolog
|
||||
*/
|
||||
class MonologFactory implements SearchLogFactory {
|
||||
|
||||
public function getOutputLogger($name, $verbose) {
|
||||
class MonologFactory implements SearchLogFactory
|
||||
{
|
||||
public function getOutputLogger($name, $verbose)
|
||||
{
|
||||
$logger = $this->getLoggerFor($name);
|
||||
$formatter = $this->getFormatter();
|
||||
|
||||
@ -26,7 +27,8 @@ class MonologFactory implements SearchLogFactory {
|
||||
return $logger;
|
||||
}
|
||||
|
||||
public function getQueuedJobLogger($job) {
|
||||
public function getQueuedJobLogger($job)
|
||||
{
|
||||
$logger = $this->getLoggerFor(get_class($job));
|
||||
$handler = $this->getJobHandler($job);
|
||||
$logger->pushHandler($handler);
|
||||
@ -42,7 +44,8 @@ class MonologFactory implements SearchLogFactory {
|
||||
* @param bool $bubble
|
||||
* @return HandlerInterface
|
||||
*/
|
||||
protected function getStreamHandler(FormatterInterface $formatter, $stream, $level = Logger::DEBUG, $bubble = true) {
|
||||
protected function getStreamHandler(FormatterInterface $formatter, $stream, $level = Logger::DEBUG, $bubble = true)
|
||||
{
|
||||
// Unless cli, force output to php://output
|
||||
$stream = Director::is_cli() ? $stream : 'php://output';
|
||||
$handler = Injector::inst()->createWithArgs(
|
||||
@ -58,7 +61,8 @@ class MonologFactory implements SearchLogFactory {
|
||||
*
|
||||
* @return FormatterInterface
|
||||
*/
|
||||
protected function getFormatter() {
|
||||
protected function getFormatter()
|
||||
{
|
||||
// Get formatter
|
||||
$format = LineFormatter::SIMPLE_FORMAT;
|
||||
if (!Director::is_cli()) {
|
||||
@ -76,7 +80,8 @@ class MonologFactory implements SearchLogFactory {
|
||||
* @param string $name
|
||||
* @return Logger
|
||||
*/
|
||||
protected function getLoggerFor($name) {
|
||||
protected function getLoggerFor($name)
|
||||
{
|
||||
return Injector::inst()->createWithArgs(
|
||||
'Monolog\Logger',
|
||||
array(strtolower($name))
|
||||
@ -89,7 +94,8 @@ class MonologFactory implements SearchLogFactory {
|
||||
* @param QueuedJob $job
|
||||
* @return HandlerInterface
|
||||
*/
|
||||
protected function getJobHandler($job) {
|
||||
protected function getJobHandler($job)
|
||||
{
|
||||
return Injector::inst()->createWithArgs(
|
||||
'QueuedJobLogHandler',
|
||||
array($job, Logger::INFO)
|
||||
|
@ -3,13 +3,15 @@
|
||||
use Monolog\Handler\AbstractProcessingHandler;
|
||||
use Monolog\Logger;
|
||||
|
||||
if(!interface_exists('QueuedJob')) return;
|
||||
if (!interface_exists('QueuedJob')) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for logging events into QueuedJob message data
|
||||
*/
|
||||
class QueuedJobLogHandler extends AbstractProcessingHandler {
|
||||
|
||||
class QueuedJobLogHandler extends AbstractProcessingHandler
|
||||
{
|
||||
/**
|
||||
* Job to log to
|
||||
*
|
||||
@ -22,7 +24,8 @@ class QueuedJobLogHandler extends AbstractProcessingHandler {
|
||||
* @param integer $level The minimum logging level at which this handler will be triggered
|
||||
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
*/
|
||||
public function __construct(QueuedJob $queuedJob, $level = Logger::DEBUG, $bubble = true) {
|
||||
public function __construct(QueuedJob $queuedJob, $level = Logger::DEBUG, $bubble = true)
|
||||
{
|
||||
parent::__construct($level, $bubble);
|
||||
$this->setQueuedJob($queuedJob);
|
||||
}
|
||||
@ -32,7 +35,8 @@ class QueuedJobLogHandler extends AbstractProcessingHandler {
|
||||
*
|
||||
* @param QueuedJob $queuedJob
|
||||
*/
|
||||
public function setQueuedJob(QueuedJob $queuedJob) {
|
||||
public function setQueuedJob(QueuedJob $queuedJob)
|
||||
{
|
||||
$this->queuedJob = $queuedJob;
|
||||
}
|
||||
|
||||
@ -41,13 +45,14 @@ class QueuedJobLogHandler extends AbstractProcessingHandler {
|
||||
*
|
||||
* @return QueuedJob
|
||||
*/
|
||||
public function getQueuedJob() {
|
||||
public function getQueuedJob()
|
||||
{
|
||||
return $this->queuedJob;
|
||||
}
|
||||
|
||||
protected function write(array $record) {
|
||||
protected function write(array $record)
|
||||
{
|
||||
// Write formatted message
|
||||
$this->getQueuedJob()->addMessage($record['formatted']);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
use Psr\Log;
|
||||
|
||||
interface SearchLogFactory {
|
||||
|
||||
interface SearchLogFactory
|
||||
{
|
||||
/**
|
||||
* Make a logger for a queuedjob
|
||||
*
|
||||
|
@ -1,23 +1,28 @@
|
||||
<?php
|
||||
|
||||
|
||||
class BatchedProcessorTest_Object extends SiteTree implements TestOnly {
|
||||
class BatchedProcessorTest_Object extends SiteTree implements TestOnly
|
||||
{
|
||||
private static $db = array(
|
||||
'TestText' => 'Varchar'
|
||||
);
|
||||
}
|
||||
|
||||
class BatchedProcessorTest_Index extends SearchIndex_Recording implements TestOnly {
|
||||
function init() {
|
||||
class BatchedProcessorTest_Index extends SearchIndex_Recording implements TestOnly
|
||||
{
|
||||
public function init()
|
||||
{
|
||||
$this->addClass('BatchedProcessorTest_Object');
|
||||
$this->addFilterField('TestText');
|
||||
}
|
||||
}
|
||||
|
||||
class BatchedProcessor_QueuedJobService {
|
||||
class BatchedProcessor_QueuedJobService
|
||||
{
|
||||
protected $jobs = array();
|
||||
|
||||
public function queueJob(QueuedJob $job, $startAfter = null, $userId = null, $queueName = null) {
|
||||
public function queueJob(QueuedJob $job, $startAfter = null, $userId = null, $queueName = null)
|
||||
{
|
||||
$this->jobs[] = array(
|
||||
'job' => $job,
|
||||
'startAfter' => $startAfter
|
||||
@ -25,7 +30,8 @@ class BatchedProcessor_QueuedJobService {
|
||||
return $job;
|
||||
}
|
||||
|
||||
public function getJobs() {
|
||||
public function getJobs()
|
||||
{
|
||||
return $this->jobs;
|
||||
}
|
||||
}
|
||||
@ -33,8 +39,8 @@ class BatchedProcessor_QueuedJobService {
|
||||
/**
|
||||
* Tests {@see SearchUpdateQueuedJobProcessor}
|
||||
*/
|
||||
class BatchedProcessorTest extends SapphireTest {
|
||||
|
||||
class BatchedProcessorTest extends SapphireTest
|
||||
{
|
||||
protected $oldProcessor;
|
||||
|
||||
protected $extraDataObjects = array(
|
||||
@ -48,7 +54,8 @@ class BatchedProcessorTest extends SapphireTest {
|
||||
)
|
||||
);
|
||||
|
||||
public function setUpOnce() {
|
||||
public function setUpOnce()
|
||||
{
|
||||
// Disable illegal extensions if skipping this test
|
||||
if (class_exists('Subsite') || !interface_exists('QueuedJob')) {
|
||||
$this->illegalExtensions = array();
|
||||
@ -56,7 +63,8 @@ class BatchedProcessorTest extends SapphireTest {
|
||||
parent::setUpOnce();
|
||||
}
|
||||
|
||||
public function setUp() {
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
Config::nest();
|
||||
|
||||
@ -89,7 +97,8 @@ class BatchedProcessorTest extends SapphireTest {
|
||||
SearchUpdater::$processor = new SearchUpdateQueuedJobProcessor();
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
public function tearDown()
|
||||
{
|
||||
if ($this->oldProcessor) {
|
||||
SearchUpdater::$processor = $this->oldProcessor;
|
||||
}
|
||||
@ -102,7 +111,8 @@ class BatchedProcessorTest extends SapphireTest {
|
||||
/**
|
||||
* @return SearchUpdateQueuedJobProcessor
|
||||
*/
|
||||
protected function generateDirtyIds() {
|
||||
protected function generateDirtyIds()
|
||||
{
|
||||
$processor = SearchUpdater::$processor;
|
||||
for ($id = 1; $id <= 42; $id++) {
|
||||
// Save to db
|
||||
@ -126,7 +136,8 @@ class BatchedProcessorTest extends SapphireTest {
|
||||
/**
|
||||
* Tests that large jobs are broken up into a suitable number of batches
|
||||
*/
|
||||
public function testBatching() {
|
||||
public function testBatching()
|
||||
{
|
||||
$index = singleton('BatchedProcessorTest_Index');
|
||||
$index->reset();
|
||||
$processor = $this->generateDirtyIds();
|
||||
@ -164,7 +175,8 @@ class BatchedProcessorTest extends SapphireTest {
|
||||
/**
|
||||
* Test creation of multiple commit jobs
|
||||
*/
|
||||
public function testMultipleCommits() {
|
||||
public function testMultipleCommits()
|
||||
{
|
||||
$index = singleton('BatchedProcessorTest_Index');
|
||||
$index->reset();
|
||||
|
||||
@ -210,7 +222,8 @@ class BatchedProcessorTest extends SapphireTest {
|
||||
/**
|
||||
* Tests that the batch_soft_cap setting is properly respected
|
||||
*/
|
||||
public function testSoftCap() {
|
||||
public function testSoftCap()
|
||||
{
|
||||
$index = singleton('BatchedProcessorTest_Index');
|
||||
$index->reset();
|
||||
$processor = $this->generateDirtyIds();
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
class SearchUpdaterTest_Container extends DataObject {
|
||||
class SearchUpdaterTest_Container extends DataObject
|
||||
{
|
||||
private static $db = array(
|
||||
'Field1' => 'Varchar',
|
||||
'Field2' => 'Varchar',
|
||||
@ -20,7 +21,8 @@ class SearchUpdaterTest_Container extends DataObject {
|
||||
);
|
||||
}
|
||||
|
||||
class SearchUpdaterTest_HasOne extends DataObject {
|
||||
class SearchUpdaterTest_HasOne extends DataObject
|
||||
{
|
||||
private static $db = array(
|
||||
'Field1' => 'Varchar',
|
||||
'Field2' => 'Varchar'
|
||||
@ -31,7 +33,8 @@ class SearchUpdaterTest_HasOne extends DataObject {
|
||||
);
|
||||
}
|
||||
|
||||
class SearchUpdaterTest_HasMany extends DataObject {
|
||||
class SearchUpdaterTest_HasMany extends DataObject
|
||||
{
|
||||
private static $db = array(
|
||||
'Field1' => 'Varchar',
|
||||
'Field2' => 'Varchar'
|
||||
@ -42,7 +45,8 @@ class SearchUpdaterTest_HasMany extends DataObject {
|
||||
);
|
||||
}
|
||||
|
||||
class SearchUpdaterTest_ManyMany extends DataObject {
|
||||
class SearchUpdaterTest_ManyMany extends DataObject
|
||||
{
|
||||
private static $db = array(
|
||||
'Field1' => 'Varchar',
|
||||
'Field2' => 'Varchar'
|
||||
@ -53,8 +57,10 @@ class SearchUpdaterTest_ManyMany extends DataObject {
|
||||
);
|
||||
}
|
||||
|
||||
class SearchUpdaterTest_Index extends SearchIndex_Recording {
|
||||
function init() {
|
||||
class SearchUpdaterTest_Index extends SearchIndex_Recording
|
||||
{
|
||||
public function init()
|
||||
{
|
||||
$this->addClass('SearchUpdaterTest_Container');
|
||||
|
||||
$this->addFilterField('Field1');
|
||||
@ -63,17 +69,21 @@ class SearchUpdaterTest_Index extends SearchIndex_Recording {
|
||||
}
|
||||
}
|
||||
|
||||
class SearchUpdaterTest extends SapphireTest {
|
||||
|
||||
class SearchUpdaterTest extends SapphireTest
|
||||
{
|
||||
protected $usesDatabase = true;
|
||||
|
||||
private static $index = null;
|
||||
|
||||
function setUp() {
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
if (self::$index === null) self::$index = singleton(get_class($this).'_Index');
|
||||
else self::$index->reset();
|
||||
if (self::$index === null) {
|
||||
self::$index = singleton(get_class($this).'_Index');
|
||||
} else {
|
||||
self::$index->reset();
|
||||
}
|
||||
|
||||
SearchUpdater::bind_manipulation_capture();
|
||||
|
||||
@ -87,13 +97,15 @@ class SearchUpdaterTest extends SapphireTest {
|
||||
SearchUpdater::clear_dirty_indexes();
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
public function tearDown()
|
||||
{
|
||||
Config::unnest();
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
function testBasic() {
|
||||
public function testBasic()
|
||||
{
|
||||
$item = new SearchUpdaterTest_Container();
|
||||
$item->write();
|
||||
|
||||
@ -101,7 +113,8 @@ class SearchUpdaterTest extends SapphireTest {
|
||||
// TODO: Get updating just field2 to not update item (maybe not possible - variants complicate)
|
||||
}
|
||||
|
||||
function testHasOneHook() {
|
||||
public function testHasOneHook()
|
||||
{
|
||||
$hasOne = new SearchUpdaterTest_HasOne();
|
||||
$hasOne->write();
|
||||
|
||||
@ -174,7 +187,8 @@ class SearchUpdaterTest extends SapphireTest {
|
||||
));
|
||||
}
|
||||
|
||||
function testHasManyHook() {
|
||||
public function testHasManyHook()
|
||||
{
|
||||
$container1 = new SearchUpdaterTest_Container();
|
||||
$container1->write();
|
||||
|
||||
|
@ -1,27 +1,31 @@
|
||||
<?php
|
||||
|
||||
class SearchVariantSiteTreeSubsitesPolyhomeTest_Item extends SiteTree {
|
||||
class SearchVariantSiteTreeSubsitesPolyhomeTest_Item extends SiteTree
|
||||
{
|
||||
// TODO: Currently theres a failure if you addClass a non-table class
|
||||
private static $db = array(
|
||||
'TestText' => 'Varchar'
|
||||
);
|
||||
}
|
||||
|
||||
class SearchVariantSiteTreeSubsitesPolyhomeTest_Index extends SearchIndex_Recording {
|
||||
function init() {
|
||||
class SearchVariantSiteTreeSubsitesPolyhomeTest_Index extends SearchIndex_Recording
|
||||
{
|
||||
public function init()
|
||||
{
|
||||
$this->addClass('SearchVariantSiteTreeSubsitesPolyhomeTest_Item');
|
||||
$this->addFilterField('TestText');
|
||||
}
|
||||
}
|
||||
|
||||
class SearchVariantSiteTreeSubsitesPolyhomeTest extends SapphireTest {
|
||||
|
||||
class SearchVariantSiteTreeSubsitesPolyhomeTest extends SapphireTest
|
||||
{
|
||||
private static $index = null;
|
||||
|
||||
private static $subsite_a = null;
|
||||
private static $subsite_b = null;
|
||||
|
||||
function setUp() {
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Check subsites installed
|
||||
@ -29,18 +33,23 @@ class SearchVariantSiteTreeSubsitesPolyhomeTest extends SapphireTest {
|
||||
return $this->markTestSkipped('The subsites polyhome module is not installed');
|
||||
}
|
||||
|
||||
if (self::$index === null) self::$index = singleton('SearchVariantSiteTreeSubsitesPolyhomeTest_Index');
|
||||
if (self::$index === null) {
|
||||
self::$index = singleton('SearchVariantSiteTreeSubsitesPolyhomeTest_Index');
|
||||
}
|
||||
|
||||
if (self::$subsite_a === null) {
|
||||
self::$subsite_a = new Subsite(); self::$subsite_a->write();
|
||||
self::$subsite_b = new Subsite(); self::$subsite_b->write();
|
||||
self::$subsite_a = new Subsite();
|
||||
self::$subsite_a->write();
|
||||
self::$subsite_b = new Subsite();
|
||||
self::$subsite_b->write();
|
||||
}
|
||||
|
||||
FullTextSearch::force_index_list(self::$index);
|
||||
SearchUpdater::clear_dirty_indexes();
|
||||
}
|
||||
|
||||
function testSavingDirect() {
|
||||
public function testSavingDirect()
|
||||
{
|
||||
// Initial add
|
||||
|
||||
$item = new SearchVariantSiteTreeSubsitesPolyhomeTest_Item();
|
||||
@ -70,7 +79,5 @@ class SearchVariantSiteTreeSubsitesPolyhomeTest extends SapphireTest {
|
||||
'SearchVariantVersioned' => 'Stage', 'SearchVariantSiteTreeSubsitesPolyhome' => self::$subsite_b->ID
|
||||
))
|
||||
));
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -1,14 +1,15 @@
|
||||
<?php
|
||||
|
||||
class SearchVariantVersionedTest extends SapphireTest {
|
||||
|
||||
class SearchVariantVersionedTest extends SapphireTest
|
||||
{
|
||||
private static $index = null;
|
||||
|
||||
protected $extraDataObjects = array(
|
||||
'SearchVariantVersionedTest_Item'
|
||||
);
|
||||
|
||||
function setUp() {
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Check versioned available
|
||||
@ -16,7 +17,9 @@ class SearchVariantVersionedTest extends SapphireTest {
|
||||
return $this->markTestSkipped('The versioned decorator is not installed');
|
||||
}
|
||||
|
||||
if (self::$index === null) self::$index = singleton('SearchVariantVersionedTest_Index');
|
||||
if (self::$index === null) {
|
||||
self::$index = singleton('SearchVariantVersionedTest_Index');
|
||||
}
|
||||
|
||||
SearchUpdater::bind_manipulation_capture();
|
||||
|
||||
@ -30,13 +33,15 @@ class SearchVariantVersionedTest extends SapphireTest {
|
||||
SearchUpdater::clear_dirty_indexes();
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
public function tearDown()
|
||||
{
|
||||
Config::unnest();
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
function testPublishing() {
|
||||
public function testPublishing()
|
||||
{
|
||||
// Check that write updates Stage
|
||||
|
||||
$item = new SearchVariantVersionedTest_Item(array('TestText' => 'Foo'));
|
||||
@ -71,7 +76,8 @@ class SearchVariantVersionedTest extends SapphireTest {
|
||||
));
|
||||
}
|
||||
|
||||
function testExcludeVariantState() {
|
||||
public function testExcludeVariantState()
|
||||
{
|
||||
$index = singleton('SearchVariantVersionedTest_IndexNoStage');
|
||||
FullTextSearch::force_index_list($index);
|
||||
|
||||
@ -91,22 +97,27 @@ class SearchVariantVersionedTest extends SapphireTest {
|
||||
}
|
||||
}
|
||||
|
||||
class SearchVariantVersionedTest_Item extends SiteTree implements TestOnly {
|
||||
class SearchVariantVersionedTest_Item extends SiteTree implements TestOnly
|
||||
{
|
||||
// TODO: Currently theres a failure if you addClass a non-table class
|
||||
private static $db = array(
|
||||
'TestText' => 'Varchar'
|
||||
);
|
||||
}
|
||||
|
||||
class SearchVariantVersionedTest_Index extends SearchIndex_Recording {
|
||||
function init() {
|
||||
class SearchVariantVersionedTest_Index extends SearchIndex_Recording
|
||||
{
|
||||
public function init()
|
||||
{
|
||||
$this->addClass('SearchVariantVersionedTest_Item');
|
||||
$this->addFilterField('TestText');
|
||||
}
|
||||
}
|
||||
|
||||
class SearchVariantVersionedTest_IndexNoStage extends SearchIndex_Recording {
|
||||
function init() {
|
||||
class SearchVariantVersionedTest_IndexNoStage extends SearchIndex_Recording
|
||||
{
|
||||
public function init()
|
||||
{
|
||||
$this->addClass('SearchVariantVersionedTest_Item');
|
||||
$this->addFilterField('TestText');
|
||||
$this->excludeVariantState(array('SearchVariantVersioned' => 'Stage'));
|
||||
|
@ -3,24 +3,27 @@
|
||||
/**
|
||||
* Test solr 4.0 compatibility
|
||||
*/
|
||||
class Solr4ServiceTest extends SapphireTest {
|
||||
|
||||
class Solr4ServiceTest extends SapphireTest
|
||||
{
|
||||
/**
|
||||
*
|
||||
* @return Solr4ServiceTest_RecordingService
|
||||
*/
|
||||
protected function getMockService() {
|
||||
protected function getMockService()
|
||||
{
|
||||
return new Solr4ServiceTest_RecordingService();
|
||||
}
|
||||
|
||||
protected function getMockDocument($id) {
|
||||
protected function getMockDocument($id)
|
||||
{
|
||||
$document = new Apache_Solr_Document();
|
||||
$document->setField('id', $id);
|
||||
$document->setField('title', "Item $id");
|
||||
return $document;
|
||||
}
|
||||
|
||||
public function testAddDocument() {
|
||||
public function testAddDocument()
|
||||
{
|
||||
$service = $this->getMockService();
|
||||
$sent = $service->addDocument($this->getMockDocument('A'), false);
|
||||
$this->assertEquals(
|
||||
@ -34,7 +37,8 @@ class Solr4ServiceTest extends SapphireTest {
|
||||
);
|
||||
}
|
||||
|
||||
public function testAddDocuments() {
|
||||
public function testAddDocuments()
|
||||
{
|
||||
$service = $this->getMockService();
|
||||
$sent = $service->addDocuments(array(
|
||||
$this->getMockDocument('C'),
|
||||
@ -55,12 +59,15 @@ class Solr4ServiceTest extends SapphireTest {
|
||||
}
|
||||
}
|
||||
|
||||
class Solr4ServiceTest_RecordingService extends Solr4Service_Core {
|
||||
protected function _sendRawPost($url, $rawPost, $timeout = FALSE, $contentType = 'text/xml; charset=UTF-8') {
|
||||
class Solr4ServiceTest_RecordingService extends Solr4Service_Core
|
||||
{
|
||||
protected function _sendRawPost($url, $rawPost, $timeout = false, $contentType = 'text/xml; charset=UTF-8')
|
||||
{
|
||||
return $rawPost;
|
||||
}
|
||||
|
||||
protected function _sendRawGet($url, $timeout = FALSE) {
|
||||
protected function _sendRawGet($url, $timeout = false)
|
||||
{
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,17 @@
|
||||
<?php
|
||||
class SolrIndexTest extends SapphireTest {
|
||||
|
||||
function setUpOnce() {
|
||||
class SolrIndexTest extends SapphireTest
|
||||
{
|
||||
public function setUpOnce()
|
||||
{
|
||||
parent::setUpOnce();
|
||||
|
||||
if (class_exists('Phockito')) Phockito::include_hamcrest();
|
||||
if (class_exists('Phockito')) {
|
||||
Phockito::include_hamcrest();
|
||||
}
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
public function setUp()
|
||||
{
|
||||
if (!class_exists('Phockito')) {
|
||||
$this->markTestSkipped("These tests need the Phockito module installed to run");
|
||||
$this->skipTest = true;
|
||||
@ -16,7 +20,8 @@ class SolrIndexTest extends SapphireTest {
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
function testFieldDataHasOne() {
|
||||
public function testFieldDataHasOne()
|
||||
{
|
||||
$index = new SolrIndexTest_FakeIndex();
|
||||
$data = $index->fieldData('HasOneObject.Field1');
|
||||
$data = $data['SearchUpdaterTest_Container_HasOneObject_Field1'];
|
||||
@ -26,7 +31,8 @@ class SolrIndexTest extends SapphireTest {
|
||||
$this->assertEquals('SearchUpdaterTest_HasOne', $data['class']);
|
||||
}
|
||||
|
||||
function testFieldDataHasMany() {
|
||||
public function testFieldDataHasMany()
|
||||
{
|
||||
$index = new SolrIndexTest_FakeIndex();
|
||||
$data = $index->fieldData('HasManyObjects.Field1');
|
||||
$data = $data['SearchUpdaterTest_Container_HasManyObjects_Field1'];
|
||||
@ -36,7 +42,8 @@ class SolrIndexTest extends SapphireTest {
|
||||
$this->assertEquals('SearchUpdaterTest_HasMany', $data['class']);
|
||||
}
|
||||
|
||||
function testFieldDataManyMany() {
|
||||
public function testFieldDataManyMany()
|
||||
{
|
||||
$index = new SolrIndexTest_FakeIndex();
|
||||
$data = $index->fieldData('ManyManyObjects.Field1');
|
||||
$data = $data['SearchUpdaterTest_Container_ManyManyObjects_Field1'];
|
||||
@ -49,7 +56,8 @@ class SolrIndexTest extends SapphireTest {
|
||||
/**
|
||||
* Test boosting on SearchQuery
|
||||
*/
|
||||
function testBoostedQuery() {
|
||||
public function testBoostedQuery()
|
||||
{
|
||||
$serviceMock = $this->getServiceMock();
|
||||
Phockito::when($serviceMock)->search(anything(), anything(), anything(), anything(), anything())->return($this->getFakeRawSolrResponse());
|
||||
|
||||
@ -70,7 +78,8 @@ class SolrIndexTest extends SapphireTest {
|
||||
/**
|
||||
* Test boosting on field schema (via queried fields parameter)
|
||||
*/
|
||||
public function testBoostedField() {
|
||||
public function testBoostedField()
|
||||
{
|
||||
$serviceMock = $this->getServiceMock();
|
||||
Phockito::when($serviceMock)
|
||||
->search(anything(), anything(), anything(), anything(), anything())
|
||||
@ -92,7 +101,8 @@ class SolrIndexTest extends SapphireTest {
|
||||
->search('+term', anything(), anything(), $matcher, anything());
|
||||
}
|
||||
|
||||
function testHighlightQueryOnBoost() {
|
||||
public function testHighlightQueryOnBoost()
|
||||
{
|
||||
$serviceMock = $this->getServiceMock();
|
||||
Phockito::when($serviceMock)->search(anything(), anything(), anything(), anything(), anything())->return($this->getFakeRawSolrResponse());
|
||||
|
||||
@ -134,7 +144,8 @@ class SolrIndexTest extends SapphireTest {
|
||||
);
|
||||
}
|
||||
|
||||
function testIndexExcludesNullValues() {
|
||||
public function testIndexExcludesNullValues()
|
||||
{
|
||||
$serviceMock = $this->getServiceMock();
|
||||
$index = new SolrIndexTest_FakeIndex();
|
||||
$index->setService($serviceMock);
|
||||
@ -157,7 +168,8 @@ class SolrIndexTest extends SapphireTest {
|
||||
$this->assertEquals('2010-12-30T00:00:00Z', $value['value'], 'Writes non-NULL dates');
|
||||
}
|
||||
|
||||
function testAddFieldExtraOptions() {
|
||||
public function testAddFieldExtraOptions()
|
||||
{
|
||||
Config::inst()->nest();
|
||||
Config::inst()->update('Director', 'environment_type', 'live'); // dev mode sets stored=true for everything
|
||||
|
||||
@ -175,7 +187,8 @@ class SolrIndexTest extends SapphireTest {
|
||||
Config::inst()->unnest();
|
||||
}
|
||||
|
||||
function testAddAnalyzer() {
|
||||
public function testAddAnalyzer()
|
||||
{
|
||||
$index = new SolrIndexTest_FakeIndex();
|
||||
|
||||
$defs = simplexml_load_string('<fields>' . $index->getFieldDefinitions() . '</fields>');
|
||||
@ -191,7 +204,8 @@ class SolrIndexTest extends SapphireTest {
|
||||
$this->assertEquals('solr.HTMLStripCharFilterFactory', $analyzers[0]->charFilter[0]['class']);
|
||||
}
|
||||
|
||||
function testAddCopyField() {
|
||||
public function testAddCopyField()
|
||||
{
|
||||
$index = new SolrIndexTest_FakeIndex();
|
||||
$index->addCopyField('sourceField', 'destField');
|
||||
|
||||
@ -205,7 +219,8 @@ class SolrIndexTest extends SapphireTest {
|
||||
/**
|
||||
* Tests the setting of the 'stored' flag
|
||||
*/
|
||||
public function testStoredFields() {
|
||||
public function testStoredFields()
|
||||
{
|
||||
// Test two fields
|
||||
$index = new SolrIndexTest_FakeIndex2();
|
||||
$index->addStoredField('Field1');
|
||||
@ -238,18 +253,21 @@ class SolrIndexTest extends SapphireTest {
|
||||
/**
|
||||
* @return Solr3Service
|
||||
*/
|
||||
protected function getServiceMock() {
|
||||
protected function getServiceMock()
|
||||
{
|
||||
return Phockito::mock('Solr3Service');
|
||||
}
|
||||
|
||||
protected function getServiceSpy() {
|
||||
protected function getServiceSpy()
|
||||
{
|
||||
$serviceSpy = Phockito::spy('Solr3Service');
|
||||
Phockito::when($serviceSpy)->_sendRawPost()->return($this->getFakeRawSolrResponse());
|
||||
|
||||
return $serviceSpy;
|
||||
}
|
||||
|
||||
protected function getFakeRawSolrResponse() {
|
||||
protected function getFakeRawSolrResponse()
|
||||
{
|
||||
return new Apache_Solr_Response(
|
||||
new Apache_Solr_HttpTransport_Response(
|
||||
null,
|
||||
@ -260,8 +278,10 @@ class SolrIndexTest extends SapphireTest {
|
||||
}
|
||||
}
|
||||
|
||||
class SolrIndexTest_FakeIndex extends SolrIndex {
|
||||
function init() {
|
||||
class SolrIndexTest_FakeIndex extends SolrIndex
|
||||
{
|
||||
public function init()
|
||||
{
|
||||
$this->addClass('SearchUpdaterTest_Container');
|
||||
|
||||
$this->addFilterField('Field1');
|
||||
@ -273,14 +293,16 @@ class SolrIndexTest_FakeIndex extends SolrIndex {
|
||||
}
|
||||
|
||||
|
||||
class SolrIndexTest_FakeIndex2 extends SolrIndex {
|
||||
|
||||
protected function getStoredDefault() {
|
||||
class SolrIndexTest_FakeIndex2 extends SolrIndex
|
||||
{
|
||||
protected function getStoredDefault()
|
||||
{
|
||||
// Override isDev defaulting to stored
|
||||
return 'false';
|
||||
}
|
||||
|
||||
function init() {
|
||||
public function init()
|
||||
{
|
||||
$this->addClass('SearchUpdaterTest_Container');
|
||||
$this->addFilterField('MyDate', 'Date');
|
||||
$this->addFilterField('HasOneObject.Field1');
|
||||
@ -290,18 +312,19 @@ class SolrIndexTest_FakeIndex2 extends SolrIndex {
|
||||
}
|
||||
|
||||
|
||||
class SolrIndexTest_BoostedIndex extends SolrIndex {
|
||||
|
||||
protected function getStoredDefault() {
|
||||
class SolrIndexTest_BoostedIndex extends SolrIndex
|
||||
{
|
||||
protected function getStoredDefault()
|
||||
{
|
||||
// Override isDev defaulting to stored
|
||||
return 'false';
|
||||
}
|
||||
|
||||
function init() {
|
||||
public function init()
|
||||
{
|
||||
$this->addClass('SearchUpdaterTest_Container');
|
||||
$this->addAllFulltextFields();
|
||||
$this->setFieldBoosting('SearchUpdaterTest_Container_Field1', 1.5);
|
||||
$this->addBoostedField('Field2', null, array(), 2.1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
<?php
|
||||
|
||||
if (class_exists('Phockito')) Phockito::include_hamcrest();
|
||||
|
||||
class SolrIndexVersionedTest extends SapphireTest {
|
||||
if (class_exists('Phockito')) {
|
||||
Phockito::include_hamcrest();
|
||||
}
|
||||
|
||||
class SolrIndexVersionedTest extends SapphireTest
|
||||
{
|
||||
protected $oldMode = null;
|
||||
|
||||
protected static $index = null;
|
||||
@ -12,8 +14,8 @@ class SolrIndexVersionedTest extends SapphireTest {
|
||||
'SearchVariantVersionedTest_Item'
|
||||
);
|
||||
|
||||
public function setUp() {
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
if (!class_exists('Phockito')) {
|
||||
@ -27,7 +29,9 @@ class SolrIndexVersionedTest extends SapphireTest {
|
||||
return $this->markTestSkipped('The versioned decorator is not installed');
|
||||
}
|
||||
|
||||
if (self::$index === null) self::$index = singleton('SolrVersionedTest_Index');
|
||||
if (self::$index === null) {
|
||||
self::$index = singleton('SolrVersionedTest_Index');
|
||||
}
|
||||
|
||||
SearchUpdater::bind_manipulation_capture();
|
||||
|
||||
@ -44,23 +48,27 @@ class SolrIndexVersionedTest extends SapphireTest {
|
||||
Versioned::reading_stage('Stage');
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
public function tearDown()
|
||||
{
|
||||
Versioned::set_reading_mode($this->oldMode);
|
||||
Config::unnest();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
protected function getServiceMock() {
|
||||
protected function getServiceMock()
|
||||
{
|
||||
return Phockito::mock('Solr3Service');
|
||||
}
|
||||
|
||||
protected function getExpectedDocumentId($id, $stage) {
|
||||
protected function getExpectedDocumentId($id, $stage)
|
||||
{
|
||||
// Prevent subsites from breaking tests
|
||||
$subsites = class_exists('Subsite') ? '"SearchVariantSubsites":"0",' : '';
|
||||
return $id.'-SiteTree-{'.$subsites.'"SearchVariantVersioned":"'.$stage.'"}';
|
||||
}
|
||||
|
||||
public function testPublishing() {
|
||||
public function testPublishing()
|
||||
{
|
||||
|
||||
// Setup mocks
|
||||
$serviceMock = $this->getServiceMock();
|
||||
@ -92,7 +100,8 @@ class SolrIndexVersionedTest extends SapphireTest {
|
||||
Phockito::verify($serviceMock)->addDocument($doc);
|
||||
}
|
||||
|
||||
public function testDelete() {
|
||||
public function testDelete()
|
||||
{
|
||||
|
||||
// Setup mocks
|
||||
$serviceMock = $this->getServiceMock();
|
||||
@ -130,36 +139,45 @@ class SolrIndexVersionedTest extends SapphireTest {
|
||||
}
|
||||
|
||||
|
||||
class SolrVersionedTest_Index extends SolrIndex {
|
||||
function init() {
|
||||
class SolrVersionedTest_Index extends SolrIndex
|
||||
{
|
||||
public function init()
|
||||
{
|
||||
$this->addClass('SearchVariantVersionedTest_Item');
|
||||
$this->addFilterField('TestText');
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists('Phockito')) return;
|
||||
|
||||
class SolrDocumentMatcher extends Hamcrest_BaseMatcher {
|
||||
if (!class_exists('Phockito')) {
|
||||
return;
|
||||
}
|
||||
|
||||
class SolrDocumentMatcher extends Hamcrest_BaseMatcher
|
||||
{
|
||||
protected $properties;
|
||||
|
||||
public function __construct($properties) {
|
||||
public function __construct($properties)
|
||||
{
|
||||
$this->properties = $properties;
|
||||
}
|
||||
|
||||
public function describeTo(\Hamcrest_Description $description) {
|
||||
public function describeTo(\Hamcrest_Description $description)
|
||||
{
|
||||
$description->appendText('Apache_Solr_Document with properties '.var_export($this->properties, true));
|
||||
}
|
||||
|
||||
public function matches($item) {
|
||||
|
||||
if(! ($item instanceof Apache_Solr_Document)) return false;
|
||||
public function matches($item)
|
||||
{
|
||||
if (! ($item instanceof Apache_Solr_Document)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->properties as $key => $value) {
|
||||
if($item->{$key} != $value) return false;
|
||||
if ($item->{$key} != $value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,8 +3,8 @@
|
||||
/**
|
||||
* Additional tests of solr reindexing processes when run with queuedjobs
|
||||
*/
|
||||
class SolrReindexQueuedTest extends SapphireTest {
|
||||
|
||||
class SolrReindexQueuedTest extends SapphireTest
|
||||
{
|
||||
protected $usesDatabase = true;
|
||||
|
||||
protected $extraDataObjects = array(
|
||||
@ -25,7 +25,8 @@ class SolrReindexQueuedTest extends SapphireTest {
|
||||
*/
|
||||
protected $service = null;
|
||||
|
||||
public function setUp() {
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
if (!class_exists('Phockito')) {
|
||||
@ -59,7 +60,8 @@ class SolrReindexQueuedTest extends SapphireTest {
|
||||
*
|
||||
* @param int $number Number of records to create in each variant
|
||||
*/
|
||||
protected function createDummyData($number) {
|
||||
protected function createDummyData($number)
|
||||
{
|
||||
// Populate dataobjects. Use truncate to generate predictable IDs
|
||||
DB::query('TRUNCATE "SolrReindexTest_Item"');
|
||||
|
||||
@ -80,11 +82,13 @@ class SolrReindexQueuedTest extends SapphireTest {
|
||||
*
|
||||
* @return SolrService
|
||||
*/
|
||||
protected function getServiceMock() {
|
||||
protected function getServiceMock()
|
||||
{
|
||||
return Phockito::mock('Solr4Service');
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
public function tearDown()
|
||||
{
|
||||
FullTextSearch::force_index_list();
|
||||
SolrReindexTest_Variant::disable();
|
||||
parent::tearDown();
|
||||
@ -95,14 +99,16 @@ class SolrReindexQueuedTest extends SapphireTest {
|
||||
*
|
||||
* @return SolrReindexHandler
|
||||
*/
|
||||
protected function getHandler() {
|
||||
protected function getHandler()
|
||||
{
|
||||
return Injector::inst()->get('SolrReindexHandler');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SolrReindexQueuedTest_Service
|
||||
*/
|
||||
protected function getQueuedJobService() {
|
||||
protected function getQueuedJobService()
|
||||
{
|
||||
return singleton('SolrReindexQueuedTest_Service');
|
||||
}
|
||||
|
||||
@ -110,7 +116,8 @@ class SolrReindexQueuedTest extends SapphireTest {
|
||||
* Test that reindex will generate a top top level queued job, and executing this will perform
|
||||
* the necessary initialisation of the grouped queued jobs
|
||||
*/
|
||||
public function testReindexSegmentsGroups() {
|
||||
public function testReindexSegmentsGroups()
|
||||
{
|
||||
$this->createDummyData(18);
|
||||
|
||||
// Create pre-existing jobs
|
||||
@ -163,7 +170,8 @@ class SolrReindexQueuedTest extends SapphireTest {
|
||||
/**
|
||||
* Test index processing on individual groups
|
||||
*/
|
||||
public function testRunGroup() {
|
||||
public function testRunGroup()
|
||||
{
|
||||
$this->createDummyData(18);
|
||||
|
||||
// Just do what the SolrReindexQueuedJob would do to create each sub
|
||||
@ -204,16 +212,18 @@ class SolrReindexQueuedTest extends SapphireTest {
|
||||
}
|
||||
}
|
||||
|
||||
if(!class_exists('QueuedJobService')) return;
|
||||
|
||||
class SolrReindexQueuedTest_Service extends QueuedJobService implements TestOnly {
|
||||
if (!class_exists('QueuedJobService')) {
|
||||
return;
|
||||
}
|
||||
|
||||
class SolrReindexQueuedTest_Service extends QueuedJobService implements TestOnly
|
||||
{
|
||||
/**
|
||||
* @return QueuedJob
|
||||
*/
|
||||
public function getNextJob() {
|
||||
public function getNextJob()
|
||||
{
|
||||
$job = $this->getNextPendingJob();
|
||||
return $this->initialiseJob($job);
|
||||
}
|
||||
|
||||
}
|
@ -5,10 +5,12 @@ use Monolog\Handler\HandlerInterface;
|
||||
use Monolog\Logger;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
if (class_exists('Phockito')) Phockito::include_hamcrest();
|
||||
|
||||
class SolrReindexTest extends SapphireTest {
|
||||
if (class_exists('Phockito')) {
|
||||
Phockito::include_hamcrest();
|
||||
}
|
||||
|
||||
class SolrReindexTest extends SapphireTest
|
||||
{
|
||||
protected $usesDatabase = true;
|
||||
|
||||
protected $extraDataObjects = array(
|
||||
@ -29,7 +31,8 @@ class SolrReindexTest extends SapphireTest {
|
||||
*/
|
||||
protected $service = null;
|
||||
|
||||
public function setUp() {
|
||||
public function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
if (!class_exists('Phockito')) {
|
||||
@ -58,7 +61,8 @@ class SolrReindexTest extends SapphireTest {
|
||||
*
|
||||
* @param int $number Number of records to create in each variant
|
||||
*/
|
||||
protected function createDummyData($number) {
|
||||
protected function createDummyData($number)
|
||||
{
|
||||
// Populate dataobjects. Use truncate to generate predictable IDs
|
||||
DB::query('TRUNCATE "SolrReindexTest_Item"');
|
||||
|
||||
@ -79,11 +83,13 @@ class SolrReindexTest extends SapphireTest {
|
||||
*
|
||||
* @return SolrService
|
||||
*/
|
||||
protected function getServiceMock() {
|
||||
protected function getServiceMock()
|
||||
{
|
||||
return Phockito::mock('Solr4Service');
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
public function tearDown()
|
||||
{
|
||||
FullTextSearch::force_index_list();
|
||||
SolrReindexTest_Variant::disable();
|
||||
parent::tearDown();
|
||||
@ -94,14 +100,16 @@ class SolrReindexTest extends SapphireTest {
|
||||
*
|
||||
* @return SolrReindexHandler
|
||||
*/
|
||||
protected function getHandler() {
|
||||
protected function getHandler()
|
||||
{
|
||||
return Injector::inst()->get('SolrReindexHandler');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the test variant is up and running properly
|
||||
*/
|
||||
public function testVariant() {
|
||||
public function testVariant()
|
||||
{
|
||||
// State defaults to 0
|
||||
$variant = SearchVariant::current_state();
|
||||
$this->assertEquals(
|
||||
@ -147,7 +155,8 @@ class SolrReindexTest extends SapphireTest {
|
||||
*
|
||||
* Test should work fine with any variants (versioned, subsites, etc) specified
|
||||
*/
|
||||
public function testReindexSegmentsGroups() {
|
||||
public function testReindexSegmentsGroups()
|
||||
{
|
||||
$this->createDummyData(120);
|
||||
|
||||
// Initiate re-index
|
||||
@ -197,7 +206,8 @@ class SolrReindexTest extends SapphireTest {
|
||||
/**
|
||||
* Test index processing on individual groups
|
||||
*/
|
||||
public function testRunGroup() {
|
||||
public function testRunGroup()
|
||||
{
|
||||
$this->createDummyData(120);
|
||||
$logger = new SolrReindexTest_RecordingLogger();
|
||||
|
||||
@ -229,7 +239,8 @@ class SolrReindexTest extends SapphireTest {
|
||||
/**
|
||||
* Test that running all groups covers the entire range of dataobject IDs
|
||||
*/
|
||||
public function testRunAllGroups() {
|
||||
public function testRunAllGroups()
|
||||
{
|
||||
$this->createDummyData(120);
|
||||
$logger = new SolrReindexTest_RecordingLogger();
|
||||
|
||||
@ -276,8 +287,8 @@ class SolrReindexTest extends SapphireTest {
|
||||
/**
|
||||
* Provides a wrapper for testing SolrReindexBase
|
||||
*/
|
||||
class SolrReindexTest_TestHandler extends SolrReindexBase {
|
||||
|
||||
class SolrReindexTest_TestHandler extends SolrReindexBase
|
||||
{
|
||||
public function processGroup(
|
||||
LoggerInterface $logger, SolrIndex $indexInstance, $state, $class, $groups, $group, $taskName
|
||||
) {
|
||||
@ -286,15 +297,17 @@ class SolrReindexTest_TestHandler extends SolrReindexBase {
|
||||
$logger->info("Called processGroup with {$indexName}, {$stateName}, {$class}, group {$group} of {$groups}");
|
||||
}
|
||||
|
||||
public function triggerReindex(LoggerInterface $logger, $batchSize, $taskName, $classes = null) {
|
||||
public function triggerReindex(LoggerInterface $logger, $batchSize, $taskName, $classes = null)
|
||||
{
|
||||
$logger->info("Called triggerReindex");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class SolrReindexTest_Index extends SolrIndex implements TestOnly {
|
||||
public function init() {
|
||||
class SolrReindexTest_Index extends SolrIndex implements TestOnly
|
||||
{
|
||||
public function init()
|
||||
{
|
||||
$this->addClass('SolrReindexTest_Item');
|
||||
$this->addAllFulltextFields();
|
||||
}
|
||||
@ -303,8 +316,8 @@ class SolrReindexTest_Index extends SolrIndex implements TestOnly {
|
||||
/**
|
||||
* Does not have any variant extensions
|
||||
*/
|
||||
class SolrReindexTest_Item extends DataObject implements TestOnly {
|
||||
|
||||
class SolrReindexTest_Item extends DataObject implements TestOnly
|
||||
{
|
||||
private static $extensions = array(
|
||||
'SolrReindexTest_ItemExtension'
|
||||
);
|
||||
@ -313,21 +326,21 @@ class SolrReindexTest_Item extends DataObject implements TestOnly {
|
||||
'Title' => 'Varchar(255)',
|
||||
'Variant' => 'Int(0)'
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Select only records in the current variant
|
||||
*/
|
||||
class SolrReindexTest_ItemExtension extends DataExtension implements TestOnly {
|
||||
|
||||
class SolrReindexTest_ItemExtension extends DataExtension implements TestOnly
|
||||
{
|
||||
/**
|
||||
* Filter records on the current variant
|
||||
*
|
||||
* @param SQLQuery $query
|
||||
* @param DataQuery $dataQuery
|
||||
*/
|
||||
public function augmentSQL(SQLQuery &$query, DataQuery &$dataQuery = null) {
|
||||
public function augmentSQL(SQLQuery &$query, DataQuery &$dataQuery = null)
|
||||
{
|
||||
$variant = SolrReindexTest_Variant::get_current();
|
||||
if ($variant !== null && !$query->filtersOnID()) {
|
||||
$sqlVariant = Convert::raw2sql($variant);
|
||||
@ -342,8 +355,8 @@ class SolrReindexTest_ItemExtension extends DataExtension implements TestOnly {
|
||||
*
|
||||
* Variant states are 0 and 1, or null if disabled
|
||||
*/
|
||||
class SolrReindexTest_Variant extends SearchVariant implements TestOnly {
|
||||
|
||||
class SolrReindexTest_Variant extends SearchVariant implements TestOnly
|
||||
{
|
||||
/**
|
||||
* Value of this variant (either null, 0, or 1)
|
||||
*
|
||||
@ -354,7 +367,8 @@ class SolrReindexTest_Variant extends SearchVariant implements TestOnly {
|
||||
/**
|
||||
* Activate this variant
|
||||
*/
|
||||
public static function enable() {
|
||||
public static function enable()
|
||||
{
|
||||
self::disable();
|
||||
|
||||
self::$current = 0;
|
||||
@ -366,14 +380,16 @@ class SolrReindexTest_Variant extends SearchVariant implements TestOnly {
|
||||
/**
|
||||
* Disable this variant and reset
|
||||
*/
|
||||
public static function disable() {
|
||||
public static function disable()
|
||||
{
|
||||
self::$current = null;
|
||||
self::$variants = null;
|
||||
self::$class_variants = array();
|
||||
self::$call_instances = array();
|
||||
}
|
||||
|
||||
public function activateState($state) {
|
||||
public function activateState($state)
|
||||
{
|
||||
self::set_current($state);
|
||||
}
|
||||
|
||||
@ -382,7 +398,8 @@ class SolrReindexTest_Variant extends SearchVariant implements TestOnly {
|
||||
*
|
||||
* @param int $current 0, 1, 2, or null (disabled)
|
||||
*/
|
||||
public static function set_current($current) {
|
||||
public static function set_current($current)
|
||||
{
|
||||
self::$current = $current;
|
||||
}
|
||||
|
||||
@ -391,14 +408,16 @@ class SolrReindexTest_Variant extends SearchVariant implements TestOnly {
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public static function get_current() {
|
||||
public static function get_current()
|
||||
{
|
||||
// Always use string values for states for consistent json_encode value
|
||||
if (isset(self::$current)) {
|
||||
return (string)self::$current;
|
||||
}
|
||||
}
|
||||
|
||||
function alterDefinition($base, $index) {
|
||||
public function alterDefinition($base, $index)
|
||||
{
|
||||
$self = get_class($this);
|
||||
|
||||
$index->filterFields['_testvariant'] = array(
|
||||
@ -412,43 +431,48 @@ class SolrReindexTest_Variant extends SearchVariant implements TestOnly {
|
||||
);
|
||||
}
|
||||
|
||||
public function alterQuery($query, $index) {
|
||||
public function alterQuery($query, $index)
|
||||
{
|
||||
// I guess just calling it _testvariant is ok?
|
||||
$query->filter('_testvariant', $this->currentState());
|
||||
}
|
||||
|
||||
public function appliesTo($class, $includeSubclasses) {
|
||||
public function appliesTo($class, $includeSubclasses)
|
||||
{
|
||||
return $class === 'SolrReindexTest_Item' ||
|
||||
($includeSubclasses && is_subclass_of($class, 'SolrReindexTest_Item', true));
|
||||
}
|
||||
|
||||
public function appliesToEnvironment() {
|
||||
public function appliesToEnvironment()
|
||||
{
|
||||
// Set to null to disable
|
||||
return self::$current !== null;
|
||||
}
|
||||
|
||||
public function currentState() {
|
||||
public function currentState()
|
||||
{
|
||||
return self::get_current();
|
||||
}
|
||||
|
||||
public function reindexStates() {
|
||||
public function reindexStates()
|
||||
{
|
||||
// Always use string values for states for consistent json_encode value
|
||||
return array('0', '1', '2');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test logger for recording messages
|
||||
*/
|
||||
class SolrReindexTest_RecordingLogger extends Logger implements TestOnly {
|
||||
|
||||
class SolrReindexTest_RecordingLogger extends Logger implements TestOnly
|
||||
{
|
||||
/**
|
||||
* @var SolrReindexTest_Handler
|
||||
*/
|
||||
protected $testHandler = null;
|
||||
|
||||
public function __construct($name = 'testlogger', array $handlers = array(), array $processors = array()) {
|
||||
public function __construct($name = 'testlogger', array $handlers = array(), array $processors = array())
|
||||
{
|
||||
parent::__construct($name, $handlers, $processors);
|
||||
|
||||
$this->testHandler = new SolrReindexTest_Handler();
|
||||
@ -458,14 +482,16 @@ class SolrReindexTest_RecordingLogger extends Logger implements TestOnly {
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMessages() {
|
||||
public function getMessages()
|
||||
{
|
||||
return $this->testHandler->getMessages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all messages
|
||||
*/
|
||||
public function clear() {
|
||||
public function clear()
|
||||
{
|
||||
$this->testHandler->clear();
|
||||
}
|
||||
|
||||
@ -475,7 +501,8 @@ class SolrReindexTest_RecordingLogger extends Logger implements TestOnly {
|
||||
* @param string $containing
|
||||
* @return array Filtered array
|
||||
*/
|
||||
public function filterMessages($containing) {
|
||||
public function filterMessages($containing)
|
||||
{
|
||||
return array_values(array_filter(
|
||||
$this->getMessages(),
|
||||
function ($content) use ($containing) {
|
||||
@ -490,7 +517,8 @@ class SolrReindexTest_RecordingLogger extends Logger implements TestOnly {
|
||||
* @param string $containing Message to filter by
|
||||
* @return int
|
||||
*/
|
||||
public function countMessages($containing = null) {
|
||||
public function countMessages($containing = null)
|
||||
{
|
||||
if ($containing) {
|
||||
$messages = $this->filterMessages($containing);
|
||||
} else {
|
||||
@ -503,8 +531,8 @@ class SolrReindexTest_RecordingLogger extends Logger implements TestOnly {
|
||||
/**
|
||||
* Logger for recording messages for later retrieval
|
||||
*/
|
||||
class SolrReindexTest_Handler extends AbstractProcessingHandler implements TestOnly {
|
||||
|
||||
class SolrReindexTest_Handler extends AbstractProcessingHandler implements TestOnly
|
||||
{
|
||||
/**
|
||||
* Messages
|
||||
*
|
||||
@ -517,15 +545,18 @@ class SolrReindexTest_Handler extends AbstractProcessingHandler implements TestO
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMessages() {
|
||||
public function getMessages()
|
||||
{
|
||||
return $this->messages;
|
||||
}
|
||||
|
||||
public function clear() {
|
||||
public function clear()
|
||||
{
|
||||
$this->messages = array();
|
||||
}
|
||||
|
||||
protected function write(array $record) {
|
||||
protected function write(array $record)
|
||||
{
|
||||
$this->messages[] = $record['message'];
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user