refactored search with free search term support (#1064)

This commit is contained in:
Kevin Papst
2019-09-04 18:54:06 +02:00
committed by GitHub
parent 49e1a1c410
commit e99b170d0a
147 changed files with 2071 additions and 1322 deletions

View File

@@ -18,6 +18,11 @@ class ThemeConfiguration implements SystemBundleConfiguration
return 'theme';
}
public function isAutoReloadDatatable(): bool
{
return (bool) $this->find('auto_reload_datatable');
}
public function getSelectPicker(): string
{
return (string) $this->find('select_type');

View File

@@ -266,6 +266,14 @@ class SystemConfigurationController extends AbstractController
->setLabel('theme.markdown_content')
->setType(CheckboxType::class)
->setTranslationDomain('system-configuration'),
// FIXME should that be configurable per user?
/*
(new Configuration())
->setName('theme.auto_reload_datatable')
->setLabel('theme.auto_reload_datatable') // FIXME translation
->setType(CheckboxType::class)
->setTranslationDomain('system-configuration'),
*/
]),
(new SystemConfigurationModel())
->setSection(SystemConfigurationModel::SECTION_CALENDAR)

View File

@@ -314,6 +314,9 @@ class Configuration implements ConfigurationInterface
->scalarNode('select_type')
->defaultNull()
->end()
->scalarNode('auto_reload_datatable')
->defaultFalse()
->end()
->booleanNode('show_about')
->defaultTrue()
->end()

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Form\DataTransformer;
use App\Utils\SearchTerm;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class SearchTermTransformer implements DataTransformerInterface
{
/**
* Transforms a SearchTerm object to a string.
*
* @param SearchTerm|null $searchTerm
* @return string
*/
public function transform($searchTerm)
{
if (empty($searchTerm) || !$searchTerm instanceof SearchTerm) {
return '';
}
return $searchTerm->getOriginalSearch();
}
/**
* Transforms a string to a SearchTerm object.
*
* @param string $searchTerm
* @return SearchTerm|null
* @throws TransformationFailedException if object (issue) is not found
*/
public function reverseTransform($searchTerm)
{
if (empty($searchTerm)) {
return null;
}
return new SearchTerm($searchTerm);
}
}

View File

