Compare commits

..

No commits in common. "3" and "3.9.0-beta1" have entirely different histories.

60 changed files with 407 additions and 544 deletions

View File

@ -1,15 +0,0 @@
name: CI
on:
push:
pull_request:
workflow_dispatch:
jobs:
ci:
name: CI
uses: silverstripe/gha-ci/.github/workflows/ci.yml@v1
with:
extra_jobs: |
- name_suffix: subsites
composer_require_extra: silverstripe/subsites:^2

View File

@ -1,16 +0,0 @@
name: Dispatch CI
on:
# At 2:10 PM UTC, only on Wednesday and Thursday
schedule:
- cron: '10 14 * * 3,4'
jobs:
dispatch-ci:
name: Dispatch CI
# Only run cron on the silverstripe account
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
runs-on: ubuntu-latest
steps:
- name: Dispatch CI
uses: silverstripe/gha-dispatch-ci@v1

View File

@ -1,17 +0,0 @@
name: Keepalive
on:
workflow_dispatch:
# The 4th of every month at 10:50am UTC
schedule:
- cron: '50 10 4 * *'
jobs:
keepalive:
name: Keepalive
# Only run cron on the silverstripe account
if: (github.event_name == 'schedule' && github.repository_owner == 'silverstripe') || (github.event_name != 'schedule')
runs-on: ubuntu-latest
steps:
- name: Keepalive
uses: silverstripe/gha-keepalive@v1

15
.scrutinizer.yml Normal file
View File