@@ -15,6 +15,7 @@ use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Converts normal select boxes into javascript enhanced versions.
@@ -49,6 +50,10 @@ class EnhancedChoiceTypeExtension extends AbstractTypeExtension
return;
}
if (isset($options['selectpicker']) && false === $options['selectpicker']) {
return;
}
if (!isset($view->vars['attr'])) {
$view->vars['attr'] = [];
}
@@ -58,4 +63,14 @@ class EnhancedChoiceTypeExtension extends AbstractTypeExtension
['class' => 'selectpicker', 'data-live-search' => true, 'data-width' => '100%']
);
}
/**
* @param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefined(['selectpicker']);
$resolver->setAllowedTypes('selectpicker', 'boolean');
$resolver->setDefault('selectpicker', true);
}
}

View File

@@ -14,6 +14,7 @@ use App\Form\Type\CustomerType;
use App\Form\Type\DateRangeType;
use App\Form\Type\PageSizeType;
use App\Form\Type\ProjectType;
use App\Form\Type\SearchTermType;
use App\Form\Type\TagsInputType;
use App\Form\Type\UserRoleType;
use App\Form\Type\UserType;
@@ -50,11 +51,17 @@ abstract class AbstractToolbarForm extends AbstractType
return '';
}
protected function getSelectpickerConfig(): bool
{
return false;
}
protected function addUserChoice(FormBuilderInterface $builder)
{
$builder->add('user', UserType::class, [
'label' => 'label.user',
'required' => false,
'selectpicker' => $this->getSelectpickerConfig(),
]);
}
@@ -64,6 +71,7 @@ abstract class AbstractToolbarForm extends AbstractType
'label' => 'label.user',
'multiple' => true,
'required' => false,
'selectpicker' => $this->getSelectpickerConfig(),
]);
}
@@ -88,6 +96,7 @@ abstract class AbstractToolbarForm extends AbstractType
return $repo->getQueryBuilderForFormType($query);
},
'selectpicker' => $this->getSelectpickerConfig(),
]);
}
);
@@ -98,7 +107,8 @@ abstract class AbstractToolbarForm extends AbstractType
$builder->add('visibility', VisibilityType::class, [
'required' => false,
'placeholder' => null,
'label' => $label
'label' => $label,
'selectpicker' => $this->getSelectpickerConfig(),
]);
}
@@ -106,6 +116,7 @@ abstract class AbstractToolbarForm extends AbstractType
{
$builder->add('pageSize', PageSizeType::class, [
'required' => false,
'selectpicker' => $this->getSelectpickerConfig(),
]);
}
@@ -113,6 +124,7 @@ abstract class AbstractToolbarForm extends AbstractType
{
$builder->add('role', UserRoleType::class, [
'required' => false,
'selectpicker' => $this->getSelectpickerConfig(),
]);
}
@@ -149,6 +161,7 @@ abstract class AbstractToolbarForm extends AbstractType
return $repo->getQueryBuilderForFormType($query);
},
'selectpicker' => $this->getSelectpickerConfig(),
]);
}
);
@@ -177,6 +190,7 @@ abstract class AbstractToolbarForm extends AbstractType
return $repo->getQueryBuilderForFormType($query);
},
'selectpicker' => $this->getSelectpickerConfig(),
]);
}
);
@@ -196,6 +210,11 @@ abstract class AbstractToolbarForm extends AbstractType
]);
}
protected function addSearchTermInputField(FormBuilderInterface $builder)
{
$builder->add('searchTerm', SearchTermType::class);
}
protected function addTimesheetStateChoice(FormBuilderInterface $builder)
{
$builder->add('state', ChoiceType::class, [
@@ -207,6 +226,7 @@ abstract class AbstractToolbarForm extends AbstractType
'entryState.running' => TimesheetQuery::STATE_RUNNING,
'entryState.stopped' => TimesheetQuery::STATE_STOPPED
],
'selectpicker' => $this->getSelectpickerConfig(),
]);
}
@@ -221,6 +241,7 @@ abstract class AbstractToolbarForm extends AbstractType
'entryState.exported' => TimesheetQuery::STATE_EXPORTED,
'entryState.not_exported' => TimesheetQuery::STATE_NOT_EXPORTED
],
'selectpicker' => $this->getSelectpickerConfig(),
]);
}
}

View File

@@ -24,8 +24,9 @@ class ActivityToolbarForm extends AbstractToolbarForm
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->addPageSizeChoice($builder);
$this->addVisibilityChoice($builder);
$this->addSearchTermInputField($builder);
$this->addCustomerChoice($builder);
$this->addProjectChoice($builder);
$builder->add('globalsOnly', ChoiceType::class, [
'choices' => [
'yes' => 1,
@@ -34,9 +35,10 @@ class ActivityToolbarForm extends AbstractToolbarForm
'placeholder' => null,
'required' => false,
'label' => 'label.globalsOnly',
'selectpicker' => $this->getSelectpickerConfig(),
]);
$this->addCustomerChoice($builder);
$this->addProjectChoice($builder);
$this->addVisibilityChoice($builder);
$this->addPageSizeChoice($builder);
$this->addHiddenPagination($builder);
}

View File

@@ -23,8 +23,9 @@ class CustomerToolbarForm extends AbstractToolbarForm
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->addPageSizeChoice($builder);
$this->addSearchTermInputField($builder);
$this->addVisibilityChoice($builder);
$this->addPageSizeChoice($builder);
$this->addHiddenPagination($builder);
}

View File

@@ -21,11 +21,17 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
*/
class ExportToolbarForm extends AbstractToolbarForm
{
protected function getSelectpickerConfig(): bool
{
return true;
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->addSearchTermInputField($builder);
$this->addExportStateChoice($builder);
$this->addTimesheetStateChoice($builder);
$this->addUsersChoice($builder);

View File

@@ -21,11 +21,17 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
*/
class InvoiceToolbarForm extends AbstractToolbarForm
{
protected function getSelectpickerConfig(): bool
{
return true;
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->addSearchTermInputField($builder);
$this->addTemplateChoice($builder);
$this->addUsersChoice($builder);
$this->addDateRangeChoice($builder);
@@ -52,6 +58,7 @@ class InvoiceToolbarForm extends AbstractToolbarForm
$builder->add('template', InvoiceTemplateType::class, [
'required' => true,
'placeholder' => null,
'selectpicker' => $this->getSelectpickerConfig(),
]);
}

View File

@@ -23,9 +23,10 @@ class ProjectToolbarForm extends AbstractToolbarForm
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->addPageSizeChoice($builder);
$this->addVisibilityChoice($builder);
$this->addSearchTermInputField($builder);
$this->addCustomerChoice($builder);
$this->addVisibilityChoice($builder);
$this->addPageSizeChoice($builder);
$this->addHiddenPagination($builder);
}

View File

@@ -20,6 +20,7 @@ class TagToolbarForm extends AbstractToolbarForm
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->addSearchTermInputField($builder);
$this->addPageSizeChoice($builder);
$this->addHiddenPagination($builder);
}

View File

@@ -20,6 +20,7 @@ class TeamToolbarForm extends AbstractToolbarForm
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->addSearchTermInputField($builder);
$this->addPageSizeChoice($builder);
$this->addHiddenPagination($builder);
}

View File

@@ -23,8 +23,7 @@ class TimesheetToolbarForm extends AbstractToolbarForm
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->addTimesheetStateChoice($builder);
$this->addPageSizeChoice($builder);
$this->addSearchTermInputField($builder);
if ($options['include_user']) {
$this->addUserChoice($builder);
}
@@ -33,6 +32,8 @@ class TimesheetToolbarForm extends AbstractToolbarForm
$this->addProjectChoice($builder);
$this->addActivityChoice($builder);
$this->addTagInputField($builder);
$this->addTimesheetStateChoice($builder);
$this->addPageSizeChoice($builder);
$this->addHiddenPagination($builder);
}

View File

@@ -23,9 +23,10 @@ class UserToolbarForm extends AbstractToolbarForm
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->addPageSizeChoice($builder);
$this->addVisibilityChoice($builder, 'label.active');
$this->addSearchTermInputField($builder);
$this->addUserRoleChoice($builder);
$this->addVisibilityChoice($builder, 'label.active');
$this->addPageSizeChoice($builder);
$this->addHiddenPagination($builder);
}

View File

@@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Form\Type;
use App\Form\DataTransformer\SearchTermTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Length;
class SearchTermType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addModelTransformer(new SearchTermTransformer());
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'label' => 'search',
'required' => false,
'constraints' => [
new Length(['min' => 3])
],
]);
}
/**
* {@inheritdoc}
*/
public function getParent()
{
return TextType::class;
}
}

View File

@@ -298,6 +298,37 @@ class ActivityRepository extends EntityRepository
$this->addPermissionCriteria($qb, $query->getCurrentUser());
if ($query->hasSearchTerm()) {
$searchAnd = $qb->expr()->andX();
$searchTerm = $query->getSearchTerm();
foreach ($searchTerm->getSearchFields() as $metaName => $metaValue) {
$qb->leftJoin('a.meta', 'meta');
$searchAnd->add(
$qb->expr()->andX(
$qb->expr()->eq('meta.name', ':metaName'),
$qb->expr()->like('meta.value', ':metaValue')
)
);
$qb->setParameter('metaName', $metaName);
$qb->setParameter('metaValue', '%' . $metaValue . '%');
}
if ($searchTerm->hasSearchTerm()) {
$searchAnd->add(
$qb->expr()->orX(
$qb->expr()->like('a.name', ':searchTerm'),
$qb->expr()->like('a.comment', ':searchTerm')
)
);
$qb->setParameter('searchTerm', '%' . $searchTerm->getSearchTerm() . '%');
}
if ($searchAnd->count() > 0) {
$qb->andWhere($searchAnd);
}
}
return $qb;
}