@ -0,0 +1,15 @@
inherit: true
build:
nodes:
analysis:
tests:
override: [php-scrutinizer-run]
checks:
php:
code_rating: true
duplication: true
filter:
paths: [src/*, tests/*]

18
.travis.yml Normal file
View File

@ -0,0 +1,18 @@
version: ~> 1.0
import:
- silverstripe/silverstripe-travis-shared:config/provision/standard-jobs-range.yml
env:
global:
- COMPOSER_ROOT_VERSION="3.x-dev"
- REQUIRE_EXTRA="symbiote/silverstripe-queuedjobs:^4.0"
jobs:
include:
- php: 7.3
env:
- DB=MYSQL
- REQUIRE_INSTALLER=4.x-dev
- PHPUNIT_TEST=1
- REQUIRE_EXTRA="symbiote/silverstripe-queuedjobs:^4 silverstripe/subsites:^2"

View File

@ -1,8 +0,0 @@
[main]
host = https://www.transifex.com
[o:silverstripe:p:silverstripe-fulltextsearch:r:master]
file_filter = lang/<lang>.yml
source_file = lang/en.yml
source_lang = en
type = YML

View File

@ -1,8 +1,11 @@
# FullTextSearch module # FullTextSearch module
[![CI](https://github.com/silverstripe/silverstripe-fulltextsearch/actions/workflows/ci.yml/badge.svg)](https://github.com/silverstripe/silverstripe-fulltextsearch/actions/workflows/ci.yml) [![Build Status](https://api.travis-ci.com/silverstripe/silverstripe-fulltextsearch.svg?branch=3)](https://travis-ci.com/silverstripe/silverstripe-fulltextsearch)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/silverstripe/silverstripe-fulltextsearch/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/silverstripe/silverstripe-fulltextsearch/?branch=master)
[![codecov](https://codecov.io/gh/silverstripe/silverstripe-fulltextsearch/branch/master/graph/badge.svg)](https://codecov.io/gh/silverstripe/silverstripe-fulltextsearch)
[![SilverStripe supported module](https://img.shields.io/badge/silverstripe-supported-0071C4.svg)](https://www.silverstripe.org/software/addons/silverstripe-commercially-supported-module-list/)
Adds support for fulltext search engines like Sphinx and Solr to Silverstripe CMS. Adds support for fulltext search engines like Sphinx and Solr to SilverStripe CMS.
Compatible with PHP 7.2 Compatible with PHP 7.2
## Important notes when upgrading to fulltextsearch 3.7.0+ ## Important notes when upgrading to fulltextsearch 3.7.0+
@ -83,9 +86,9 @@ it's recommended you update your implementation to call `SearchableService::isVi
## Requirements ## Requirements
* Silverstripe 4.0+ * SilverStripe 4.0+
**Note:** For Silverstripe 3.x, please use the [2.x release line](https://github.com/silverstripe/silverstripe-fulltextsearch/tree/2). **Note:** For SilverStripe 3.x, please use the [2.x release line](https://github.com/silverstripe/silverstripe-fulltextsearch/tree/2).
## Documentation ## Documentation

5
_config.php Normal file
View File

@ -0,0 +1,5 @@
<?php
use SilverStripe\Dev\Deprecation;
Deprecation::notification_version('3.0', 'silverstripe/fulltextsearch');

View File

@ -21,19 +21,17 @@
} }
], ],
"require": { "require": {
"php": "^7.4 || ^8.0", "php": "^7.3 || ^8.0",
"silverstripe/framework": "^4.10", "silverstripe/framework": "^4.10",
"monolog/monolog": "~1.15", "monolog/monolog": "~1.15",
"silverstripe/solr-php-client": "^1.0", "ptcinc/solr-php-client": "^1.0",
"symfony/process": "^3.4 || ^4", "symfony/process": "^3.2",
"tractorcow/silverstripe-proxy-db": "^1", "tractorcow/silverstripe-proxy-db": "~0.1"
"ext-curl": "*"
}, },
"require-dev": { "require-dev": {
"silverstripe/cms": "^4.0", "silverstripe/cms": "^4.0",
"phpunit/phpunit": "^9.5", "phpunit/phpunit": "^9.5",
"squizlabs/php_codesniffer": "^3.0", "squizlabs/php_codesniffer": "^3.0"
"symbiote/silverstripe-queuedjobs": "^4.9"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

View File

@ -16,9 +16,9 @@
limitations under the License. limitations under the License.
--> -->
<!-- <!--
For more details about configurations options that may appear in For more details about configurations options that may appear in
this file, see http://wiki.apache.org/solr/SolrConfigXml. this file, see http://wiki.apache.org/solr/SolrConfigXml.
--> -->
<config> <config>
<!-- In all configuration below, a prefix of "solr." for class names <!-- In all configuration below, a prefix of "solr." for class names
@ -46,19 +46,19 @@
instanceDir. instanceDir.
Please note that <lib/> directives are processed in the order Please note that <lib/> directives are processed in the order
that they appear in your solrconfig.xml file, and are "stacked" that they appear in your solrconfig.xml file, and are "stacked"
on top of each other when building a ClassLoader - so if you have on top of each other when building a ClassLoader - so if you have
plugin jars with dependencies on other jars, the "lower level" plugin jars with dependencies on other jars, the "lower level"
dependency jars should be loaded first. dependency jars should be loaded first.
If a "./lib" directory exists in your instanceDir, all files If a "./lib" directory exists in your instanceDir, all files
found in it are included as if you had used the following found in it are included as if you had used the following
syntax... syntax...
<lib dir="./lib" /> <lib dir="./lib" />
--> -->
<!-- A 'dir' option by itself adds any files found in the directory <!-- A 'dir' option by itself adds any files found in the directory
to the classpath, this is useful for including all jars in a to the classpath, this is useful for including all jars in a
directory. directory.
@ -69,11 +69,11 @@
If a 'dir' option (with or without a regex) is used and nothing If a 'dir' option (with or without a regex) is used and nothing
is found that matches, a warning will be logged. is found that matches, a warning will be logged.
The examples below can be used to load some solr-contribs along The examples below can be used to load some solr-contribs along
with their external dependencies. with their external dependencies.
--> -->
<lib dir="./lib" /> <lib dir="./lib" />
<lib dir="../../../contrib/extraction/lib" regex=".*\.jar" /> <lib dir="../../../contrib/extraction/lib" regex=".*\.jar" />
<lib dir="../../../dist/" regex="solr-cell-\d.*\.jar" /> <lib dir="../../../dist/" regex="solr-cell-\d.*\.jar" />
@ -86,14 +86,14 @@
<lib dir="../../../contrib/velocity/lib" regex=".*\.jar" /> <lib dir="../../../contrib/velocity/lib" regex=".*\.jar" />
<lib dir="../../../dist/" regex="solr-velocity-\d.*\.jar" /> <lib dir="../../../dist/" regex="solr-velocity-\d.*\.jar" />
<!-- an exact 'path' can be used instead of a 'dir' to specify a <!-- an exact 'path' can be used instead of a 'dir' to specify a
specific jar file. This will cause a serious error to be logged specific jar file. This will cause a serious error to be logged
if it can't be loaded. if it can't be loaded.
--> -->
<!-- <!--
<lib path="../a-jar-that-does-not-exist.jar" /> <lib path="../a-jar-that-does-not-exist.jar" />
--> -->
<!-- Data Directory <!-- Data Directory
Used to specify an alternate directory to hold all index data Used to specify an alternate directory to hold all index data
@ -105,7 +105,7 @@
<!-- The DirectoryFactory to use for indexes. <!-- The DirectoryFactory to use for indexes.
solr.StandardDirectoryFactory is filesystem solr.StandardDirectoryFactory is filesystem
based and tries to pick the best implementation for the current based and tries to pick the best implementation for the current
JVM and platform. solr.NRTCachingDirectoryFactory, the default, JVM and platform. solr.NRTCachingDirectoryFactory, the default,
@ -118,8 +118,8 @@
solr.RAMDirectoryFactory is memory based, not solr.RAMDirectoryFactory is memory based, not
persistent, and doesn't work with replication. persistent, and doesn't work with replication.
--> -->
<directoryFactory name="DirectoryFactory" <directoryFactory name="DirectoryFactory"
class="${solr.directoryFactory:solr.NRTCachingDirectoryFactory}"/> class="${solr.directoryFactory:solr.NRTCachingDirectoryFactory}"/>
<!-- The CodecFactory for defining the format of the inverted index. <!-- The CodecFactory for defining the format of the inverted index.
The default implementation is SchemaCodecFactory, which is the official Lucene The default implementation is SchemaCodecFactory, which is the official Lucene
@ -133,24 +133,24 @@
<codecFactory class="solr.SchemaCodecFactory"/> <codecFactory class="solr.SchemaCodecFactory"/>
<!-- To enable dynamic schema REST APIs, use the following for <schemaFactory>: <!-- To enable dynamic schema REST APIs, use the following for <schemaFactory>:
<schemaFactory class="ManagedIndexSchemaFactory"> <schemaFactory class="ManagedIndexSchemaFactory">
<bool name="mutable">true</bool> <bool name="mutable">true</bool>
<str name="managedSchemaResourceName">managed-schema</str> <str name="managedSchemaResourceName">managed-schema</str>
</schemaFactory> </schemaFactory>
When ManagedIndexSchemaFactory is specified, Solr will load the schema from When ManagedIndexSchemaFactory is specified, Solr will load the schema from
he resource named in 'managedSchemaResourceName', rather than from schema.xml. he resource named in 'managedSchemaResourceName', rather than from schema.xml.
Note that the managed schema resource CANNOT be named schema.xml. If the managed Note that the managed schema resource CANNOT be named schema.xml. If the managed
schema does not exist, Solr will create it after reading schema.xml, then rename schema does not exist, Solr will create it after reading schema.xml, then rename
'schema.xml' to 'schema.xml.bak'. 'schema.xml' to 'schema.xml.bak'.
Do NOT hand edit the managed schema - external modifications will be ignored and Do NOT hand edit the managed schema - external modifications will be ignored and
overwritten as a result of schema modification REST API calls. overwritten as a result of schema modification REST API calls.
When ManagedIndexSchemaFactory is specified with mutable = true, schema When ManagedIndexSchemaFactory is specified with mutable = true, schema
modification REST API calls will be allowed; otherwise, error responses will be modification REST API calls will be allowed; otherwise, error responses will be
sent back for these requests. sent back for these requests.
--> -->
<schemaFactory class="ClassicIndexSchemaFactory"/> <schemaFactory class="ClassicIndexSchemaFactory"/>
@ -158,12 +158,12 @@
Index Config - These settings control low-level behavior of indexing Index Config - These settings control low-level behavior of indexing
Most example settings here show the default value, but are commented Most example settings here show the default value, but are commented
out, to more easily see where customizations have been made. out, to more easily see where customizations have been made.
Note: This replaces <indexDefaults> and <mainIndex> from older versions Note: This replaces <indexDefaults> and <mainIndex> from older versions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<indexConfig> <indexConfig>
<!-- maxFieldLength was removed in 4.0. To get similar behavior, include a <!-- maxFieldLength was removed in 4.0. To get similar behavior, include a
LimitTokenCountFilterFactory in your fieldType definition. E.g. LimitTokenCountFilterFactory in your fieldType definition. E.g.
<filter class="solr.LimitTokenCountFilterFactory" maxTokenCount="10000"/> <filter class="solr.LimitTokenCountFilterFactory" maxTokenCount="10000"/>
--> -->
<!-- Maximum time to wait for a write lock (ms) for an IndexWriter. Default: 1000 --> <!-- Maximum time to wait for a write lock (ms) for an IndexWriter. Default: 1000 -->
@ -175,8 +175,8 @@
Default in Solr/Lucene is 8. --> Default in Solr/Lucene is 8. -->
<!-- <maxIndexingThreads>8</maxIndexingThreads> --> <!-- <maxIndexingThreads>8</maxIndexingThreads> -->
<!-- Expert: Enabling compound file will use less files for the index, <!-- Expert: Enabling compound file will use less files for the index,
using fewer file descriptors on the expense of performance decrease. using fewer file descriptors on the expense of performance decrease.
Default in Lucene is "true". Default in Solr is "false" (since 3.6) --> Default in Lucene is "true". Default in Solr is "false" (since 3.6) -->
<!-- <useCompoundFile>false</useCompoundFile> --> <!-- <useCompoundFile>false</useCompoundFile> -->
@ -191,7 +191,7 @@
<!-- <ramBufferSizeMB>100</ramBufferSizeMB> --> <!-- <ramBufferSizeMB>100</ramBufferSizeMB> -->
<!-- <maxBufferedDocs>1000</maxBufferedDocs> --> <!-- <maxBufferedDocs>1000</maxBufferedDocs> -->
<!-- Expert: Merge Policy <!-- Expert: Merge Policy
The Merge Policy in Lucene controls how merging of segments is done. The Merge Policy in Lucene controls how merging of segments is done.
The default since Solr/Lucene 3.3 is TieredMergePolicy. The default since Solr/Lucene 3.3 is TieredMergePolicy.
The default since Lucene 2.3 was the LogByteSizeMergePolicy, The default since Lucene 2.3 was the LogByteSizeMergePolicy,
@ -203,7 +203,7 @@
<int name="segmentsPerTier">10</int> <int name="segmentsPerTier">10</int>
</mergePolicy> </mergePolicy>
--> -->
<!-- Merge Factor <!-- Merge Factor
The merge factor controls how many segments will get merged at a time. The merge factor controls how many segments will get merged at a time.
For TieredMergePolicy, mergeFactor is a convenience parameter which For TieredMergePolicy, mergeFactor is a convenience parameter which
@ -212,7 +212,7 @@
will be allowed before they are merged into one. will be allowed before they are merged into one.
Default is 10 for both merge policies. Default is 10 for both merge policies.
--> -->
<!-- <!--
<mergeFactor>10</mergeFactor> <mergeFactor>10</mergeFactor>
--> -->
@ -222,15 +222,15 @@
can perform merges in the background using separate threads. can perform merges in the background using separate threads.
The SerialMergeScheduler (Lucene 2.2 default) does not. The SerialMergeScheduler (Lucene 2.2 default) does not.
--> -->
<!-- <!--
<mergeScheduler class="org.apache.lucene.index.ConcurrentMergeScheduler"/> <mergeScheduler class="org.apache.lucene.index.ConcurrentMergeScheduler"/>
--> -->
<!-- LockFactory <!-- LockFactory
This option specifies which Lucene LockFactory implementation This option specifies which Lucene LockFactory implementation
to use. to use.
single = SingleInstanceLockFactory - suggested for a single = SingleInstanceLockFactory - suggested for a
read-only index or when there is no possibility of read-only index or when there is no possibility of
another process trying to modify the index. another process trying to modify the index.
@ -259,7 +259,7 @@
<!-- <!--
<unlockOnStartup>false</unlockOnStartup> <unlockOnStartup>false</unlockOnStartup>
--> -->
<!-- Expert: Controls how often Lucene loads terms into memory <!-- Expert: Controls how often Lucene loads terms into memory
Default is 128 and is likely good for most everyone. Default is 128 and is likely good for most everyone.
--> -->
@ -268,7 +268,7 @@
<!-- If true, IndexReaders will be reopened (often more efficient) <!-- If true, IndexReaders will be reopened (often more efficient)
instead of closed and then opened. Default: true instead of closed and then opened. Default: true
--> -->
<!-- <!--
<reopenReaders>true</reopenReaders> <reopenReaders>true</reopenReaders>
--> -->
@ -279,11 +279,11 @@
The default Solr IndexDeletionPolicy implementation supports The default Solr IndexDeletionPolicy implementation supports
deleting index commit points on number of commits, age of deleting index commit points on number of commits, age of
commit point and optimized status. commit point and optimized status.
The latest commit point should always be preserved regardless The latest commit point should always be preserved regardless
of the criteria. of the criteria.
--> -->
<!-- <!--
<deletionPolicy class="solr.SolrDeletionPolicy"> <deletionPolicy class="solr.SolrDeletionPolicy">
--> -->
<!-- The number of commit points to be kept --> <!-- The number of commit points to be kept -->
@ -298,12 +298,12 @@
<str name="maxCommitAge">30MINUTES</str> <str name="maxCommitAge">30MINUTES</str>
<str name="maxCommitAge">1DAY</str> <str name="maxCommitAge">1DAY</str>
--> -->
<!-- <!--
</deletionPolicy> </deletionPolicy>
--> -->
<!-- Lucene Infostream <!-- Lucene Infostream
To aid in advanced debugging, Lucene provides an "InfoStream" To aid in advanced debugging, Lucene provides an "InfoStream"
of detailed information when indexing. of detailed information when indexing.
@ -316,7 +316,7 @@
<!-- JMX <!-- JMX
This example enables JMX if and only if an existing MBeanServer This example enables JMX if and only if an existing MBeanServer
is found, use this if you want to configure JMX through JVM is found, use this if you want to configure JMX through JVM
parameters. Remove this to disable exposing Solr configuration parameters. Remove this to disable exposing Solr configuration
@ -326,7 +326,7 @@
--> -->
<jmx /> <jmx />
<!-- If you want to connect to a particular server, specify the <!-- If you want to connect to a particular server, specify the
agentId agentId
--> -->
<!-- <jmx agentId="myAgent" /> --> <!-- <jmx agentId="myAgent" /> -->
<!-- If you want to start a new MBeanServer, specify the serviceUrl --> <!-- If you want to start a new MBeanServer, specify the serviceUrl -->
@ -341,16 +341,16 @@
uncommitted changes to the index, so use of a hard autoCommit uncommitted changes to the index, so use of a hard autoCommit
is recommended (see below). is recommended (see below).
"dir" - the target directory for transaction logs, defaults to the "dir" - the target directory for transaction logs, defaults to the
solr data directory. --> solr data directory. -->
<updateLog> <updateLog>
<str name="dir">${solr.ulog.dir:}</str> <str name="dir">${solr.ulog.dir:}</str>
</updateLog> </updateLog>
<!-- AutoCommit <!-- AutoCommit
Perform a hard commit automatically under certain conditions. Perform a hard commit automatically under certain conditions.
Instead of enabling autoCommit, consider using "commitWithin" Instead of enabling autoCommit, consider using "commitWithin"
when adding documents. when adding documents.
http://wiki.apache.org/solr/UpdateXmlMessages http://wiki.apache.org/solr/UpdateXmlMessages
@ -359,7 +359,7 @@
maxTime - Maximum amount of time in ms that is allowed to pass maxTime - Maximum amount of time in ms that is allowed to pass
since a document was added before automatically since a document was added before automatically
triggering a new commit. triggering a new commit.
openSearcher - if false, the commit causes recent index changes openSearcher - if false, the commit causes recent index changes
to be flushed to stable storage, but does not cause a new to be flushed to stable storage, but does not cause a new
searcher to be opened to make those changes visible. searcher to be opened to make those changes visible.
@ -367,9 +367,9 @@
If the updateLog is enabled, then it's highly recommended to If the updateLog is enabled, then it's highly recommended to
have some sort of hard autoCommit to limit the log size. have some sort of hard autoCommit to limit the log size.
--> -->
<autoCommit> <autoCommit>
<maxTime>${solr.autoCommit.maxTime:15000}</maxTime> <maxTime>${solr.autoCommit.maxTime:15000}</maxTime>
<openSearcher>false</openSearcher> <openSearcher>false</openSearcher>
</autoCommit> </autoCommit>
<!-- softAutoCommit is like autoCommit except it causes a <!-- softAutoCommit is like autoCommit except it causes a
@ -378,12 +378,12 @@
faster and more near-realtime friendly than a hard commit. faster and more near-realtime friendly than a hard commit.
--> -->
<autoSoftCommit> <autoSoftCommit>
<maxTime>${solr.autoSoftCommit.maxTime:60000}</maxTime> <maxTime>${solr.autoSoftCommit.maxTime:-1}</maxTime>
</autoSoftCommit> </autoSoftCommit>
<!-- Update Related Event Listeners <!-- Update Related Event Listeners
Various IndexWriter related events can trigger Listeners to Various IndexWriter related events can trigger Listeners to
take actions. take actions.
@ -392,10 +392,10 @@
--> -->
<!-- The RunExecutableListener executes an external command from a <!-- The RunExecutableListener executes an external command from a
hook such as postCommit or postOptimize. hook such as postCommit or postOptimize.
exe - the name of the executable to run exe - the name of the executable to run
dir - dir to use as the current working directory. (default=".") dir - dir to use as the current working directory. (default=".")
wait - the calling thread waits until the executable returns. wait - the calling thread waits until the executable returns.
(default="true") (default="true")
args - the arguments to pass to the program. (default is none) args - the arguments to pass to the program. (default is none)
env - environment variables to set. (default is none) env - environment variables to set. (default is none)
@ -415,7 +415,7 @@
--> -->
</updateHandler> </updateHandler>
<!-- IndexReaderFactory <!-- IndexReaderFactory
Use the following format to specify a custom IndexReaderFactory, Use the following format to specify a custom IndexReaderFactory,
@ -447,7 +447,7 @@
be specified. be specified.
--> -->
<!-- <!--
<indexReaderFactory name="IndexReaderFactory" <indexReaderFactory name="IndexReaderFactory"
class="solr.StandardIndexReaderFactory"> class="solr.StandardIndexReaderFactory">
<int name="setTermIndexDivisor">12</int> <int name="setTermIndexDivisor">12</int>
</indexReaderFactory > </indexReaderFactory >
@ -463,12 +463,12 @@
is thrown if exceeded. is thrown if exceeded.
** WARNING ** ** WARNING **
This option actually modifies a global Lucene property that This option actually modifies a global Lucene property that
will affect all SolrCores. If multiple solrconfig.xml files will affect all SolrCores. If multiple solrconfig.xml files
disagree on this property, the value at any given moment will disagree on this property, the value at any given moment will
be based on the last SolrCore to be initialized. be based on the last SolrCore to be initialized.
--> -->
<maxBooleanClauses>1024</maxBooleanClauses> <maxBooleanClauses>1024</maxBooleanClauses>
@ -477,7 +477,7 @@
There are two implementations of cache available for Solr, There are two implementations of cache available for Solr,
LRUCache, based on a synchronized LinkedHashMap, and LRUCache, based on a synchronized LinkedHashMap, and
FastLRUCache, based on a ConcurrentHashMap. FastLRUCache, based on a ConcurrentHashMap.
FastLRUCache has faster gets and slower puts in single FastLRUCache has faster gets and slower puts in single
threaded operation and thus is generally faster than LRUCache threaded operation and thus is generally faster than LRUCache
@ -502,7 +502,7 @@
initialSize - the initial capacity (number of entries) of initialSize - the initial capacity (number of entries) of
the cache. (see java.util.HashMap) the cache. (see java.util.HashMap)
autowarmCount - the number of entries to prepopulate from autowarmCount - the number of entries to prepopulate from
and old cache. and old cache.
--> -->
<filterCache class="solr.FastLRUCache" <filterCache class="solr.FastLRUCache"
size="512" size="512"
@ -510,28 +510,28 @@
autowarmCount="0"/> autowarmCount="0"/>
<!-- Query Result Cache <!-- Query Result Cache
Caches results of searches - ordered lists of document ids Caches results of searches - ordered lists of document ids
(DocList) based on a query, a sort, and the range of documents requested. (DocList) based on a query, a sort, and the range of documents requested.
--> -->
<queryResultCache class="solr.LRUCache" <queryResultCache class="solr.LRUCache"
size="512" size="512"
initialSize="512" initialSize="512"
autowarmCount="0"/> autowarmCount="0"/>
<!-- Document Cache <!-- Document Cache
Caches Lucene Document objects (the stored fields for each Caches Lucene Document objects (the stored fields for each
document). Since Lucene internal document ids are transient, document). Since Lucene internal document ids are transient,
this cache will not be autowarmed. this cache will not be autowarmed.
--> -->
<documentCache class="solr.LRUCache" <documentCache class="solr.LRUCache"
size="512" size="512"
initialSize="512" initialSize="512"
autowarmCount="0"/> autowarmCount="0"/>
<!-- Field Value Cache <!-- Field Value Cache
Cache used to hold field values that are quickly accessible Cache used to hold field values that are quickly accessible
by document id. The fieldValueCache is created by default by document id. The fieldValueCache is created by default
even if not configured here. even if not configured here.
@ -549,8 +549,8 @@
name through SolrIndexSearcher.getCache(),cacheLookup(), and name through SolrIndexSearcher.getCache(),cacheLookup(), and
cacheInsert(). The purpose is to enable easy caching of cacheInsert(). The purpose is to enable easy caching of
user/application level data. The regenerator argument should user/application level data. The regenerator argument should
be specified as an implementation of solr.CacheRegenerator be specified as an implementation of solr.CacheRegenerator
if autowarming is desired. if autowarming is desired.
--> -->
<!-- <!--
<cache name="myUserCache" <cache name="myUserCache"
@ -597,12 +597,12 @@
are collected. For example, if a search for a particular query are collected. For example, if a search for a particular query
requests matching documents 10 through 19, and queryWindowSize is 50, requests matching documents 10 through 19, and queryWindowSize is 50,
then documents 0 through 49 will be collected and cached. Any further then documents 0 through 49 will be collected and cached. Any further
requests in that range can be satisfied via the cache. requests in that range can be satisfied via the cache.
--> -->
<queryResultWindowSize>20</queryResultWindowSize> <queryResultWindowSize>20</queryResultWindowSize>
<!-- Maximum number of documents to cache for any entry in the <!-- Maximum number of documents to cache for any entry in the
queryResultCache. queryResultCache.
--> -->
<queryResultMaxDocsCached>200</queryResultMaxDocsCached> <queryResultMaxDocsCached>200</queryResultMaxDocsCached>
@ -620,10 +620,10 @@
prepared but there is no current registered searcher to handle prepared but there is no current registered searcher to handle
requests or to gain autowarming data from. requests or to gain autowarming data from.
--> -->
<!-- QuerySenderListener takes an array of NamedList and executes a <!-- QuerySenderListener takes an array of NamedList and executes a
local query request for each NamedList in sequence. local query request for each NamedList in sequence.
--> -->
<listener event="newSearcher" class="solr.QuerySenderListener"> <listener event="newSearcher" class="solr.QuerySenderListener">
<arr name="queries"> <arr name="queries">
@ -651,7 +651,7 @@
<useColdSearcher>false</useColdSearcher> <useColdSearcher>false</useColdSearcher>
<!-- Max Warming Searchers <!-- Max Warming Searchers
Maximum number of searchers that may be warming in the Maximum number of searchers that may be warming in the
background concurrently. An error is returned if this limit background concurrently. An error is returned if this limit
is exceeded. is exceeded.
@ -673,7 +673,7 @@
such as /select?qt=XXX such as /select?qt=XXX
handleSelect="true" will cause the SolrDispatchFilter to process handleSelect="true" will cause the SolrDispatchFilter to process
the request and dispatch the query to a handler specified by the the request and dispatch the query to a handler specified by the
"qt" param, assuming "/select" isn't already registered. "qt" param, assuming "/select" isn't already registered.
handleSelect="false" will cause the SolrDispatchFilter to handleSelect="false" will cause the SolrDispatchFilter to
@ -695,26 +695,26 @@
multipartUploadLimitInKB - specifies the max size (in KiB) of multipartUploadLimitInKB - specifies the max size (in KiB) of
Multipart File Uploads that Solr will allow in a Request. Multipart File Uploads that Solr will allow in a Request.
formdataUploadLimitInKB - specifies the max size (in KiB) of formdataUploadLimitInKB - specifies the max size (in KiB) of
form data (application/x-www-form-urlencoded) sent via form data (application/x-www-form-urlencoded) sent via
POST. You can use POST to pass request parameters not POST. You can use POST to pass request parameters not
fitting into the URL. fitting into the URL.
addHttpRequestToContext - if set to true, it will instruct addHttpRequestToContext - if set to true, it will instruct
the requestParsers to include the original HttpServletRequest the requestParsers to include the original HttpServletRequest
object in the context map of the SolrQueryRequest under the object in the context map of the SolrQueryRequest under the
key "httpRequest". It will not be used by any of the existing key "httpRequest". It will not be used by any of the existing
Solr components, but may be useful when developing custom Solr components, but may be useful when developing custom
plugins. plugins.
*** WARNING *** *** WARNING ***
The settings below authorize Solr to fetch remote files, You The settings below authorize Solr to fetch remote files, You
should make sure your system has some authentication before should make sure your system has some authentication before
using enableRemoteStreaming="true" using enableRemoteStreaming="true"
--> -->
<requestParsers enableRemoteStreaming="true" <requestParsers enableRemoteStreaming="true"
multipartUploadLimitInKB="2048000" multipartUploadLimitInKB="2048000"
formdataUploadLimitInKB="2048" formdataUploadLimitInKB="2048"
addHttpRequestToContext="false"/> addHttpRequestToContext="false"/>
@ -730,21 +730,21 @@
<!-- If you include a <cacheControl> directive, it will be used to <!-- If you include a <cacheControl> directive, it will be used to
generate a Cache-Control header (as well as an Expires header generate a Cache-Control header (as well as an Expires header
if the value contains "max-age=") if the value contains "max-age=")
By default, no Cache-Control header is generated. By default, no Cache-Control header is generated.
You can use the <cacheControl> option even if you have set You can use the <cacheControl> option even if you have set
never304="true" never304="true"
--> -->
<!-- <!--
<httpCaching never304="true" > <httpCaching never304="true" >
<cacheControl>max-age=30, public</cacheControl> <cacheControl>max-age=30, public</cacheControl>
</httpCaching> </httpCaching>
--> -->
<!-- To enable Solr to respond with automatically generated HTTP <!-- To enable Solr to respond with automatically generated HTTP
Caching headers, and to response to Cache Validation requests Caching headers, and to response to Cache Validation requests
correctly, set the value of never304="false" correctly, set the value of never304="false"
This will cause Solr to generate Last-Modified and ETag This will cause Solr to generate Last-Modified and ETag
headers based on the properties of the Index. headers based on the properties of the Index.
@ -769,12 +769,12 @@
<!-- <!--
<httpCaching lastModifiedFrom="openTime" <httpCaching lastModifiedFrom="openTime"
etagSeed="Solr"> etagSeed="Solr">
<cacheControl>max-age=30, public</cacheControl> <cacheControl>max-age=30, public</cacheControl>
</httpCaching> </httpCaching>
--> -->
</requestDispatcher> </requestDispatcher>
<!-- Request Handlers <!-- Request Handlers
http://wiki.apache.org/solr/SolrRequestHandler http://wiki.apache.org/solr/SolrRequestHandler
@ -863,7 +863,7 @@
<str>nameOfCustomComponent2</str> <str>nameOfCustomComponent2</str>
</arr> </arr>
--> -->
<arr name="last-components"> <arr name="last-components">
<str>spellcheck</str> <str>spellcheck</str>
</arr> </arr>
@ -990,8 +990,8 @@
</requestHandler> </requestHandler>
<!-- Update Request Handler. <!-- Update Request Handler.
http://wiki.apache.org/solr/UpdateXmlMessages http://wiki.apache.org/solr/UpdateXmlMessages
The canonical Request Handler for Modifying the Index through The canonical Request Handler for Modifying the Index through
@ -1000,11 +1000,11 @@
Note: Since solr1.1 requestHandlers requires a valid content Note: Since solr1.1 requestHandlers requires a valid content
type header if posted in the body. For example, curl now type header if posted in the body. For example, curl now
requires: -H 'Content-type:text/xml; charset=utf-8' requires: -H 'Content-type:text/xml; charset=utf-8'
To override the request content type and force a specific To override the request content type and force a specific
Content-type, use the request parameter: Content-type, use the request parameter:
?update.contentType=text/csv ?update.contentType=text/csv
This handler will pick a response format to match the input This handler will pick a response format to match the input
if the 'wt' parameter is not explicit if the 'wt' parameter is not explicit
--> -->
@ -1034,10 +1034,10 @@
<!-- Solr Cell Update Request Handler <!-- Solr Cell Update Request Handler
http://wiki.apache.org/solr/ExtractingRequestHandler http://wiki.apache.org/solr/ExtractingRequestHandler
--> -->
<requestHandler name="/update/extract" <requestHandler name="/update/extract"
startup="lazy" startup="lazy"
class="solr.extraction.ExtractingRequestHandler" > class="solr.extraction.ExtractingRequestHandler" >
<lst name="defaults"> <lst name="defaults">
@ -1070,7 +1070,7 @@
field value analysis will be marked as "matched" for every field value analysis will be marked as "matched" for every
token that is produces by the query analysis token that is produces by the query analysis
--> -->
<requestHandler name="/analysis/field" <requestHandler name="/analysis/field"
startup="lazy" startup="lazy"
class="solr.FieldAnalysisRequestHandler" /> class="solr.FieldAnalysisRequestHandler" />
@ -1103,18 +1103,18 @@
request parameter that holds the query text to be analyzed. It request parameter that holds the query text to be analyzed. It
also supports the "analysis.showmatch" parameter which when set to also supports the "analysis.showmatch" parameter which when set to
true, all field tokens that match the query tokens will be marked true, all field tokens that match the query tokens will be marked
as a "match". as a "match".
--> -->
<requestHandler name="/analysis/document" <requestHandler name="/analysis/document"
class="solr.DocumentAnalysisRequestHandler" class="solr.DocumentAnalysisRequestHandler"
startup="lazy" /> startup="lazy" />
<!-- Admin Handlers <!-- Admin Handlers
Admin Handlers - This will register all the standard admin Admin Handlers - This will register all the standard admin
RequestHandlers. RequestHandlers.
--> -->
<requestHandler name="/admin/" <requestHandler name="/admin/"
class="solr.admin.AdminHandlers" /> class="solr.admin.AdminHandlers" />
<!-- This single handler is equivalent to the following... --> <!-- This single handler is equivalent to the following... -->
<!-- <!--
@ -1126,14 +1126,14 @@
<requestHandler name="/admin/file" class="solr.admin.ShowFileRequestHandler" > <requestHandler name="/admin/file" class="solr.admin.ShowFileRequestHandler" >
--> -->
<!-- If you wish to hide files under ${solr.home}/conf, explicitly <!-- If you wish to hide files under ${solr.home}/conf, explicitly
register the ShowFileRequestHandler using: register the ShowFileRequestHandler using:
--> -->
<!-- <!--
<requestHandler name="/admin/file" <requestHandler name="/admin/file"
class="solr.admin.ShowFileRequestHandler" > class="solr.admin.ShowFileRequestHandler" >
<lst name="invariants"> <lst name="invariants">
<str name="hidden">synonyms.txt</str> <str name="hidden">synonyms.txt</str>
<str name="hidden">anotherfile.txt</str> <str name="hidden">anotherfile.txt</str>
</lst> </lst>
</requestHandler> </requestHandler>
--> -->
@ -1146,10 +1146,10 @@
<lst name="defaults"> <lst name="defaults">
<str name="echoParams">all</str> <str name="echoParams">all</str>
</lst> </lst>
<!-- An optional feature of the PingRequestHandler is to configure the <!-- An optional feature of the PingRequestHandler is to configure the
handler with a "healthcheckFile" which can be used to enable/disable handler with a "healthcheckFile" which can be used to enable/disable
the PingRequestHandler. the PingRequestHandler.
relative paths are resolved against the data dir relative paths are resolved against the data dir
--> -->
<!-- <str name="healthcheckFile">server-enabled.txt</str> --> <!-- <str name="healthcheckFile">server-enabled.txt</str> -->
</requestHandler> </requestHandler>
@ -1157,29 +1157,29 @@
<!-- Echo the request contents back to the client --> <!-- Echo the request contents back to the client -->
<requestHandler name="/debug/dump" class="solr.DumpRequestHandler" > <requestHandler name="/debug/dump" class="solr.DumpRequestHandler" >
<lst name="defaults"> <lst name="defaults">
<str name="echoParams">explicit</str> <str name="echoParams">explicit</str>
<str name="echoHandler">true</str> <str name="echoHandler">true</str>
</lst> </lst>
</requestHandler> </requestHandler>
<!-- Solr Replication <!-- Solr Replication
The SolrReplicationHandler supports replicating indexes from a The SolrReplicationHandler supports replicating indexes from a
"master" used for indexing and "slaves" used for queries. "master" used for indexing and "slaves" used for queries.
http://wiki.apache.org/solr/SolrReplication http://wiki.apache.org/solr/SolrReplication
It is also necessary for SolrCloud to function (in Cloud mode, the It is also necessary for SolrCloud to function (in Cloud mode, the
replication handler is used to bulk transfer segments when nodes replication handler is used to bulk transfer segments when nodes
are added or need to recover). are added or need to recover).
https://wiki.apache.org/solr/SolrCloud/ https://wiki.apache.org/solr/SolrCloud/
--> -->
<requestHandler name="/replication" class="solr.ReplicationHandler" > <requestHandler name="/replication" class="solr.ReplicationHandler" >
<!-- <!--
To enable simple master/slave replication, uncomment one of the To enable simple master/slave replication, uncomment one of the
sections below, depending on whether this solr instance should be sections below, depending on whether this solr instance should be
the "master" or a "slave". If this instance is a "slave" you will the "master" or a "slave". If this instance is a "slave" you will
also need to fill in the masterUrl to point to a real machine. also need to fill in the masterUrl to point to a real machine.
--> -->
<!-- <!--
@ -1199,18 +1199,18 @@
<!-- Search Components <!-- Search Components
Search components are registered to SolrCore and used by Search components are registered to SolrCore and used by
instances of SearchHandler (which can access them by name) instances of SearchHandler (which can access them by name)
By default, the following components are available: By default, the following components are available:
<searchComponent name="query" class="solr.QueryComponent" /> <searchComponent name="query" class="solr.QueryComponent" />
<searchComponent name="facet" class="solr.FacetComponent" /> <searchComponent name="facet" class="solr.FacetComponent" />
<searchComponent name="mlt" class="solr.MoreLikeThisComponent" /> <searchComponent name="mlt" class="solr.MoreLikeThisComponent" />
<searchComponent name="highlight" class="solr.HighlightComponent" /> <searchComponent name="highlight" class="solr.HighlightComponent" />
<searchComponent name="stats" class="solr.StatsComponent" /> <searchComponent name="stats" class="solr.StatsComponent" />
<searchComponent name="debug" class="solr.DebugComponent" /> <searchComponent name="debug" class="solr.DebugComponent" />
Default configuration in a requestHandler would look like: Default configuration in a requestHandler would look like:
<arr name="components"> <arr name="components">
@ -1222,28 +1222,28 @@
<str>debug</str> <str>debug</str>
</arr> </arr>
If you register a searchComponent to one of the standard names, If you register a searchComponent to one of the standard names,
that will be used instead of the default. that will be used instead of the default.
To insert components before or after the 'standard' components, use: To insert components before or after the 'standard' components, use:
<arr name="first-components"> <arr name="first-components">
<str>myFirstComponentName</str> <str>myFirstComponentName</str>
</arr> </arr>
<arr name="last-components"> <arr name="last-components">
<str>myLastComponentName</str> <str>myLastComponentName</str>
</arr> </arr>
NOTE: The component registered with the name "debug" will NOTE: The component registered with the name "debug" will
always be executed after the "last-components" always be executed after the "last-components"
--> -->
<!-- Spell Check <!-- Spell Check
The spell check component can return a list of alternative spelling The spell check component can return a list of alternative spelling
suggestions. suggestions.
http://wiki.apache.org/solr/SpellCheckComponent http://wiki.apache.org/solr/SpellCheckComponent
--> -->
@ -1302,11 +1302,11 @@
<float name="thresholdTokenFrequency">.01</float> <float name="thresholdTokenFrequency">.01</float>
--> -->
</lst> </lst>
<!-- a spellchecker that can break or combine words. See "/spell" handler below for usage --> <!-- a spellchecker that can break or combine words. See "/spell" handler below for usage -->
<lst name="spellchecker"> <lst name="spellchecker">
<str name="name">wordbreak</str> <str name="name">wordbreak</str>
<str name="classname">solr.WordBreakSolrSpellChecker</str> <str name="classname">solr.WordBreakSolrSpellChecker</str>
<str name="field">_text</str> <str name="field">_text</str>
<str name="combineWords">true</str> <str name="combineWords">true</str>
<str name="breakWords">true</str> <str name="breakWords">true</str>
@ -1325,7 +1325,7 @@
</lst> </lst>
--> -->
<!-- a spellchecker that use an alternate comparator <!-- a spellchecker that use an alternate comparator
comparatorClass be one of: comparatorClass be one of:
1. score (default) 1. score (default)
@ -1352,7 +1352,7 @@
--> -->
</searchComponent> </searchComponent>
<!-- A request handler for demonstrating the spellcheck component. <!-- A request handler for demonstrating the spellcheck component.
NOTE: This is purely as an example. The whole purpose of the NOTE: This is purely as an example. The whole purpose of the
SpellCheckComponent is to hook it into the request handler that SpellCheckComponent is to hook it into the request handler that
@ -1361,7 +1361,7 @@
IN OTHER WORDS, THERE IS REALLY GOOD CHANCE THE SETUP BELOW IS IN OTHER WORDS, THERE IS REALLY GOOD CHANCE THE SETUP BELOW IS
NOT WHAT YOU WANT FOR YOUR PRODUCTION SYSTEM! NOT WHAT YOU WANT FOR YOUR PRODUCTION SYSTEM!
See http://wiki.apache.org/solr/SpellCheckComponent for details See http://wiki.apache.org/solr/SpellCheckComponent for details
on the request parameters. on the request parameters.
--> -->
@ -1375,14 +1375,14 @@
<str name="spellcheck.dictionary">default</str> <str name="spellcheck.dictionary">default</str>
<str name="spellcheck.dictionary">wordbreak</str> <str name="spellcheck.dictionary">wordbreak</str>
<str name="spellcheck">on</str> <str name="spellcheck">on</str>
<str name="spellcheck.extendedResults">true</str> <str name="spellcheck.extendedResults">true</str>
<str name="spellcheck.count">10</str> <str name="spellcheck.count">10</str>
<str name="spellcheck.alternativeTermCount">5</str> <str name="spellcheck.alternativeTermCount">5</str>
<str name="spellcheck.maxResultsForSuggest">5</str> <str name="spellcheck.maxResultsForSuggest">5</str>
<str name="spellcheck.collate">true</str> <str name="spellcheck.collate">true</str>
<str name="spellcheck.collateExtendedResults">true</str> <str name="spellcheck.collateExtendedResults">true</str>
<str name="spellcheck.maxCollationTries">10</str> <str name="spellcheck.maxCollationTries">10</str>
<str name="spellcheck.maxCollations">5</str> <str name="spellcheck.maxCollations">5</str>
</lst> </lst>
<arr name="last-components"> <arr name="last-components">
<str>spellcheck</str> <str>spellcheck</str>
@ -1399,8 +1399,8 @@
This is purely as an example. This is purely as an example.
In reality you will likely want to add the component to your In reality you will likely want to add the component to your
already specified request handlers. already specified request handlers.
--> -->
<requestHandler name="/tvrh" class="solr.SearchHandler" startup="lazy"> <requestHandler name="/tvrh" class="solr.SearchHandler" startup="lazy">
<lst name="defaults"> <lst name="defaults">
@ -1433,11 +1433,11 @@
<!-- Class name of Carrot2 clustering algorithm. <!-- Class name of Carrot2 clustering algorithm.
Currently available algorithms are: Currently available algorithms are:
* org.carrot2.clustering.lingo.LingoClusteringAlgorithm * org.carrot2.clustering.lingo.LingoClusteringAlgorithm
* org.carrot2.clustering.stc.STCClusteringAlgorithm * org.carrot2.clustering.stc.STCClusteringAlgorithm
* org.carrot2.clustering.kmeans.BisectingKMeansClusteringAlgorithm * org.carrot2.clustering.kmeans.BisectingKMeansClusteringAlgorithm
See http://project.carrot2.org/algorithms.html for the See http://project.carrot2.org/algorithms.html for the
algorithm's characteristics. algorithm's characteristics.
--> -->
@ -1484,8 +1484,8 @@
This is purely as an example. This is purely as an example.
In reality you will likely want to add the component to your In reality you will likely want to add the component to your
already specified request handlers. already specified request handlers.
--> -->
<requestHandler name="/clustering" <requestHandler name="/clustering"
startup="lazy" startup="lazy"
@ -1506,7 +1506,7 @@
<!--<int name="carrot.numDescriptions">5</int>--> <!--<int name="carrot.numDescriptions">5</int>-->
<!-- produce sub clusters --> <!-- produce sub clusters -->
<bool name="carrot.outputSubClusters">false</bool> <bool name="carrot.outputSubClusters">false</bool>
<str name="defType">edismax</str> <str name="defType">edismax</str>
<str name="qf"> <str name="qf">
text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 manu^1.1 cat^1.4 text^0.5 features^1.0 name^1.2 sku^1.5 id^10.0 manu^1.1 cat^1.4
@ -1514,12 +1514,12 @@
<str name="q.alt">*:*</str> <str name="q.alt">*:*</str>
<str name="rows">10</str> <str name="rows">10</str>
<str name="fl">*,score</str> <str name="fl">*,score</str>
</lst> </lst>
<arr name="last-components"> <arr name="last-components">
<str>clustering</str> <str>clustering</str>
</arr> </arr>
</requestHandler> </requestHandler>
<!-- Terms Component <!-- Terms Component
http://wiki.apache.org/solr/TermsComponent http://wiki.apache.org/solr/TermsComponent
@ -1534,7 +1534,7 @@
<lst name="defaults"> <lst name="defaults">
<bool name="terms">true</bool> <bool name="terms">true</bool>
<bool name="distrib">false</bool> <bool name="distrib">false</bool>
</lst> </lst>
<arr name="components"> <arr name="components">
<str>terms</str> <str>terms</str>
</arr> </arr>
@ -1574,7 +1574,7 @@
<highlighting> <highlighting>
<!-- Configure the standard fragmenter --> <!-- Configure the standard fragmenter -->
<!-- This could most likely be commented out in the "default" case --> <!-- This could most likely be commented out in the "default" case -->
<fragmenter name="gap" <fragmenter name="gap"
default="true" default="true"
class="solr.highlight.GapFragmenter"> class="solr.highlight.GapFragmenter">
<lst name="defaults"> <lst name="defaults">
@ -1582,10 +1582,10 @@
</lst> </lst>
</fragmenter> </fragmenter>
<!-- A regular-expression-based fragmenter <!-- A regular-expression-based fragmenter
(for sentence extraction) (for sentence extraction)
--> -->
<fragmenter name="regex" <fragmenter name="regex"
class="solr.highlight.RegexFragmenter"> class="solr.highlight.RegexFragmenter">
<lst name="defaults"> <lst name="defaults">
<!-- slightly smaller fragsizes work better because of slop --> <!-- slightly smaller fragsizes work better because of slop -->
@ -1598,7 +1598,7 @@
</fragmenter> </fragmenter>
<!-- Configure the standard formatter --> <!-- Configure the standard formatter -->
<formatter name="html" <formatter name="html"
default="true" default="true"
class="solr.highlight.HtmlFormatter"> class="solr.highlight.HtmlFormatter">
<lst name="defaults"> <lst name="defaults">
@ -1608,27 +1608,27 @@
</formatter> </formatter>
<!-- Configure the standard encoder --> <!-- Configure the standard encoder -->
<encoder name="html" <encoder name="html"
class="solr.highlight.HtmlEncoder" /> class="solr.highlight.HtmlEncoder" />
<!-- Configure the standard fragListBuilder --> <!-- Configure the standard fragListBuilder -->
<fragListBuilder name="simple" <fragListBuilder name="simple"
class="solr.highlight.SimpleFragListBuilder"/> class="solr.highlight.SimpleFragListBuilder"/>
<!-- Configure the single fragListBuilder --> <!-- Configure the single fragListBuilder -->
<fragListBuilder name="single" <fragListBuilder name="single"
class="solr.highlight.SingleFragListBuilder"/> class="solr.highlight.SingleFragListBuilder"/>
<!-- Configure the weighted fragListBuilder --> <!-- Configure the weighted fragListBuilder -->
<fragListBuilder name="weighted" <fragListBuilder name="weighted"
default="true" default="true"
class="solr.highlight.WeightedFragListBuilder"/> class="solr.highlight.WeightedFragListBuilder"/>
<!-- default tag FragmentsBuilder --> <!-- default tag FragmentsBuilder -->
<fragmentsBuilder name="default" <fragmentsBuilder name="default"
default="true" default="true"
class="solr.highlight.ScoreOrderFragmentsBuilder"> class="solr.highlight.ScoreOrderFragmentsBuilder">
<!-- <!--
<lst name="defaults"> <lst name="defaults">
<str name="hl.multiValuedSeparatorChar">/</str> <str name="hl.multiValuedSeparatorChar">/</str>
</lst> </lst>
@ -1636,7 +1636,7 @@
</fragmentsBuilder> </fragmentsBuilder>
<!-- multi-colored tag FragmentsBuilder --> <!-- multi-colored tag FragmentsBuilder -->
<fragmentsBuilder name="colored" <fragmentsBuilder name="colored"
class="solr.highlight.ScoreOrderFragmentsBuilder"> class="solr.highlight.ScoreOrderFragmentsBuilder">
<lst name="defaults"> <lst name="defaults">
<str name="hl.tag.pre"><![CDATA[ <str name="hl.tag.pre"><![CDATA[
@ -1648,8 +1648,8 @@
<str name="hl.tag.post"><![CDATA[</b>]]></str> <str name="hl.tag.post"><![CDATA[</b>]]></str>
</lst> </lst>
</fragmentsBuilder> </fragmentsBuilder>
<boundaryScanner name="default" <boundaryScanner name="default"
default="true" default="true"
class="solr.highlight.SimpleBoundaryScanner"> class="solr.highlight.SimpleBoundaryScanner">
<lst name="defaults"> <lst name="defaults">
@ -1657,8 +1657,8 @@
<str name="hl.bs.chars">.,!? &#9;&#10;&#13;</str> <str name="hl.bs.chars">.,!? &#9;&#10;&#13;</str>
</lst> </lst>
</boundaryScanner> </boundaryScanner>
<boundaryScanner name="breakIterator" <boundaryScanner name="breakIterator"
class="solr.highlight.BreakIteratorBoundaryScanner"> class="solr.highlight.BreakIteratorBoundaryScanner">
<lst name="defaults"> <lst name="defaults">
<!-- type should be one of CHARACTER, WORD(default), LINE and SENTENCE --> <!-- type should be one of CHARACTER, WORD(default), LINE and SENTENCE -->
@ -1680,15 +1680,15 @@
http://wiki.apache.org/solr/UpdateRequestProcessor http://wiki.apache.org/solr/UpdateRequestProcessor
--> -->
<!-- Deduplication <!-- Deduplication
An example dedup update processor that creates the "id" field An example dedup update processor that creates the "id" field
on the fly based on the hash code of some other fields. This on the fly based on the hash code of some other fields. This
example has overwriteDupes set to false since we are using the example has overwriteDupes set to false since we are using the
id field as the signatureField and Solr will maintain id field as the signatureField and Solr will maintain
uniqueness based on that anyway. uniqueness based on that anyway.
--> -->
<!-- <!--
<updateRequestProcessorChain name="dedupe"> <updateRequestProcessorChain name="dedupe">
@ -1703,7 +1703,7 @@
<processor class="solr.RunUpdateProcessorFactory" /> <processor class="solr.RunUpdateProcessorFactory" />
</updateRequestProcessorChain> </updateRequestProcessorChain>
--> -->
<!-- Language identification <!-- Language identification
This example update chain identifies the language of the incoming This example update chain identifies the language of the incoming
@ -1743,7 +1743,7 @@
<processor class="solr.RunUpdateProcessorFactory" /> <processor class="solr.RunUpdateProcessorFactory" />
</updateRequestProcessorChain> </updateRequestProcessorChain>
--> -->
<!-- Response Writers <!-- Response Writers
http://wiki.apache.org/solr/QueryResponseWriter http://wiki.apache.org/solr/QueryResponseWriter
@ -1759,7 +1759,7 @@
overridden... overridden...
--> -->
<!-- <!--
<queryResponseWriter name="xml" <queryResponseWriter name="xml"
default="true" default="true"
class="solr.XMLResponseWriter" /> class="solr.XMLResponseWriter" />
<queryResponseWriter name="json" class="solr.JSONResponseWriter"/> <queryResponseWriter name="json" class="solr.JSONResponseWriter"/>
@ -1778,16 +1778,16 @@
--> -->
<str name="content-type">text/plain; charset=UTF-8</str> <str name="content-type">text/plain; charset=UTF-8</str>
</queryResponseWriter> </queryResponseWriter>
<!-- <!--
Custom response writers can be declared as needed... Custom response writers can be declared as needed...
--> -->
<queryResponseWriter name="velocity" class="solr.VelocityResponseWriter" startup="lazy"/> <queryResponseWriter name="velocity" class="solr.VelocityResponseWriter" startup="lazy"/>
<!-- XSLT response writer transforms the XML output by any xslt file found <!-- XSLT response writer transforms the XML output by any xslt file found
in Solr's conf/xslt directory. Changes to xslt files are checked for in Solr's conf/xslt directory. Changes to xslt files are checked for
every xsltCacheLifetimeSeconds. every xsltCacheLifetimeSeconds.
--> -->
<queryResponseWriter name="xslt" class="solr.XSLTResponseWriter"> <queryResponseWriter name="xslt" class="solr.XSLTResponseWriter">
<int name="xsltCacheLifetimeSeconds">5</int> <int name="xsltCacheLifetimeSeconds">5</int>
@ -1815,11 +1815,11 @@
--> -->
<!-- example of registering a custom function parser --> <!-- example of registering a custom function parser -->
<!-- <!--
<valueSourceParser name="myfunc" <valueSourceParser name="myfunc"
class="com.mycompany.MyValueSourceParser" /> class="com.mycompany.MyValueSourceParser" />
--> -->
<!-- Document Transformers <!-- Document Transformers
http://wiki.apache.org/solr/DocTransformers http://wiki.apache.org/solr/DocTransformers
--> -->
@ -1828,12 +1828,12 @@
<transformer name="db" class="com.mycompany.LoadFromDatabaseTransformer" > <transformer name="db" class="com.mycompany.LoadFromDatabaseTransformer" >
<int name="connection">jdbc://....</int> <int name="connection">jdbc://....</int>
</transformer> </transformer>
To add a constant value to all docs, use: To add a constant value to all docs, use:
<transformer name="mytrans2" class="org.apache.solr.response.transform.ValueAugmenterFactory" > <transformer name="mytrans2" class="org.apache.solr.response.transform.ValueAugmenterFactory" >
<int name="value">5</int> <int name="value">5</int>
</transformer> </transformer>
If you want the user to still be able to change it with _value:something_ use this: If you want the user to still be able to change it with _value:something_ use this:
<transformer name="mytrans3" class="org.apache.solr.response.transform.ValueAugmenterFactory" > <transformer name="mytrans3" class="org.apache.solr.response.transform.ValueAugmenterFactory" >
<double name="defaultValue">5</double> <double name="defaultValue">5</double>
@ -1843,7 +1843,7 @@
EditorialMarkerFactory will do exactly that: EditorialMarkerFactory will do exactly that:
<transformer name="qecBooster" class="org.apache.solr.response.transform.EditorialMarkerFactory" /> <transformer name="qecBooster" class="org.apache.solr.response.transform.EditorialMarkerFactory" />
--> -->
<!-- Legacy config for the admin interface --> <!-- Legacy config for the admin interface -->
<admin> <admin>

View File

@ -1,26 +1,13 @@
# Troubleshooting # Troubleshooting
## Newly indexed content only shows in searches after a delay
First, check how you're running index operations.
In many cases where the `queuedjobs` module is installed,
saving or publishing a record will create a new index job which needs to complete first.
Solr also distinguishes between adding documents to the indexing,
committing them, and making them available to new searches.
In most cases this happens within a few seconds, but
in sometimes it can take up to a minute due to the
`autoSoftCommit` configuration setting defaults in your `solrconfig.xml`.
To find out more detail, read about
[soft vs. hard commits](https://lucidworks.com/post/understanding-transaction-logs-softcommit-and-commit-in-sorlcloud/).
## Common gotchas ## Common gotchas
* By default number-letter boundaries are treated as a word boundary. For example, `A1` is two words - `a` and `1` - when Solr parses the search term. * By default number-letter boundaries are treated as a word boundary. For example, `A1` is two words - `a` and `1` - when Solr parses the search term.
* Special characters and operators are not correctly escaped * Special characters and operators are not correctly escaped
* Multi-word synonym issues * Multi-word synonym issues
* When Solr indexes are reconfigured and reindexed, their content is trashed and rebuilt * When Dolr indexes are reconfigured and reindexed, their content is trashed and rebuilt
## CWP-specific ### CWP-specific
* `solrconfig.xml` customisations fail silently * `solrconfig.xml` customisations fail silently
* Developers arent able to test raw queries or see output via the * Developers arent able to test raw queries or see output via the

View File

@ -2,13 +2,3 @@ en:
SilverStripe\FullTextSearch\Solr\Forms\SearchForm: SilverStripe\FullTextSearch\Solr\Forms\SearchForm:
GO: Go GO: Go
SEARCH: Search SEARCH: Search
SolrResultsPage:
DidYouMean: 'Did you mean'
NoResults: 'Sorry, your search query did not return any results.'
Page: Page
ReadMore: 'Read more about'
SearchQuery: 'You searched for'
ViewNextPage: 'View the next page'
ViewPageNumber: 'View page number'
ViewPreviousPage: 'View the previous page'
of: of

View File

@ -1,14 +0,0 @@
eo:
SilverStripe\FullTextSearch\Solr\Forms\SearchForm:
GO: Iri
SEARCH: Serĉi
SolrResultsPage:
DidYouMean: 'Ĉu vi intencis'
NoResults: 'Bedaŭrinde via serĉpeto ne liveris rezultojn.'
Page: Paĝo
ReadMore: 'Legi pli pri'
SearchQuery: 'Vi serĉis por'
ViewNextPage: 'Vidi la sekvan paĝon'
ViewPageNumber: 'Vidi paĝnumeron'
ViewPreviousPage: 'Vidi la antaŭan paĝon'
of: de

View File

@ -3,12 +3,12 @@ fi:
GO: Hae GO: Hae
SEARCH: Haku SEARCH: Haku
SolrResultsPage: SolrResultsPage:
DidYouMean: Tarkoititko
NoResults: 'Pahoittelut, mutta hakusi ei tuottanut yhtään osumaa.'
Page: Sivu
ReadMore: 'Lue lisää'
SearchQuery: 'Hakusanasi oli' SearchQuery: 'Hakusanasi oli'
'View page number': 'Siirry sivulle' DidYouMean: 'Tarkoititko'
ViewNextPage: 'Katso seuraava sivu' ReadMore: 'Lue lisää'
NoResults: 'Pahoittelut, mutta hakusi ei tuottanut yhtään osumaa.'
Page: 'Sivu'
of: '/'
ViewPreviousPage: 'Katso edellinen sivu' ViewPreviousPage: 'Katso edellinen sivu'
of: / View page number: 'Siirry sivulle'
ViewNextPage: 'Katso seuraava sivu'

View File

@ -1,14 +0,0 @@
nl:
SilverStripe\FullTextSearch\Solr\Forms\SearchForm:
GO: Zoek
SEARCH: Zoeken
SolrResultsPage:
DidYouMean: 'Bedoelde je'
NoResults: 'Helaas, deze zoekterm heeft geen resultaten opgeleverd.'
Page: Pagina
ReadMore: 'Lees meer over'
SearchQuery: 'Er is gezocht op'
ViewNextPage: 'Bekijk volgende pagina'
ViewPageNumber: 'Bekijk pagina'
ViewPreviousPage: 'Bekijk volgende pagina'
of: van

View File

@ -1,14 +0,0 @@
sl:
SilverStripe\FullTextSearch\Solr\Forms\SearchForm:
GO: Išči
SEARCH: Iskanje
SolrResultsPage:
DidYouMean: 'Ste morda mislili'
NoResults: 'Fraze, ki ste jo iskali, nismo našli.'
Page: Stran
ReadMore: 'Več o'
SearchQuery: 'Iskali ste'
ViewNextPage: Naslednja
ViewPageNumber: 'Skoči na stran'
ViewPreviousPage: Prejšnja
of: od

View File

@ -1,14 +0,0 @@
sv:
SilverStripe\FullTextSearch\Solr\Forms\SearchForm:
GO:
SEARCH: Sök
SolrResultsPage:
DidYouMean: 'Menade du'
NoResults: 'Förlåt, din sökfråga gav inga resultat.'
Page: Sida
ReadMore: 'Läs mera om'
SearchQuery: 'Du sökte efter'
ViewNextPage: 'Visa nästa sida'
ViewPageNumber: 'Visa sidnummer'
ViewPreviousPage: 'Visa föregående sida'
of: av

View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/silverstripe/cms/tests/bootstrap.php" colors="true"> <phpunit bootstrap="vendor/silverstripe/cms/tests/bootstrap.php" colors="true">
<testsuites> <testsuites>
<testsuite name="Default"> <testsuite name="Default">

View File

@ -2,12 +2,11 @@
namespace SilverStripe\FullTextSearch\Search\Captures; namespace SilverStripe\FullTextSearch\Search\Captures;
use SilverStripe\Dev\Deprecation;
use SilverStripe\ORM\Connect\MySQLDatabase; use SilverStripe\ORM\Connect\MySQLDatabase;
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater; use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
/** /**
* @deprecated 3.1.0 Use tractorcow/silverstripe-proxy-db to proxy the database connector instead * @deprecated 3.1...4.0 Please use tractorcow/silverstripe-proxy-db to proxy the database connector instead
*/ */
class SearchManipulateCapture_MySQLDatabase extends MySQLDatabase class SearchManipulateCapture_MySQLDatabase extends MySQLDatabase
@ -15,11 +14,6 @@ class SearchManipulateCapture_MySQLDatabase extends MySQLDatabase
public $isManipulationCapture = true; public $isManipulationCapture = true;
public function __construct()
{
Deprecation::notice('3.1.0', 'Use tractorcow/silverstripe-proxy-db to proxy the database connector instead', Deprecation::SCOPE_CLASS);
}
public function manipulate($manipulation) public function manipulate($manipulation)
{ {
$res = parent::manipulate($manipulation); $res = parent::manipulate($manipulation);

View File

@ -2,7 +2,6 @@
namespace SilverStripe\FullTextSearch\Search\Captures; namespace SilverStripe\FullTextSearch\Search\Captures;
use SilverStripe\Dev\Deprecation;
use SilverStripe\PostgreSQL\PostgreSQLDatabase; use SilverStripe\PostgreSQL\PostgreSQLDatabase;
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater; use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
@ -11,17 +10,13 @@ if (!class_exists(PostgreSQLDatabase::class)) {
} }
/** /**
* @deprecated 3.1.0 Use tractorcow/silverstripe-proxy-db to proxy the database connector instead * @deprecated 3.1...4.0 Please use tractorcow/silverstripe-proxy-db to proxy the database connector instead
*/ */
class SearchManipulateCapture_PostgreSQLDatabase extends PostgreSQLDatabase class SearchManipulateCapture_PostgreSQLDatabase extends PostgreSQLDatabase
{ {
public $isManipulationCapture = true; public $isManipulationCapture = true;
public function __construct()
{
Deprecation::notice('3.1.0', 'Use tractorcow/silverstripe-proxy-db to proxy the database connector instead', Deprecation::SCOPE_CLASS);
}
public function manipulate($manipulation) public function manipulate($manipulation)
{ {
$res = parent::manipulate($manipulation); $res = parent::manipulate($manipulation);

View File

@ -2,7 +2,6 @@
namespace SilverStripe\FullTextSearch\Search\Captures; namespace SilverStripe\FullTextSearch\Search\Captures;
use SilverStripe\Dev\Deprecation;
use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater; use SilverStripe\FullTextSearch\Search\Updaters\SearchUpdater;
use SilverStripe\SQLite\SQLite3Database; use SilverStripe\SQLite\SQLite3Database;
@ -11,7 +10,7 @@ if (!class_exists(SQLite3Database::class)) {
} }
/** /**
* @deprecated 3.1.0 Use tractorcow/silverstripe-proxy-db to proxy the database connector instead * @deprecated 3.1...4.0 Please use tractorcow/silverstripe-proxy-db to proxy the database connector instead
*/ */
class SearchManipulateCapture_SQLite3Database extends SQLite3Database class SearchManipulateCapture_SQLite3Database extends SQLite3Database
@ -19,11 +18,6 @@ class SearchManipulateCapture_SQLite3Database extends SQLite3Database
public $isManipulationCapture = true; public $isManipulationCapture = true;
public function __construct()
{
Deprecation::notice('3.1.0', 'Use tractorcow/silverstripe-proxy-db to proxy the database connector instead', Deprecation::SCOPE_CLASS);
}
public function manipulate($manipulation) public function manipulate($manipulation)
{ {
$res = parent::manipulate($manipulation); $res = parent::manipulate($manipulation);

View File

@ -222,7 +222,7 @@ class SearchCriteria implements SearchCriteriaInterface
protected function getConjunction($key) protected function getConjunction($key)
{ {
$conjunctions = $this->getConjunctions(); $conjunctions = $this->getConjunctions();
if (!array_key_exists($key, $conjunctions ?? [])) { if (!array_key_exists($key, $conjunctions)) {
return null; return null;
} }

View File

@ -67,7 +67,7 @@ class FullTextSearch
} }
if ($hidden) { if ($hidden) {
$candidates = array_diff($candidates ?? [], $hidden); $candidates = array_diff($candidates, $hidden);
} }
// Create all indexes // Create all indexes
@ -86,7 +86,7 @@ class FullTextSearch
$valid = array(); $valid = array();
foreach ($all as $indexclass => $instance) { foreach ($all as $indexclass => $instance) {
if (is_subclass_of($indexclass, $class ?? '')) { if (is_subclass_of($indexclass, $class)) {
$valid[$indexclass] = $instance; $valid[$indexclass] = $instance;
} }
} }

View File

@ -70,7 +70,7 @@ abstract class SearchIndex extends ViewableData
*/ */
protected function getSourceName($source) protected function getSourceName($source)
{ {
$source = explode(self::config()->get('class_delimiter') ?? '', $source ?? ''); $source = explode(self::config()->get('class_delimiter'), $source);
return $source[0]; return $source[0];
} }
@ -102,7 +102,7 @@ abstract class SearchIndex extends ViewableData
*/ */
public function fieldData($field, $forceType = null, $extraOptions = []) public function fieldData($field, $forceType = null, $extraOptions = [])
{ {
$fullfield = str_replace(".", "_", $field ?? ''); $fullfield = str_replace(".", "_", $field);
$sources = $this->getClasses(); $sources = $this->getClasses();
foreach ($sources as $source => $options) { foreach ($sources as $source => $options) {
@ -112,8 +112,8 @@ abstract class SearchIndex extends ViewableData
$found = []; $found = [];
if (strpos($field ?? '', '.') !== false) { if (strpos($field, '.') !== false) {
$lookups = explode(".", $field ?? ''); $lookups = explode(".", $field);
$field = array_pop($lookups); $field = array_pop($lookups);
foreach ($lookups as $lookup) { foreach ($lookups as $lookup) {
@ -133,7 +133,7 @@ abstract class SearchIndex extends ViewableData
// we only want to include base class for relation, omit classes that inherited the relation // we only want to include base class for relation, omit classes that inherited the relation
$relationList = Config::inst()->get($dataclass, 'has_one', Config::UNINHERITED); $relationList = Config::inst()->get($dataclass, 'has_one', Config::UNINHERITED);
$relationList = (!is_null($relationList)) ? $relationList : []; $relationList = (!is_null($relationList)) ? $relationList : [];
if (!array_key_exists($lookup, $relationList ?? [])) { if (!array_key_exists($lookup, $relationList)) {
continue; continue;
} }
@ -146,7 +146,7 @@ abstract class SearchIndex extends ViewableData
// we only want to include base class for relation, omit classes that inherited the relation // we only want to include base class for relation, omit classes that inherited the relation
$relationList = Config::inst()->get($dataclass, 'has_many', Config::UNINHERITED); $relationList = Config::inst()->get($dataclass, 'has_many', Config::UNINHERITED);
$relationList = (!is_null($relationList)) ? $relationList : []; $relationList = (!is_null($relationList)) ? $relationList : [];
if (!array_key_exists($lookup, $relationList ?? [])) { if (!array_key_exists($lookup, $relationList)) {
continue; continue;
} }
@ -160,7 +160,7 @@ abstract class SearchIndex extends ViewableData
// we only want to include base class for relation, omit classes that inherited the relation // we only want to include base class for relation, omit classes that inherited the relation
$relationList = Config::inst()->get($dataclass, 'many_many', Config::UNINHERITED); $relationList = Config::inst()->get($dataclass, 'many_many', Config::UNINHERITED);
$relationList = (!is_null($relationList)) ? $relationList : []; $relationList = (!is_null($relationList)) ? $relationList : [];
if (!array_key_exists($lookup, $relationList ?? [])) { if (!array_key_exists($lookup, $relationList)) {
continue; continue;
} }
@ -199,7 +199,7 @@ abstract class SearchIndex extends ViewableData
$class = $this->getSourceName($class); $class = $this->getSourceName($class);
$dataclasses = SearchIntrospection::hierarchy($class, $options['include_children']); $dataclasses = SearchIntrospection::hierarchy($class, $options['include_children']);
while (count($dataclasses ?? [])) { while (count($dataclasses)) {
$dataclass = array_shift($dataclasses); $dataclass = array_shift($dataclasses);
$type = null; $type = null;
$fieldoptions = $options; $fieldoptions = $options;
@ -228,9 +228,9 @@ abstract class SearchIndex extends ViewableData
if ($type) { if ($type) {
// Don't search through child classes of a class we matched on. TODO: Should we? // Don't search through child classes of a class we matched on. TODO: Should we?
$dataclasses = array_diff($dataclasses ?? [], array_values(ClassInfo::subclassesFor($dataclass) ?? [])); $dataclasses = array_diff($dataclasses, array_values(ClassInfo::subclassesFor($dataclass)));
// Trim arguments off the type string // Trim arguments off the type string
if (preg_match('/^(\w+)\(/', $type ?? '', $match)) { if (preg_match('/^(\w+)\(/', $type, $match)) {
$type = $match[1]; $type = $match[1];
} }
// Get the origin // Get the origin
@ -412,7 +412,7 @@ abstract class SearchIndex extends ViewableData
public function buildDependancyList() public function buildDependancyList()
{ {
$this->dependancyList = array_keys($this->getClasses() ?? []); $this->dependancyList = array_keys($this->getClasses());
foreach ($this->getFieldsIterator() as $name => $field) { foreach ($this->getFieldsIterator() as $name => $field) {
if (!isset($field['class'])) { if (!isset($field['class'])) {
@ -434,7 +434,7 @@ abstract class SearchIndex extends ViewableData
$this->derivedFields = array(); $this->derivedFields = array();
foreach ($this->getFieldsIterator() as $name => $field) { foreach ($this->getFieldsIterator() as $name => $field) {
if (count($field['lookup_chain'] ?? []) < 2) { if (count($field['lookup_chain']) < 2) {
continue; continue;
} }
@ -445,7 +445,7 @@ abstract class SearchIndex extends ViewableData
$this->derivedFields[$key]['fields'][$fieldname] = $fieldname; $this->derivedFields[$key]['fields'][$fieldname] = $fieldname;
SearchIntrospection::add_unique_by_ancestor($this->derivedFields['classes'], $field['class']); SearchIntrospection::add_unique_by_ancestor($this->derivedFields['classes'], $field['class']);
} else { } else {
$chain = array_reverse($field['lookup_chain'] ?? []); $chain = array_reverse($field['lookup_chain']);
array_shift($chain); array_shift($chain);
$this->derivedFields[$key] = array( $this->derivedFields[$key] = array(
@ -473,7 +473,7 @@ abstract class SearchIndex extends ViewableData
{ {
ksort($state); ksort($state);
$parts = array('id' => $id, 'base' => $base, 'state' => json_encode($state)); $parts = array('id' => $id, 'base' => $base, 'state' => json_encode($state));
return implode('-', array_values($parts ?? [])); return implode('-', array_values($parts));
} }
/** /**
@ -501,7 +501,7 @@ abstract class SearchIndex extends ViewableData
$errorHandler = function ($no, $str) { $errorHandler = function ($no, $str) {
throw new Exception('HTML Parse Error: ' . $str); throw new Exception('HTML Parse Error: ' . $str);
}; };
set_error_handler($errorHandler, E_ALL & ~(E_DEPRECATED | E_USER_DEPRECATED)); set_error_handler($errorHandler, E_ALL);
try { try {
foreach ($field['lookup_chain'] as $step) { foreach ($field['lookup_chain'] as $step) {
@ -586,7 +586,7 @@ abstract class SearchIndex extends ViewableData
// First, if this object is directly contained in the index, add it // First, if this object is directly contained in the index, add it
foreach ($this->classes as $searchclass => $options) { foreach ($this->classes as $searchclass => $options) {
if ($searchclass == $class || ($options['include_children'] && is_subclass_of($class, $searchclass ?? ''))) { if ($searchclass == $class || ($options['include_children'] && is_subclass_of($class, $searchclass))) {
$base = DataObject::getSchema()->baseDataClass($searchclass); $base = DataObject::getSchema()->baseDataClass($searchclass);
$dirty[$base] = array(); $dirty[$base] = array();
foreach ($statefulids as $statefulid) { foreach ($statefulids as $statefulid) {
@ -605,7 +605,7 @@ abstract class SearchIndex extends ViewableData
if (!SearchIntrospection::is_subclass_of($class, $derivation['classes'])) { if (!SearchIntrospection::is_subclass_of($class, $derivation['classes'])) {
continue; continue;
} }
if (!array_intersect_key($fields ?? [], $derivation['fields'])) { if (!array_intersect_key($fields, $derivation['fields'])) {
continue; continue;
} }

View File

@ -89,7 +89,7 @@ abstract class SearchUpdateBatchedProcessor extends SearchUpdateProcessor
} }
// Don't re-process completed queue // Don't re-process completed queue
if ($this->currentBatch >= count($this->batches ?? [])) { if ($this->currentBatch >= count($this->batches)) {
return true; return true;
} }
@ -138,14 +138,14 @@ abstract class SearchUpdateBatchedProcessor extends SearchUpdateProcessor
while ($ids) { while ($ids) {
// Estimate maximum number of items to take for this iteration, allowing for the soft cap // Estimate maximum number of items to take for this iteration, allowing for the soft cap
$take = $batchSize - $currentSize; $take = $batchSize - $currentSize;
if (count($ids ?? []) <= $take + $softCap) { if (count($ids) <= $take + $softCap) {
$take += $softCap; $take += $softCap;
} }
$items = array_slice($ids ?? [], 0, $take, true); $items = array_slice($ids, 0, $take, true);
$ids = array_slice($ids ?? [], count($items ?? []), null, true); $ids = array_slice($ids, count($items), null, true);
// Update batch // Update batch
$currentSize += count($items ?? []); $currentSize += count($items);
$merge = array( $merge = array(
$base => array( $base => array(
$stateKey => array( $stateKey => array(

View File

@ -97,7 +97,7 @@ class SearchUpdateCommitJobProcessor implements QueuedJob
if ($dirty) { if ($dirty) {
$indexes = FullTextSearch::get_indexes(); $indexes = FullTextSearch::get_indexes();
static::$dirty_indexes = array_keys($indexes ?? []); static::$dirty_indexes = array_keys($indexes);
} }
return $id; return $id;
} }
@ -126,7 +126,7 @@ class SearchUpdateCommitJobProcessor implements QueuedJob
{ {
if (empty($this->indexes)) { if (empty($this->indexes)) {
$indexes = FullTextSearch::get_indexes(); $indexes = FullTextSearch::get_indexes();
$this->indexes = array_keys($indexes ?? []); $this->indexes = array_keys($indexes);
} }
return $this->indexes; return $this->indexes;
} }
@ -135,7 +135,7 @@ class SearchUpdateCommitJobProcessor implements QueuedJob
{ {
// If we've indexed exactly as many as we would like, we are done // If we've indexed exactly as many as we would like, we are done
return $this->skipped return $this->skipped
|| (count($this->getAllIndexes() ?? []) <= count($this->completed ?? [])); || (count($this->getAllIndexes()) <= count($this->completed));
} }
public function prepareForRestart() public function prepareForRestart()
@ -213,7 +213,7 @@ class SearchUpdateCommitJobProcessor implements QueuedJob
{ {
// Skip index if this is already complete // Skip index if this is already complete
$name = get_class($index); $name = get_class($index);
if (in_array($name, $this->completed ?? [])) { if (in_array($name, $this->completed)) {
$this->addMessage("Skipping already comitted index {$name}"); $this->addMessage("Skipping already comitted index {$name}");
return; return;
} }
@ -240,8 +240,8 @@ class SearchUpdateCommitJobProcessor implements QueuedJob
public function getJobData() public function getJobData()
{ {
$data = new stdClass(); $data = new stdClass();
$data->totalSteps = count($this->getAllIndexes() ?? []); $data->totalSteps = count($this->getAllIndexes());
$data->currentStep = count($this->completed ?? []); $data->currentStep = count($this->completed);
$data->isComplete = $this->jobFinished(); $data->isComplete = $this->jobFinished();
$data->messages = $this->messages; $data->messages = $this->messages;
@ -265,7 +265,7 @@ class SearchUpdateCommitJobProcessor implements QueuedJob
public function addMessage($message, $severity = 'INFO') public function addMessage($message, $severity = 'INFO')
{ {
$severity = strtoupper($severity ?? ''); $severity = strtoupper($severity);
$this->messages[] = '[' . date('Y-m-d H:i:s') . "][$severity] $message"; $this->messages[] = '[' . date('Y-m-d H:i:s') . "][$severity] $message";
} }

View File

@ -5,7 +5,6 @@ namespace SilverStripe\FullTextSearch\Search\Processors;
use SilverStripe\FullTextSearch\Search\Services\SearchableService; use SilverStripe\FullTextSearch\Search\Services\SearchableService;
use SilverStripe\FullTextSearch\Search\Variants\SearchVariantVersioned; use SilverStripe\FullTextSearch\Search\Variants\SearchVariantVersioned;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DB;
use SilverStripe\FullTextSearch\Search\Variants\SearchVariant; use SilverStripe\FullTextSearch\Search\Variants\SearchVariant;
use SilverStripe\FullTextSearch\Search\FullTextSearch; use SilverStripe\FullTextSearch\Search\FullTextSearch;
use SilverStripe\Versioned\Versioned; use SilverStripe\Versioned\Versioned;
@ -55,7 +54,7 @@ abstract class SearchUpdateProcessor
$forclass[$statekey] = array('state' => $state, 'ids' => array($id => array($index))); $forclass[$statekey] = array('state' => $state, 'ids' => array($id => array($index)));
} elseif (!isset($forclass[$statekey]['ids'][$id])) { } elseif (!isset($forclass[$statekey]['ids'][$id])) {
$forclass[$statekey]['ids'][$id] = array($index); $forclass[$statekey]['ids'][$id] = array($index);
} elseif (array_search($index, $forclass[$statekey]['ids'][$id] ?? []) === false) { } elseif (array_search($index, $forclass[$statekey]['ids'][$id]) === false) {
$forclass[$statekey]['ids'][$id][] = $index; $forclass[$statekey]['ids'][$id][] = $index;
// dirty count stays the same // dirty count stays the same
} }
@ -89,7 +88,7 @@ abstract class SearchUpdateProcessor
SearchVariant::activate_state($state); SearchVariant::activate_state($state);
// Ensure that indexes for all new / updated objects are included // Ensure that indexes for all new / updated objects are included
$objs = DataObject::get($base)->byIDs(array_keys($ids ?? [])); $objs = DataObject::get($base)->byIDs(array_keys($ids));
/** @var DataObject $obj */ /** @var DataObject $obj */
foreach ($objs as $obj) { foreach ($objs as $obj) {
@ -155,9 +154,6 @@ abstract class SearchUpdateProcessor
*/ */
public function process() public function process()
{ {
if (!DB::is_active()) {
return false;
}
// Generate and commit all instances // Generate and commit all instances
$indexes = $this->prepareIndexes(); $indexes = $this->prepareIndexes();
foreach ($indexes as $index) { foreach ($indexes as $index) {

View File

@ -45,7 +45,7 @@ class SearchUpdateQueuedJobProcessor extends SearchUpdateBatchedProcessor implem
public function jobFinished() public function jobFinished()
{ {
return $this->currentBatch >= count($this->batches ?? []); return $this->currentBatch >= count($this->batches);
} }
public function setup() public function setup()
@ -68,7 +68,7 @@ class SearchUpdateQueuedJobProcessor extends SearchUpdateBatchedProcessor implem
public function getJobData() public function getJobData()
{ {
$data = new stdClass(); $data = new stdClass();
$data->totalSteps = count($this->batches ?? []); $data->totalSteps = count($this->batches);
$data->currentStep = $this->currentBatch; $data->currentStep = $this->currentBatch;
$data->isComplete = $this->jobFinished(); $data->isComplete = $this->jobFinished();
$data->messages = $this->messages; $data->messages = $this->messages;
@ -91,7 +91,7 @@ class SearchUpdateQueuedJobProcessor extends SearchUpdateBatchedProcessor implem
public function addMessage($message, $severity = 'INFO') public function addMessage($message, $severity = 'INFO')
{ {
$severity = strtoupper($severity ?? ''); $severity = strtoupper($severity);
$this->messages[] = '[' . date('Y-m-d H:i:s') . "][$severity] $message"; $this->messages[] = '[' . date('Y-m-d H:i:s') . "][$severity] $message";
} }

View File

@ -284,81 +284,81 @@ class SearchQuery extends ViewableData
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
* @deprecated 4.0.0 Use addSearchTerm() instead * @deprecated
*/ */
public function search($text, $fields = null, $boost = []) public function search($text, $fields = null, $boost = [])
{ {
Deprecation::notice('4.0.0', 'Use addSearchTerm() instead'); Deprecation::notice('4.0', 'Use addSearchTerm() instead');
return $this->addSearchTerm($text, $fields, $boost); return $this->addSearchTerm($text, $fields, $boost);
} }
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
* @deprecated 4.0.0 Use addFuzzySearchTerm() instead * @deprecated
*/ */
public function fuzzysearch($text, $fields = null, $boost = []) public function fuzzysearch($text, $fields = null, $boost = [])
{ {
Deprecation::notice('4.0.0', 'Use addFuzzySearchTerm() instead'); Deprecation::notice('4.0', 'Use addFuzzySearchTerm() instead');
return $this->addFuzzySearchTerm($text, $fields, $boost); return $this->addFuzzySearchTerm($text, $fields, $boost);
} }
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
* @deprecated 4.0.0 Use addClassFilter() instead * @deprecated
*/ */
public function inClass($class, $includeSubclasses = true) public function inClass($class, $includeSubclasses = true)
{ {
Deprecation::notice('4.0.0', 'Use addClassFilter() instead'); Deprecation::notice('4.0', 'Use addClassFilter() instead');
return $this->addClassFilter($class, $includeSubclasses); return $this->addClassFilter($class, $includeSubclasses);
} }
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
* @deprecated 4.0.0 Use addFilter() instead * @deprecated
*/ */
public function filter($field, $values) public function filter($field, $values)
{ {
Deprecation::notice('4.0.0', 'Use addFilter() instead'); Deprecation::notice('4.0', 'Use addFilter() instead');
return $this->addFilter($field, $values); return $this->addFilter($field, $values);
} }
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
* @deprecated 4.0.0 Use addExclude() instead * @deprecated
*/ */
public function exclude($field, $values) public function exclude($field, $values)
{ {
Deprecation::notice('4.0.0', 'Use addExclude() instead'); Deprecation::notice('4.0', 'Use addExclude() instead');
return $this->addExclude($field, $values); return $this->addExclude($field, $values);
} }
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
* @deprecated 4.0.0 Use setStart() instead * @deprecated
*/ */
public function start($start) public function start($start)
{ {
Deprecation::notice('4.0.0', 'Use setStart() instead'); Deprecation::notice('4.0', 'Use setStart() instead');
return $this->setStart($start); return $this->setStart($start);
} }
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
* @deprecated 4.0.0 Use setLimit() instead * @deprecated
*/ */
public function limit($limit) public function limit($limit)
{ {
Deprecation::notice('4.0.0', 'Use setLimit() instead'); Deprecation::notice('4.0', 'Use setLimit() instead');
return $this->setLimit($limit); return $this->setLimit($limit);
} }
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
* @deprecated 4.0.0 Use setPageSize() instead * @deprecated
*/ */
public function page($page) public function page($page)
{ {
Deprecation::notice('4.0.0', 'Use setPageSize() instead'); Deprecation::notice('4.0', 'Use setPageSize() instead');
return $this->setPageSize($page); return $this->setPageSize($page);
} }

View File

@ -40,22 +40,22 @@ class SearchQuery_Range
} }
/** /**
* @deprecated 4.0.0 Use setStart() instead * @deprecated
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
public function start($start) public function start($start)
{ {
Deprecation::notice('4.0.0', 'Use setStart() instead'); Deprecation::notice('4.0', 'Use setStart() instead');
return $this->setStart($start); return $this->setStart($start);
} }
/** /**
* @deprecated 4.0.0 Use setEnd() instead * @deprecated
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
public function end($end) public function end($end)
{ {
Deprecation::notice('4.0.0', 'Use setEnd() instead'); Deprecation::notice('4.0', 'Use setEnd() instead');
return $this->setEnd($end); return $this->setEnd($end);
} }
} }

View File

@ -21,7 +21,7 @@ class SearchIntrospection
public 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)); $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 ?? []); return is_array($of) ? (bool)array_intersect($of, $ancestry) : array_key_exists($of, $ancestry);
} }
protected static $hierarchy = array(); protected static $hierarchy = array();
@ -40,12 +40,12 @@ class SearchIntrospection
$key = "$class!" . ($includeSubclasses ? 'sc' : 'an') . '!' . ($dataOnly ? 'do' : 'al'); $key = "$class!" . ($includeSubclasses ? 'sc' : 'an') . '!' . ($dataOnly ? 'do' : 'al');
if (!isset(self::$hierarchy[$key])) { if (!isset(self::$hierarchy[$key])) {
$classes = array_values(ClassInfo::ancestry($class) ?? []); $classes = array_values(ClassInfo::ancestry($class));
if ($includeSubclasses) { if ($includeSubclasses) {
$classes = array_unique(array_merge($classes, array_values(ClassInfo::subclassesFor($class) ?? []))); $classes = array_unique(array_merge($classes, array_values(ClassInfo::subclassesFor($class))));
} }
$idx = array_search(DataObject::class, $classes ?? []); $idx = array_search(DataObject::class, $classes);
if ($idx !== false) { if ($idx !== false) {
array_splice($classes, 0, $idx+1); array_splice($classes, 0, $idx+1);
} }
@ -76,7 +76,7 @@ class SearchIntrospection
// Strip out any subclasses of $class already in the list // Strip out any subclasses of $class already in the list
$children = ClassInfo::subclassesFor($class); $children = ClassInfo::subclassesFor($class);
$list = array_diff($list ?? [], $children); $list = array_diff($list, $children);
// Then add the class in // Then add the class in
$list[] = $class; $list[] = $class;

View File

@ -151,12 +151,12 @@ class SearchableService
// Anonymous member canView() for indexing // Anonymous member canView() for indexing
if (!$this->classSkipsCanViewCheck($objClass)) { if (!$this->classSkipsCanViewCheck($objClass)) {
$value = Member::actAs(null, function () use ($obj) { $value = Member::actAs(null, function () use ($obj) {
return (bool) $obj->canView(); return $obj->canView();
}); });
} }
} else { } else {
// Current member canView() check for retrieving search results // Current member canView() check for retrieving search results
$value = (bool) $obj->canView(); $value = $obj->canView();
} }
} }
$this->extend('updateIsSearchable', $obj, $indexing, $value); $this->extend('updateIsSearchable', $obj, $indexing, $value);
@ -199,11 +199,11 @@ class SearchableService
if (empty($skipClasses)) { if (empty($skipClasses)) {
return false; return false;
} }
if (in_array($class, $skipClasses ?? [])) { if (in_array($class, $skipClasses)) {
return true; return true;
} }
foreach ($skipClasses as $skipClass) { foreach ($skipClasses as $skipClass) {
if (in_array($skipClass, class_parents($class) ?? [])) { if (in_array($skipClass, class_parents($class))) {
return true; return true;
} }
} }

View File

@ -106,7 +106,7 @@ class SearchUpdater
'command' => $command, 'command' => $command,
'fields' => array() 'fields' => array()
); );
} elseif (is_subclass_of($class, $writes[$key]['class'] ?? '')) { } elseif (is_subclass_of($class, $writes[$key]['class'])) {
// Otherwise update the class label if it's more specific than the currently recorded one // Otherwise update the class label if it's more specific than the currently recorded one
$writes[$key]['class'] = $class; $writes[$key]['class'] = $class;
} }
@ -118,7 +118,7 @@ class SearchUpdater
} }
// Trim non-delete records without fields // Trim non-delete records without fields
foreach (array_keys($writes ?? []) as $key) { foreach (array_keys($writes) as $key) {
if ($writes[$key]['command'] !== 'delete' && empty($writes[$key]['fields'])) { if ($writes[$key]['command'] !== 'delete' && empty($writes[$key]['fields'])) {
unset($writes[$key]); unset($writes[$key]);
} }

View File

@ -105,9 +105,6 @@ abstract class SearchVariant
$ref = new ReflectionClass($variantclass); $ref = new ReflectionClass($variantclass);
if ($ref->isInstantiable()) { if ($ref->isInstantiable()) {
$variant = singleton($variantclass); $variant = singleton($variantclass);
// reassign actual class since Injector could be involved when creating the singleton
$variantclass = get_class($variant);
if ($variant->appliesToEnvironment()) { if ($variant->appliesToEnvironment()) {
$concrete[$variantclass] = $variant; $concrete[$variantclass] = $variant;
} }
@ -205,7 +202,7 @@ abstract class SearchVariant
// Merge the variants applicable to the current class into the list of common variants, using // Merge the variants applicable to the current class into the list of common variants, using
// the variant instance to replace any previous versions for the same class name (should be singleton // the variant instance to replace any previous versions for the same class name (should be singleton
// anyway). // anyway).
$commonVariants = array_replace($commonVariants ?? [], $variantsForClass); $commonVariants = array_replace($commonVariants, $variantsForClass);
} }
// Cache for future calls // Cache for future calls
@ -325,7 +322,7 @@ abstract class SearchVariant
$merged = array_values(array_unique(array_merge($left, $right))); $merged = array_values(array_unique(array_merge($left, $right)));
// If there is only one item, return it as a single string // If there is only one item, return it as a single string
if (count($merged ?? []) === 1) { if (count($merged) === 1) {
return reset($merged); return reset($merged);
} }
return $merged; return $merged;

View File

@ -65,7 +65,7 @@ class SearchVariantVersioned extends SearchVariant
$class = $details['class']; $class = $details['class'];
$stage = Versioned::DRAFT; $stage = Versioned::DRAFT;
if (preg_match('/^(.*)_' . Versioned::LIVE . '$/', $table ?? '', $matches)) { if (preg_match('/^(.*)_' . Versioned::LIVE . '$/', $table, $matches)) {
$class = DataObject::getSchema()->tableClass($matches[1]); $class = DataObject::getSchema()->tableClass($matches[1]);
$stage = Versioned::LIVE; $stage = Versioned::LIVE;
} }

View File

@ -19,7 +19,7 @@ class SearchVariant_Caller
$values = array(); $values = array();
foreach ($this->variants as $variant) { foreach ($this->variants as $variant) {
if (method_exists($variant, $method ?? '')) { if (method_exists($variant, $method)) {
$value = $variant->$method(...$args); $value = $variant->$method(...$args);
if ($value !== null) { if ($value !== null) {
$values[] = $value; $values[] = $value;

View File

@ -81,9 +81,9 @@ abstract class SolrReindexBase implements SolrReindexHandler
// Apply filter // Apply filter
if (!is_array($filterClasses)) { if (!is_array($filterClasses)) {
$filterClasses = explode(',', $filterClasses ?? ''); $filterClasses = explode(',', $filterClasses);
} }
return array_intersect_key($classes ?? [], array_combine($filterClasses ?? [], $filterClasses ?? [])); return array_intersect_key($classes, array_combine($filterClasses, $filterClasses));
} }
/** /**

View File

@ -77,26 +77,27 @@ class SolrReindexImmediateHandler extends SolrReindexBase
$indexClass = get_class($indexInstance); $indexClass = get_class($indexInstance);
// Build script parameters // Build script parameters
$indexClassEscaped = $indexClass;
$statevar = json_encode($state); $statevar = json_encode($state);
if (strpos(PHP_OS, "WIN") !== false) {
$statevar = '"' . str_replace('"', '\\"', $statevar) . '"';
} else {
$statevar = "'" . $statevar . "'";
$class = addslashes($class);
$indexClassEscaped = addslashes($indexClass);
}
$php = Environment::getEnv('SS_PHP_BIN') ?: Config::inst()->get(static::class, 'php_bin'); $php = Environment::getEnv('SS_PHP_BIN') ?: Config::inst()->get(static::class, 'php_bin');
// Build script line // Build script line
$frameworkPath = ModuleLoader::getModule('silverstripe/framework')->getPath(); $frameworkPath = ModuleLoader::getModule('silverstripe/framework')->getPath();
$scriptPath = sprintf("%s%scli-script.php", $frameworkPath, DIRECTORY_SEPARATOR); $scriptPath = sprintf("%s%scli-script.php", $frameworkPath, DIRECTORY_SEPARATOR);
$scriptTask = "{$php} {$scriptPath} dev/tasks/{$taskName}";
$cmd = [ $cmd = "{$scriptTask} index={$indexClassEscaped} class={$class} group={$group} groups={$groups} variantstate={$statevar}";
$php, $cmd .= " verbose=1";
$scriptPath, $logger->info("Running '$cmd'");
"dev/tasks/{$taskName}",
"index={$indexClass}",
"class={$class}",
"group={$group}",
"groups={$groups}",
"variantstate={$statevar}",
"verbose=1"
];
$logger->info('Running ' . implode(' ', $cmd));
// Execute script via shell // Execute script via shell
$process = new Process($cmd); $process = new Process($cmd);
@ -109,7 +110,7 @@ class SolrReindexImmediateHandler extends SolrReindexBase
$res = $process->getOutput(); $res = $process->getOutput();
if ($logger) { if ($logger) {
$logger->info(preg_replace('/\r\n|\n/', '$0 ', $res ?? '')); $logger->info(preg_replace('/\r\n|\n/', '$0 ', $res));
} }
// If we're in dev mode, commit more often for fun and profit // If we're in dev mode, commit more often for fun and profit

View File

@ -24,7 +24,7 @@ class SolrService extends SolrService_Core
*/ */
protected function coreCommand($command, $core, $params = array()) protected function coreCommand($command, $core, $params = array())
{ {
$command = strtoupper($command ?? ''); $command = strtoupper($command);
$params = array_merge($params, array('action' => $command, 'wt' => 'json')); $params = array_merge($params, array('action' => $command, 'wt' => 'json'));
$params[$command === 'CREATE' ? 'name' : 'core'] = $core; $params[$command === 'CREATE' ? 'name' : 'core'] = $core;

View File

@ -94,7 +94,7 @@ class Solr
$module = ModuleLoader::getModule('silverstripe/fulltextsearch'); $module = ModuleLoader::getModule('silverstripe/fulltextsearch');
$modulePath = $module->getPath(); $modulePath = $module->getPath();
if (version_compare($version ?? '', '4', '>=')) { if (version_compare($version, '4', '>=')) {
$versionDefaults = [ $versionDefaults = [
'service' => Solr4Service::class, 'service' => Solr4Service::class,
'extraspath' => $modulePath . '/conf/solr/4/extras/', 'extraspath' => $modulePath . '/conf/solr/4/extras/',

View File

@ -89,7 +89,7 @@ abstract class SolrIndex extends SearchIndex
{ {
$globalOptions = Solr::solr_options(); $globalOptions = Solr::solr_options();
$path = $this->templatesPath ? $this->templatesPath : $globalOptions['templatespath']; $path = $this->templatesPath ? $this->templatesPath : $globalOptions['templatespath'];
return rtrim($path ?? '', '/'); return rtrim($path, '/');
} }
/** /**
@ -127,7 +127,7 @@ abstract class SolrIndex extends SearchIndex
$indexParts[] = $indexSuffix; $indexParts[] = $indexSuffix;
} }
return implode($indexParts ?? ''); return implode($indexParts);
} }
public function getTypes() public function getTypes()
@ -256,11 +256,11 @@ abstract class SolrIndex extends SearchIndex
*/ */
protected function getNiceSuggestion($collation = '') protected function getNiceSuggestion($collation = '')
{ {
$collationParts = explode(' ', $collation ?? ''); $collationParts = explode(' ', $collation);
// Remove advanced query params from the beginning of each collation part. // Remove advanced query params from the beginning of each collation part.
foreach ($collationParts as $key => &$part) { foreach ($collationParts as $key => &$part) {
$part = ltrim($part ?? '', '+'); $part = ltrim($part, '+');
} }
return implode(' ', $collationParts); return implode(' ', $collationParts);
@ -275,7 +275,7 @@ abstract class SolrIndex extends SearchIndex
*/ */
protected function getSuggestionQueryString($collation = '') protected function getSuggestionQueryString($collation = '')
{ {
return str_replace(' ', '+', $this->getNiceSuggestion($collation) ?? ''); return str_replace(' ', '+', $this->getNiceSuggestion($collation));
} }
/** /**
@ -312,7 +312,7 @@ abstract class SolrIndex extends SearchIndex
{ {
// Ensure that 'boost' is recorded here without being captured by solr // Ensure that 'boost' is recorded here without being captured by solr
$boost = null; $boost = null;
if (array_key_exists('boost', $extraOptions ?? [])) { if (array_key_exists('boost', $extraOptions)) {
$boost = $extraOptions['boost']; $boost = $extraOptions['boost'];
unset($extraOptions['boost']); unset($extraOptions['boost']);
} }
@ -515,7 +515,7 @@ abstract class SolrIndex extends SearchIndex
} }
// Check single origin // Check single origin
return $class === $base || is_subclass_of($class, $base ?? ''); return $class === $base || is_subclass_of($class, $base);
} }
protected function _addField($doc, $object, $field) protected function _addField($doc, $object, $field)
@ -536,7 +536,7 @@ abstract class SolrIndex extends SearchIndex
if (!$sub) { if (!$sub) {
continue; continue;
} }
$sub = gmdate('Y-m-d\TH:i:s\Z', strtotime($sub ?? '')); $sub = gmdate('Y-m-d\TH:i:s\Z', strtotime($sub));
} }
/* Solr requires numbers to be valid if presented, not just empty */ /* Solr requires numbers to be valid if presented, not just empty */
@ -552,7 +552,7 @@ abstract class SolrIndex extends SearchIndex
if (!$value) { if (!$value) {
return; return;
} }
$value = gmdate('Y-m-d\TH:i:s\Z', strtotime($value ?? '')); $value = gmdate('Y-m-d\TH:i:s\Z', strtotime($value));
} }
/* Solr requires numbers to be valid if presented, not just empty */ /* Solr requires numbers to be valid if presented, not just empty */
@ -586,7 +586,7 @@ abstract class SolrIndex extends SearchIndex
// Add the user-specified fields // Add the user-specified fields
foreach ($this->getFieldsIterator() as $name => $field) { foreach ($this->getFieldsIterator() as $name => $field) {
if ($field['base'] === $base || (is_array($field['base']) && in_array($base, $field['base'] ?? []))) { if ($field['base'] === $base || (is_array($field['base']) && in_array($base, $field['base']))) {
$this->_addField($doc, $object, $field); $this->_addField($doc, $object, $field);
} }
} }
@ -607,7 +607,7 @@ abstract class SolrIndex extends SearchIndex
$docs = array(); $docs = array();
foreach ($this->getClasses() as $searchclass => $options) { foreach ($this->getClasses() as $searchclass => $options) {
if ($searchclass == $class || ($options['include_children'] && is_subclass_of($class, $searchclass ?? ''))) { if ($searchclass == $class || ($options['include_children'] && is_subclass_of($class, $searchclass))) {
$base = DataObject::getSchema()->baseDataClass($searchclass); $base = DataObject::getSchema()->baseDataClass($searchclass);
$docs[] = $this->_addAs($object, $base, $options); $docs[] = $this->_addAs($object, $base, $options);
} }
@ -619,7 +619,7 @@ abstract class SolrIndex extends SearchIndex
public function canAdd($class) public function canAdd($class)
{ {
foreach ($this->classes as $searchclass => $options) { foreach ($this->classes as $searchclass => $options) {
if ($searchclass == $class || ($options['include_children'] && is_subclass_of($class, $searchclass ?? ''))) { if ($searchclass == $class || ($options['include_children'] && is_subclass_of($class, $searchclass))) {
return true; return true;
} }
} }
@ -713,7 +713,7 @@ abstract class SolrIndex extends SearchIndex
// If using boosting, set the clean term separately for highlighting. // If using boosting, set the clean term separately for highlighting.
// See https://issues.apache.org/jira/browse/SOLR-2632 // See https://issues.apache.org/jira/browse/SOLR-2632
if (array_key_exists('hl', $params ?? []) && !array_key_exists('hl.q', $params ?? [])) { if (array_key_exists('hl', $params) && !array_key_exists('hl.q', $params)) {
$params['hl.q'] = implode(' ', $hlq); $params['hl.q'] = implode(' ', $hlq);
} }
@ -870,7 +870,7 @@ abstract class SolrIndex extends SearchIndex
*/ */
protected function applySearchVariants(SearchQuery $query) protected function applySearchVariants(SearchQuery $query)
{ {
$classes = count($query->classes ?? []) ? $query->classes : $this->getClasses(); $classes = count($query->classes) ? $query->classes : $this->getClasses();
/** @var SearchVariant_Caller $variantCaller */ /** @var SearchVariant_Caller $variantCaller */
$variantCaller = SearchVariant::withCommon($classes); $variantCaller = SearchVariant::withCommon($classes);
@ -886,7 +886,7 @@ abstract class SolrIndex extends SearchIndex
*/ */
public function sanitiseClassName($className, $replaceWith = '\\\\') public function sanitiseClassName($className, $replaceWith = '\\\\')
{ {
return str_replace('\\', $replaceWith ?? '', $className ?? ''); return str_replace('\\', $replaceWith, $className);
} }
/** /**
@ -901,14 +901,14 @@ abstract class SolrIndex extends SearchIndex
$q = array(); $q = array();
foreach ($searchQuery->search as $search) { foreach ($searchQuery->search as $search) {
$text = $search['text']; $text = $search['text'];
preg_match_all('/"[^"]*"|\S+/', $text ?? '', $parts); preg_match_all('/"[^"]*"|\S+/', $text, $parts);
$fuzzy = $search['fuzzy'] ? '~' : ''; $fuzzy = $search['fuzzy'] ? '~' : '';
foreach ($parts[0] as $part) { foreach ($parts[0] as $part) {
$fields = (isset($search['fields'])) ? $search['fields'] : array(); $fields = (isset($search['fields'])) ? $search['fields'] : array();
if (isset($search['boost'])) { if (isset($search['boost'])) {
$fields = array_merge($fields, array_keys($search['boost'] ?? [])); $fields = array_merge($fields, array_keys($search['boost']));
} }
if ($fields) { if ($fields) {
$searchq = array(); $searchq = array();
@ -1014,7 +1014,7 @@ abstract class SolrIndex extends SearchIndex
*/ */
protected function getCriteriaComponent(SearchQuery $searchQuery) protected function getCriteriaComponent(SearchQuery $searchQuery)
{ {
if (count($searchQuery->getCriteria() ?? []) === 0) { if (count($searchQuery->getCriteria()) === 0) {
return null; return null;
} }
@ -1095,7 +1095,7 @@ abstract class SolrIndex extends SearchIndex
// Upload additional files // Upload additional files
foreach (glob($this->getExtrasPath() . '/*') as $file) { foreach (glob($this->getExtrasPath() . '/*') as $file) {
if (is_file($file ?? '')) { if (is_file($file)) {
$store->uploadFile($this->getIndexName(), $file); $store->uploadFile($this->getIndexName(), $file);
} }
} }

View File

@ -20,8 +20,8 @@ class SolrConfigStore_File implements SolrConfigStore
{ {
$targetDir = "{$this->local}/{$index}/conf"; $targetDir = "{$this->local}/{$index}/conf";
if (!is_dir($targetDir ?? '')) { if (!is_dir($targetDir)) {
$worked = @mkdir($targetDir ?? '', 0770, true); $worked = @mkdir($targetDir, 0770, true);
if (!$worked) { if (!$worked) {
throw new \RuntimeException( throw new \RuntimeException(
@ -36,7 +36,7 @@ class SolrConfigStore_File implements SolrConfigStore
public function uploadFile($index, $file) public function uploadFile($index, $file)
{ {
$targetDir = $this->getTargetDir($index); $targetDir = $this->getTargetDir($index);
copy($file ?? '', $targetDir . '/' . basename($file ?? '')); copy($file, $targetDir . '/' . basename($file));
} }
public function uploadString($index, $filename, $string) public function uploadString($index, $filename, $string)

View File

@ -43,7 +43,7 @@ class SolrConfigStore_Post implements SolrConfigStore
*/ */
public function uploadFile($index, $file) public function uploadFile($index, $file)
{ {
$this->uploadString($index, basename($file ?? ''), file_get_contents($file ?? '')); $this->uploadString($index, basename($file), file_get_contents($file));
} }
/** /**

View File

@ -46,7 +46,7 @@ class SolrConfigStore_WebDAV implements SolrConfigStore
public function uploadFile($index, $file) public function uploadFile($index, $file)
{ {
$targetDir = $this->getTargetDir($index); $targetDir = $this->getTargetDir($index);
WebDAV::upload_from_file($file, $targetDir . '/' . basename($file ?? '')); WebDAV::upload_from_file($file, $targetDir . '/' . basename($file));
} }
public function uploadString($index, $filename, $string) public function uploadString($index, $filename, $string)

View File

@ -86,7 +86,7 @@ class Solr_Reindex extends Solr_BuildTask
continue; continue;
} }
//check the indexname matches the index passed to the request //check the indexname matches the index passed to the request
if (!strcasecmp(singleton($solrIndexClass)->getIndexName() ?? '', $index ?? '')) { if (!strcasecmp(singleton($solrIndexClass)->getIndexName(), $index)) {
//if we match, set the correct index name and move on //if we match, set the correct index name and move on
$index = $solrIndexClass; $index = $solrIndexClass;
break; break;
@ -104,7 +104,7 @@ class Solr_Reindex extends Solr_BuildTask
// Run grouped batches (id % groups = group) // Run grouped batches (id % groups = group)
$group = $request->getVar('group'); $group = $request->getVar('group');
$indexInstance = singleton($index); $indexInstance = singleton($index);
$state = json_decode($request->getVar('variantstate') ?? '', true); $state = json_decode($request->getVar('variantstate'), true);
$handler->runGroup($this->getLogger(), $indexInstance, $state, $class, $groups, $group); $handler->runGroup($this->getLogger(), $indexInstance, $state, $class, $groups, $group);
return; return;

View File

@ -20,7 +20,7 @@ class SolrSearchQueryWriterBasic extends AbstractSearchQueryWriter
return sprintf( return sprintf(
'%s(%s%s%s)', '%s(%s%s%s)',
$this->getComparisonPolarity($searchCriterion->getComparison()), $this->getComparisonPolarity($searchCriterion->getComparison()),
addslashes($searchCriterion->getTarget() ?? ''), addslashes($searchCriterion->getTarget()),
$this->getComparisonConjunction(), $this->getComparisonConjunction(),
$searchCriterion->getQuoteValue($searchCriterion->getValue()) $searchCriterion->getQuoteValue($searchCriterion->getValue())
); );

View File

@ -62,7 +62,7 @@ class SolrSearchQueryWriterIn extends AbstractSearchQueryWriter
$conditions[] = sprintf( $conditions[] = sprintf(
'%s%s%s', '%s%s%s',
addslashes($searchCriterion->getTarget() ?? ''), addslashes($searchCriterion->getTarget()),
$this->getComparisonConjunction(), $this->getComparisonConjunction(),
$value $value
); );

View File

@ -21,7 +21,7 @@ class SolrSearchQueryWriterRange extends AbstractSearchQueryWriter
return sprintf( return sprintf(
'%s(%s:%s%s%s%s%s)', '%s(%s:%s%s%s%s%s)',
$this->getComparisonPolarity($searchCriterion->getComparison()), $this->getComparisonPolarity($searchCriterion->getComparison()),
addslashes($searchCriterion->getTarget() ?? ''), addslashes($searchCriterion->getTarget()),
$this->getOpenComparisonContainer($searchCriterion->getComparison()), $this->getOpenComparisonContainer($searchCriterion->getComparison()),
$this->getLeftComparison($searchCriterion), $this->getLeftComparison($searchCriterion),
$this->getComparisonConjunction(), $this->getComparisonConjunction(),

View File

@ -18,21 +18,20 @@ class CombinationsArrayIterator implements Iterator
$this->arrays = array(); $this->arrays = array();
$this->keys = array(); $this->keys = array();
$keys = array_keys($args ?? []); $keys = array_keys($args);
$values = array_values($args ?? []); $values = array_values($args);
foreach ($values as $i => $arg) { foreach ($values as $i => $arg) {
if (is_array($arg) && count($arg ?? [])) { if (is_array($arg) && count($arg)) {
$this->arrays[] = $arg; $this->arrays[] = $arg;
$this->keys[] = $keys[$i]; $this->keys[] = $keys[$i];
} }
} }
$this->numArrays = count($this->arrays ?? []); $this->numArrays = count($this->arrays);
$this->rewind(); $this->rewind();
} }
#[\ReturnTypeWillChange]
public function rewind() public function rewind()
{ {
if (!$this->numArrays) { if (!$this->numArrays) {
@ -47,13 +46,11 @@ class CombinationsArrayIterator implements Iterator
} }
} }
#[\ReturnTypeWillChange]
public function valid() public function valid()
{ {
return $this->isValid; return $this->isValid;
} }
#[\ReturnTypeWillChange]
public function next() public function next()
{ {
$this->k++; $this->k++;
@ -71,17 +68,15 @@ class CombinationsArrayIterator implements Iterator
} }
} }
#[\ReturnTypeWillChange]
public function current() public function current()
{ {
$res = array(); $res = array();
for ($i = 0; $i < $this->numArrays; $i++) { for ($i = 0; $i < $this->numArrays; $i++) {
$res[$this->keys[$i]] = current($this->arrays[$i] ?? []); $res[$this->keys[$i]] = current($this->arrays[$i]);
} }
return $res; return $res;
} }
#[\ReturnTypeWillChange]
public function key() public function key()
{ {
return $this->k; return $this->k;

View File

@ -90,7 +90,7 @@ class MonologFactory implements SearchLogFactory
{ {
return Injector::inst()->createWithArgs( return Injector::inst()->createWithArgs(
Logger::class, Logger::class,
array(strtolower($name ?? '')) array(strtolower($name))
); );
} }

View File

@ -14,7 +14,7 @@ class MultipleArrayIterator implements Iterator
$this->arrays = array(); $this->arrays = array();
foreach ($args as $arg) { foreach ($args as $arg) {
if (is_array($arg) && count($arg ?? [])) { if (is_array($arg) && count($arg)) {
$this->arrays[] = $arg; $this->arrays[] = $arg;
} }
} }
@ -22,7 +22,6 @@ class MultipleArrayIterator implements Iterator
$this->rewind(); $this->rewind();
} }
#[\ReturnTypeWillChange]
public function rewind() public function rewind()
{ {
$this->active = $this->arrays; $this->active = $this->arrays;
@ -31,19 +30,16 @@ class MultipleArrayIterator implements Iterator
} }
} }
#[\ReturnTypeWillChange]
public function current() public function current()
{ {
return $this->active ? current($this->active[0]) : false; return $this->active ? current($this->active[0]) : false;
} }
#[\ReturnTypeWillChange]
public function key() public function key()
{ {
return $this->active ? key($this->active[0]) : false; return $this->active ? key($this->active[0]) : false;
} }
#[\ReturnTypeWillChange]
public function next() public function next()
{ {
if (!$this->active) { if (!$this->active) {
@ -58,9 +54,8 @@ class MultipleArrayIterator implements Iterator
} }
} }
#[\ReturnTypeWillChange]
public function valid() public function valid()
{ {
return $this->active && (current($this->active[0] ?? []) !== false); return $this->active && (current($this->active[0]) !== false);
} }
} }

View File

@ -17,19 +17,15 @@ class WebDAV
public static function exists($url) public static function exists($url)
{ {
// WebDAV expects that checking a directory exists has a trailing slash // WebDAV expects that checking a directory exists has a trailing slash
if (substr($url ?? '', -1) != '/') { if (substr($url, -1) != '/') {
$url .= '/'; $url .= '/';
} }
$ch = self::curl_init($url, 'PROPFIND'); $ch = self::curl_init($url, 'PROPFIND');
curl_exec($ch); $res = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$err = curl_error($ch);
curl_close($ch);
if ($code == 404) { if ($code == 404) {
return false; return false;
} }
@ -37,16 +33,15 @@ class WebDAV
return true; return true;
} }
user_error("Got error from webdav server - " . $err, E_USER_ERROR); user_error("Got error from webdav server - " . $code, E_USER_ERROR);
} }
public static function mkdir($url) public static function mkdir($url)
{ {
$ch = self::curl_init(rtrim($url ?? '', '/') . '/', 'MKCOL'); $ch = self::curl_init(rtrim($url, '/') . '/', 'MKCOL');
curl_exec($ch); $res = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $code == 201; return $code == 201;
} }
@ -61,24 +56,22 @@ class WebDAV
curl_setopt($ch, CURLOPT_INFILE, $handle); curl_setopt($ch, CURLOPT_INFILE, $handle);
curl_exec($ch); $res = curl_exec($ch);
fclose($handle); fclose($handle);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $code; return curl_getinfo($ch, CURLINFO_HTTP_CODE);
} }
public static function upload_from_string($string, $url) public static function upload_from_string($string, $url)
{ {
$fh = tmpfile(); $fh = tmpfile();
fwrite($fh, $string ?? ''); fwrite($fh, $string);
fseek($fh, 0); fseek($fh, 0);
return self::put($fh, $url); return self::put($fh, $url);
} }
public static function upload_from_file($string, $url) public static function upload_from_file($string, $url)
{ {
return self::put(fopen($string ?? '', 'rb'), $url); return self::put(fopen($string, 'rb'), $url);
} }
} }

View File

@ -106,7 +106,7 @@ class BatchedProcessorTest extends SapphireTest
$processor->addDirtyIDs( $processor->addDirtyIDs(
BatchedProcessorTest_Object::class, BatchedProcessorTest_Object::class,
array(array( array(array(
'id' => $object->ID, 'id' => $id,
'state' => array(SearchVariantVersioned::class => 'Stage') 'state' => array(SearchVariantVersioned::class => 'Stage')
)), )),
BatchedProcessorTest_Index::class BatchedProcessorTest_Index::class
@ -133,28 +133,28 @@ class BatchedProcessorTest extends SapphireTest
$this->assertEquals(9, $data->totalSteps); $this->assertEquals(9, $data->totalSteps);
$this->assertEquals(0, $data->currentStep); $this->assertEquals(0, $data->currentStep);
$this->assertEmpty($data->isComplete); $this->assertEmpty($data->isComplete);
$this->assertEquals(0, count($index->getAdded() ?? [])); $this->assertEquals(0, count($index->getAdded()));
// Advance state // Advance state
for ($pass = 1; $pass <= 8; $pass++) { for ($pass = 1; $pass <= 8; $pass++) {
$processor->process(); $processor->process();
$data = $processor->getJobData(); $data = $processor->getJobData();
$this->assertEquals($pass, $data->currentStep); $this->assertEquals($pass, $data->currentStep);
$this->assertEquals($pass * 5, count($index->getAdded() ?? [])); $this->assertEquals($pass * 5, count($index->getAdded()));
} }
// Last run should have two hanging items // Last run should have two hanging items
$processor->process(); $processor->process();
$data = $processor->getJobData(); $data = $processor->getJobData();
$this->assertEquals(9, $data->currentStep); $this->assertEquals(9, $data->currentStep);
$this->assertEquals(42, count($index->getAdded() ?? [])); $this->assertEquals(42, count($index->getAdded()));
$this->assertTrue($data->isComplete); $this->assertTrue($data->isComplete);
// Check any additional queued jobs // Check any additional queued jobs
$processor->afterComplete(); $processor->afterComplete();
$service = singleton(QueuedJobService::class); $service = singleton(QueuedJobService::class);
$jobs = $service->getJobs(); $jobs = $service->getJobs();
$this->assertEquals(1, count($jobs ?? [])); $this->assertEquals(1, count($jobs));
$this->assertInstanceOf(SearchUpdateCommitJobProcessor::class, $jobs[0]['job']); $this->assertInstanceOf(SearchUpdateCommitJobProcessor::class, $jobs[0]['job']);
} }
@ -242,7 +242,7 @@ class BatchedProcessorTest extends SapphireTest
} }
$data = $processor->getJobData(); $data = $processor->getJobData();
$this->assertEquals(8, $data->currentStep); $this->assertEquals(8, $data->currentStep);
$this->assertEquals(42, count($index->getAdded() ?? [])); $this->assertEquals(42, count($index->getAdded()));
$this->assertTrue($data->isComplete); $this->assertTrue($data->isComplete);
} }
} }

View File

@ -31,7 +31,7 @@ class SearchVariantSubsiteTest extends SapphireTest
self::$index = singleton(static::class); self::$index = singleton(static::class);
} }
Config::inst()->merge(Injector::class, SearchUpdateProcessor::class, [ Config::inst()->update(Injector::class, SearchUpdateProcessor::class, [
'class' => SearchUpdateImmediateProcessor::class 'class' => SearchUpdateImmediateProcessor::class
]); ]);

View File

@ -223,7 +223,7 @@ class SolrIndexSubsitesTest extends SapphireTest
$subsiteIDs = ['0'] + $this->allFixtureIDs(Subsite::class); $subsiteIDs = ['0'] + $this->allFixtureIDs(Subsite::class);
$subsiteIDs = array_map(function ($v) { $subsiteIDs = array_map(function ($v) {
return (string) $v; return (string) $v;
}, $subsiteIDs ?? []); }, $subsiteIDs);
$mockWrites = [ $mockWrites = [
'35910:File:a:0:{}' => [ '35910:File:a:0:{}' => [
'base' => File::class, 'base' => File::class,
@ -253,7 +253,7 @@ class SolrIndexSubsitesTest extends SapphireTest
$tmpMockWrites = $mockWrites; $tmpMockWrites = $mockWrites;
$variant->extractManipulationWriteState($tmpMockWrites); $variant->extractManipulationWriteState($tmpMockWrites);
foreach ($tmpMockWrites as $mockWrite) { foreach ($tmpMockWrites as $mockWrite) {
$this->assertCount(count($subsiteIDs ?? []), $mockWrite['statefulids']); $this->assertCount(count($subsiteIDs), $mockWrite['statefulids']);
foreach ($mockWrite['statefulids'] as $statefulIDs) { foreach ($mockWrite['statefulids'] as $statefulIDs) {
$this->assertContains( $this->assertContains(
(string) $statefulIDs['state'][SearchVariantSubsites::class], (string) $statefulIDs['state'][SearchVariantSubsites::class],

View File

@ -478,7 +478,7 @@ class SolrIndexTest extends SapphireTest
SolrIndexTest_MyDataObjectOne::class . $objOneA->ID, SolrIndexTest_MyDataObjectOne::class . $objOneA->ID,
SolrIndexTest_MyDataObjectTwo::class . $objTwoA->ID SolrIndexTest_MyDataObjectTwo::class . $objTwoA->ID
]; ];
return in_array($this->createSolrDocKey($doc), $validKeys ?? []); return in_array($this->createSolrDocKey($doc), $validKeys);
}; };
$serviceMock $serviceMock
@ -504,7 +504,7 @@ class SolrIndexTest extends SapphireTest
->method('deleteById') ->method('deleteById')
->withConsecutive( ->withConsecutive(
[$this->callback(function (string $docID) use ($pageA): bool { [$this->callback(function (string $docID) use ($pageA): bool {
return strpos($docID ?? '', $pageA->ID . '-' . SiteTree::class) !== false; return strpos($docID, $pageA->ID . '-' . SiteTree::class) !== false;
})] })]
); );
@ -578,7 +578,7 @@ class SolrIndexTest extends SapphireTest
File::class . $fileA->ID, File::class . $fileA->ID,
SolrIndexTest_MyDataObjectOne::class . $objOneA->ID SolrIndexTest_MyDataObjectOne::class . $objOneA->ID
]; ];
return in_array($this->createSolrDocKey($doc), $validKeys ?? []); return in_array($this->createSolrDocKey($doc), $validKeys);
}; };
$serviceMock $serviceMock
@ -602,7 +602,7 @@ class SolrIndexTest extends SapphireTest
->method('deleteById') ->method('deleteById')
->withConsecutive( ->withConsecutive(
[$this->callback(function (string $docID) use ($pageA): bool { [$this->callback(function (string $docID) use ($pageA): bool {
return strpos($docID ?? '', $pageA->ID . '-' . SiteTree::class) !== false; return strpos($docID, $pageA->ID . '-' . SiteTree::class) !== false;
})] })]
); );

View File

@ -233,9 +233,9 @@ class SolrReindexQueuedTest extends SapphireTest
// Check IDs // Check IDs
$idMessage = $logger->filterMessages('Updated '); $idMessage = $logger->filterMessages('Updated ');
$this->assertNotEmpty(preg_match('/^Updated (?<ids>[,\d]+)/i', $idMessage[0] ?? '', $matches)); $this->assertNotEmpty(preg_match('/^Updated (?<ids>[,\d]+)/i', $idMessage[0], $matches));
$ids = array_unique(explode(',', $matches['ids'] ?? '')); $ids = array_unique(explode(',', $matches['ids']));
$this->assertEquals(6, count($ids ?? [])); $this->assertEquals(6, count($ids));
foreach ($ids as $id) { foreach ($ids as $id) {
// Each id should be % 3 == 0 // Each id should be % 3 == 0
$this->assertEquals(0, $id % 3, "ID $id Should match pattern ID % 3 = 0"); $this->assertEquals(0, $id % 3, "ID $id Should match pattern ID % 3 = 0");

View File

@ -250,8 +250,8 @@ class SolrReindexTest extends SapphireTest
$state = array(SolrReindexTest_Variant::class => '1'); $state = array(SolrReindexTest_Variant::class => '1');
$this->getHandler()->runGroup($logger, $this->index, $state, SolrReindexTest_Item::class, 6, 2); $this->getHandler()->runGroup($logger, $this->index, $state, SolrReindexTest_Item::class, 6, 2);
$idMessage = $logger->filterMessages('Updated '); $idMessage = $logger->filterMessages('Updated ');
$this->assertNotEmpty(preg_match('/^Updated (?<ids>[,\d]+)/i', $idMessage[0] ?? '', $matches)); $this->assertNotEmpty(preg_match('/^Updated (?<ids>[,\d]+)/i', $idMessage[0], $matches));
$ids = array_unique(explode(',', $matches['ids'] ?? '')); $ids = array_unique(explode(',', $matches['ids']));
// Test successful // Test successful
$this->assertNotEmpty($logger->getMessages('Adding ' . SolrReindexTest_Item::class)); $this->assertNotEmpty($logger->getMessages('Adding ' . SolrReindexTest_Item::class));
@ -259,7 +259,7 @@ class SolrReindexTest extends SapphireTest
// Test that items in this variant / group are re-indexed // Test that items in this variant / group are re-indexed
// 120 divided into 6 groups should be 20 at least (max 21) // 120 divided into 6 groups should be 20 at least (max 21)
$c = count($ids ?? []); $c = count($ids);
$this->assertTrue($c === 20 || $c === 21, 'Group size is about 20'); $this->assertTrue($c === 20 || $c === 21, 'Group size is about 20');
foreach ($ids as $id) { foreach ($ids as $id) {
// Each id should be % 6 == 2 // Each id should be % 6 == 2
@ -301,12 +301,12 @@ class SolrReindexTest extends SapphireTest
// Count all ids updated // Count all ids updated
$ids = array(); $ids = array();
foreach ($logger->filterMessages('Updated ') as $message) { foreach ($logger->filterMessages('Updated ') as $message) {
$this->assertNotEmpty(preg_match('/^Updated (?<ids>[,\d]+)/', $message ?? '', $matches)); $this->assertNotEmpty(preg_match('/^Updated (?<ids>[,\d]+)/', $message, $matches));
$ids = array_unique(array_merge($ids, explode(',', $matches['ids'] ?? ''))); $ids = array_unique(array_merge($ids, explode(',', $matches['ids'])));
} }
// Check ids // Check ids
$this->assertEquals(120, count($ids ?? [])); $this->assertEquals(120, count($ids));
} }
/** /**
@ -385,7 +385,7 @@ class SolrReindexTest extends SapphireTest
SolrIndexTest_MyDataObjectOne::class . $objOneA->ID, SolrIndexTest_MyDataObjectOne::class . $objOneA->ID,
SolrIndexTest_MyDataObjectTwo::class . $objTwoA->ID SolrIndexTest_MyDataObjectTwo::class . $objTwoA->ID
]; ];
return in_array($this->createSolrDocKey($doc), $validKeys ?? []); return in_array($this->createSolrDocKey($doc), $validKeys);
}; };
$serviceMock $serviceMock
@ -470,7 +470,7 @@ class SolrReindexTest extends SapphireTest
SolrIndexTest_MyDataObjectOne::class . $objOneA->ID, SolrIndexTest_MyDataObjectOne::class . $objOneA->ID,
]; ];
$solrDocKey = $this->createSolrDocKey($doc); $solrDocKey = $this->createSolrDocKey($doc);
return in_array($this->createSolrDocKey($doc), $validKeys ?? []); return in_array($this->createSolrDocKey($doc), $validKeys);
}; };
$serviceMock $serviceMock

View File

@ -49,9 +49,9 @@ class SolrReindexTest_RecordingLogger extends Logger implements TestOnly
public function filterMessages($containing) public function filterMessages($containing)
{ {
return array_values(array_filter( return array_values(array_filter(
$this->getMessages() ?? [], $this->getMessages(),
function ($content) use ($containing) { function ($content) use ($containing) {
return stripos($content ?? '', $containing ?? '') !== false; return stripos($content, $containing) !== false;
} }
)); ));
} }
@ -69,6 +69,6 @@ class SolrReindexTest_RecordingLogger extends Logger implements TestOnly
} else { } else {
$messages = $this->getMessages(); $messages = $this->getMessages();
} }
return count($messages ?? []); return count($messages);
} }
} }

View File

@ -185,7 +185,7 @@ class SolrWritersTest extends SapphireTest
$index = new SolrIndexTest_FakeIndex(); $index = new SolrIndexTest_FakeIndex();
$this->assertTrue(in_array($expected, $index->getFiltersComponent($query) ?? [])); $this->assertTrue(in_array($expected, $index->getFiltersComponent($query)));
} }
/** /**
@ -235,6 +235,6 @@ class SolrWritersTest extends SapphireTest
$index = new SolrIndexTest_FakeIndex(); $index = new SolrIndexTest_FakeIndex();
$this->assertTrue(in_array($expected, $index->getFiltersComponent($query) ?? [])); $this->assertTrue(in_array($expected, $index->getFiltersComponent($query)));
} }
} }