View File

@@ -210,7 +210,6 @@ class CustomerRepository extends EntityRepository
$qb->select('c')
->from(Customer::class, 'c')
->leftJoin('c.meta', 'meta')
->orderBy('c.' . $query->getOrderBy(), $query->getOrder());
if (CustomerQuery::SHOW_VISIBLE == $query->getVisibility()) {
@@ -223,6 +222,42 @@ class CustomerRepository extends EntityRepository
$this->addPermissionCriteria($qb, $query->getCurrentUser(), $query->getTeams());
if ($query->hasSearchTerm()) {
$searchAnd = $qb->expr()->andX();
$searchTerm = $query->getSearchTerm();
foreach ($searchTerm->getSearchFields() as $metaName => $metaValue) {
$qb->leftJoin('c.meta', 'meta');
$searchAnd->add(
$qb->expr()->andX(
$qb->expr()->eq('meta.name', ':metaName'),
$qb->expr()->like('meta.value', ':metaValue')
)
);
$qb->setParameter('metaName', $metaName);
$qb->setParameter('metaValue', '%' . $metaValue . '%');
}
if ($searchTerm->hasSearchTerm()) {
$searchAnd->add(
$qb->expr()->orX(
$qb->expr()->like('c.name', ':searchTerm'),
$qb->expr()->like('c.comment', ':searchTerm'),
$qb->expr()->like('c.number', ':searchTerm'),
$qb->expr()->like('c.contact', ':searchTerm'),
$qb->expr()->like('c.phone', ':searchTerm'),
$qb->expr()->like('c.email', ':searchTerm'),
$qb->expr()->like('c.address', ':searchTerm')
)
);
$qb->setParameter('searchTerm', '%' . $searchTerm->getSearchTerm() . '%');
}
if ($searchAnd->count() > 0) {
$qb->andWhere($searchAnd);
}
}
return $qb;
}

View File

@@ -212,6 +212,7 @@ class ProjectRepository extends EntityRepository
->select('p')
->from(Project::class, 'p')
->leftJoin('p.customer', 'c')
->addOrderBy('p.' . $query->getOrderBy(), $query->getOrder())
;
if (in_array($query->getVisibility(), [ProjectQuery::SHOW_VISIBLE, ProjectQuery::SHOW_HIDDEN])) {
@@ -236,7 +237,37 @@ class ProjectRepository extends EntityRepository
$this->addPermissionCriteria($qb, $query->getCurrentUser());
$qb->orderBy('p.' . $query->getOrderBy(), $query->getOrder());
if ($query->hasSearchTerm()) {
$searchAnd = $qb->expr()->andX();
$searchTerm = $query->getSearchTerm();
foreach ($searchTerm->getSearchFields() as $metaName => $metaValue) {
$qb->leftJoin('p.meta', 'meta');
$searchAnd->add(
$qb->expr()->andX(
$qb->expr()->eq('meta.name', ':metaName'),
$qb->expr()->like('meta.value', ':metaValue')
)
);
$qb->setParameter('metaName', $metaName);
$qb->setParameter('metaValue', '%' . $metaValue . '%');
}
if ($searchTerm->hasSearchTerm()) {
$searchAnd->add(
$qb->expr()->orX(
$qb->expr()->like('p.name', ':searchTerm'),
$qb->expr()->like('p.comment', ':searchTerm'),
$qb->expr()->like('p.orderNumber', ':searchTerm')
)
);
$qb->setParameter('searchTerm', '%' . $searchTerm->getSearchTerm() . '%');
}
if ($searchAnd->count() > 0) {
$qb->andWhere($searchAnd);
}
}
return $qb;
}

View File

@@ -11,6 +11,7 @@ namespace App\Repository\Query;
use App\Entity\Team;
use App\Entity\User;
use App\Utils\SearchTerm;
/**
* Base class for advanced Repository queries.
@@ -55,6 +56,10 @@ class BaseQuery
* @var Team[]
*/
private $teams = [];
/**
* @var SearchTerm|null
*/
private $searchTerm;
public function addTeam(Team $team): self
{
@@ -188,6 +193,27 @@ class BaseQuery
return $this;
}
public function hasSearchTerm(): bool
{
return null !== $this->searchTerm;
}
public function getSearchTerm(): ?SearchTerm
{
return $this->searchTerm;
}
/**
* @param SearchTerm|null $searchTerm
* @return BaseQuery
*/
public function setSearchTerm(?SearchTerm $searchTerm)
{
$this->searchTerm = $searchTerm;
return $this;
}
/**
* Returns whether the query has changed fields, compared to the original state.
*
@@ -203,6 +229,14 @@ class BaseQuery
return true;
}
if (!empty($this->teams)) {
return true;
}
if (null !== $this->searchTerm) {
return true;
}
return false;
}
}

View File

@@ -93,7 +93,26 @@ class TagRepository extends EntityRepository
->leftJoin('tag.timesheets', 'timesheets')
->addGroupBy('tag.id')
->addGroupBy('tag.name')
->orderBy('tag.' . $query->getOrderBy(), $query->getOrder());
->addOrderBy('tag.' . $query->getOrderBy(), $query->getOrder())
;
if ($query->hasSearchTerm()) {
$searchTerm = $query->getSearchTerm();
$searchAnd = $qb->expr()->andX();
if ($searchTerm->hasSearchTerm()) {
$searchAnd->add(
$qb->expr()->orX(
$qb->expr()->like('tag.name', ':searchTerm')
)
);
$qb->setParameter('searchTerm', '%' . $searchTerm->getSearchTerm() . '%');
}
if ($searchAnd->count() > 0) {
$qb->andWhere($searchAnd);
}
}
$paginator = new Pagerfanta(new DoctrineORMAdapter($qb->getQuery(), false));
$paginator->setMaxPerPage($query->getPageSize());

View File

@@ -104,9 +104,17 @@ class TeamRepository extends EntityRepository
$qb
->select('t')
->from(Team::class, 't')
->addOrderBy('t.' . $query->getOrderBy(), $query->getOrder())
;
$qb->orderBy('t.' . $query->getOrderBy(), $query->getOrder());
if (!empty($query->getSearchTerm())) {
$qb->andWhere(
$qb->expr()->orX(
$qb->expr()->like('t.name', ':likeContains')
)
);
$qb->setParameter('likeContains', '%' . $query->getSearchTerm() . '%');
}
return $qb;
}

View File

@@ -551,6 +551,7 @@ class TimesheetRepository extends EntityRepository
->select('t')
->from(Timesheet::class, 't')
->leftJoin('t.project', 'p')
->addOrderBy('t.' . $query->getOrderBy(), $query->getOrder())
;
$user = [];
@@ -636,7 +637,33 @@ class TimesheetRepository extends EntityRepository
$this->addPermissionCriteria($qb, $query->getCurrentUser(), $query->getTeams());
$qb->orderBy('t.' . $query->getOrderBy(), $query->getOrder());
if ($query->hasSearchTerm()) {
$searchAnd = $qb->expr()->andX();
$searchTerm = $query->getSearchTerm();
foreach ($searchTerm->getSearchFields() as $metaName => $metaValue) {
$qb->leftJoin('t.meta', 'meta');
$searchAnd->add(
$qb->expr()->andX(
$qb->expr()->eq('meta.name', ':metaName'),
$qb->expr()->like('meta.value', ':metaValue')
)
);
$qb->setParameter('metaName', $metaName);
$qb->setParameter('metaValue', '%' . $metaValue . '%');
}
if ($searchTerm->hasSearchTerm()) {
$searchAnd->add(
$qb->expr()->like('t.description', ':searchTerm')
);
$qb->setParameter('searchTerm', '%' . $searchTerm->getSearchTerm() . '%');
}
if ($searchAnd->count() > 0) {
$qb->andWhere($searchAnd);
}
}
return $qb;
}

View File

@@ -109,6 +109,39 @@ class UserRepository extends EntityRepository implements UserLoaderInterface
$qb->andWhere($rolesWhere);
}
if ($query->hasSearchTerm()) {
$searchAnd = $qb->expr()->andX();
$searchTerm = $query->getSearchTerm();
foreach ($searchTerm->getSearchFields() as $metaName => $metaValue) {
$qb->leftJoin('u.preferences', 'meta');
$searchAnd->add(
$qb->expr()->andX(
$qb->expr()->eq('meta.name', ':metaName'),
$qb->expr()->like('meta.value', ':metaValue')
)
);
$qb->setParameter('metaName', $metaName);
$qb->setParameter('metaValue', '%' . $metaValue . '%');
}
if ($searchTerm->hasSearchTerm()) {
$searchAnd->add(
$qb->expr()->orX(
$qb->expr()->like('u.alias', ':searchTerm'),
$qb->expr()->like('u.title', ':searchTerm'),
$qb->expr()->like('u.email', ':searchTerm'),
$qb->expr()->like('u.username', ':searchTerm')
)
);
$qb->setParameter('searchTerm', '%' . $searchTerm->getSearchTerm() . '%');
}
if ($searchAnd->count() > 0) {
$qb->andWhere($searchAnd);
}
}
return $this->getBaseQueryResult($qb, $query);
}

View File

@@ -0,0 +1,46 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Twig;
use App\Configuration\ThemeConfiguration;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class ConfigExtension extends AbstractExtension
{
/**
* @var ThemeConfiguration
*/
protected $configuration;
public function __construct(ThemeConfiguration $configuration)
{
$this->configuration = $configuration;
}
/**
* {@inheritdoc}
*/
public function getFunctions()
{
return [
new TwigFunction('theme_config', [$this, 'getThemeConfig']),
];
}
/**
* @param string $name
* @return mixed
*/
public function getThemeConfig(string $name)
{
return $this->configuration->find($name);
}
}

View File

@@ -39,6 +39,7 @@ final class IconExtension extends AbstractExtension
'print' => 'fas fa-print',
'project' => 'fas fa-briefcase',
'repeat' => 'fas fa-redo-alt',
'search' => 'fas fa-search',
'start' => 'fas fa-play-circle',
'start-small' => 'far fa-play-circle',
'stop' => 'fas fa-stop',

View File

@@ -25,9 +25,6 @@ class TitleExtension extends AbstractExtension
*/
protected $configuration;
/**
* @param TranslatorInterface $translator
*/
public function __construct(TranslatorInterface $translator, ThemeConfiguration $configuration)
{
$this->translator = $translator;

90
src/Utils/SearchTerm.php Normal file
View File

@@ -0,0 +1,90 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Utils;
final class SearchTerm
{
/**
* @var string
*/
private $originalTerm;
/**
* @var string
*/
private $term;
/**
* @var string[]
*/
private $fields = [];
public function __construct(string $searchTerm)
{
$this->originalTerm = $searchTerm;
$this->parse($searchTerm);
}
private function parse(string $searchTerm)
{
$terms = explode(' ', $searchTerm);
$fields = [];
$finalTerm = [];
foreach ($terms as $term) {
$tmp = explode(':', $term);
if (count($tmp) === 2) {
$fields[$tmp[0]] = $tmp[1];
} else {
$finalTerm[] = $term;
}
}
$this->term = implode(' ', $finalTerm);
$this->fields = $fields;
}
public function hasSearchField(string $name): bool
{
return array_key_exists($name, $this->fields);
}
public function getSearchField(string $name): ?string
{
if (!$this->hasSearchField($name)) {
return null;
}
return $this->fields[$name];
}
public function getSearchFields(): array
{
return $this->fields;
}
public function getSearchTerm(): ?string
{
return $this->term;
}
public function hasSearchTerm(): bool
{
return !empty($this->term);
}
public function getOriginalSearch(): string
{
return $this->originalTerm;
}
public function __toString()
{
return $this->originalTerm;
}
}