From 1395dae6e49fcdca4502ce541951b6c2e2e93604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Wed, 2 Oct 2019 18:39:40 +0200 Subject: [PATCH] Cache list of builtin ressource attachments. This should be a bit faster than searching every time. --- src/Form/AttachmentFormType.php | 5 +- src/Services/BuiltinAttachmentsFinder.php | 111 +++++++++++++----- .../BuiltinAttachmentsFinderTest.php | 81 +++++++++++++ 3 files changed, 164 insertions(+), 33 deletions(-) create mode 100644 tests/Services/Attachments/BuiltinAttachmentsFinderTest.php diff --git a/src/Form/AttachmentFormType.php b/src/Form/AttachmentFormType.php index befde37d..4eb0b0f8 100644 --- a/src/Form/AttachmentFormType.php +++ b/src/Form/AttachmentFormType.php @@ -83,7 +83,10 @@ class AttachmentFormType extends AbstractType $builder->add('url', TextType::class, [ 'label' => $this->trans->trans('attachment.edit.url'), 'required' => false, - 'attr' => ['data-autocomplete' => $this->urlGenerator->generate('typeahead_builtInRessources', ['query' => 'QUERY']) + 'attr' => [ + 'data-autocomplete' => $this->urlGenerator->generate('typeahead_builtInRessources', ['query' => 'QUERY']), + //Disable browser autocomplete + 'autocomplete' => 'off' ], 'constraints' => [ $options['allow_builtins'] ? new UrlOrBuiltin() : new Url() diff --git a/src/Services/BuiltinAttachmentsFinder.php b/src/Services/BuiltinAttachmentsFinder.php index ff298a8b..9bd440c0 100644 --- a/src/Services/BuiltinAttachmentsFinder.php +++ b/src/Services/BuiltinAttachmentsFinder.php @@ -36,6 +36,7 @@ use App\Services\Attachments\AttachmentPathResolver; use Symfony\Component\Finder\Finder; use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Contracts\Cache\CacheInterface; /** * This service is used to find builtin attachment ressources @@ -44,64 +45,110 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class BuiltinAttachmentsFinder { protected $pathResolver; + protected $cache; - public function __construct(KernelInterface $kernel, AttachmentPathResolver $pathResolver) + public function __construct(CacheInterface $cache, AttachmentPathResolver $pathResolver) { $this->pathResolver = $pathResolver; + $this->cache = $cache; } protected function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'limit' => 15, //Given only 15 entries - 'filename_filter' => '', //Filter the filenames. For example *.jpg to only get jpegs. Can also be an array - 'placeholders' => Attachment::BUILTIN_PLACEHOLDER, //By default use all builtin ressources + //'allowed_extensions' => [], //Filter the filenames. For example ['jpg', 'jpeg'] to only get jpegs. + //'placeholders' => Attachment::BUILTIN_PLACEHOLDER, //By default use all builtin ressources, + 'empty_returns_all' => false //Return the whole list of ressources when empty keyword is given ]); } - public function find(string $keyword, array $options = []) : array + /** + * Returns a list of all builtin ressources. + * The array is a list of the relative filenames using the %PLACEHOLDERS%. + * The list contains the files from all configured valid ressoureces. + * @return array The list of the ressources, or an empty array if an error happened. + */ + public function getListOfRessources() : array { - $finder = new Finder(); + try { + return $this->cache->get('attachment_builtin_ressources', function () { + $results = []; + + $finder = new Finder(); + //We search only files + $finder->files(); + //Add the folder for each placeholder + foreach (Attachment::BUILTIN_PLACEHOLDER as $placeholder) { + $tmp = $this->pathResolver->placeholderToRealPath($placeholder); + //Ignore invalid/deactivated placeholders: + if ($tmp !== null) { + $finder->in($tmp); + } + } + + foreach ($finder as $file) { + $results[] = $this->pathResolver->realPathToPlaceholder($file->getPathname()); + } + + //Sort results ascending + sort($results); + + return $results; + }); + } catch (\Psr\Cache\InvalidArgumentException $ex) { + return []; + } + } + + /** + * Find all ressources which are matching the given keyword and the specified options + * @param string $keyword The keyword you want to search for. + * @param array $options Here you can specify some options (see configureOptions for list of options) + * @param array|null $base_list The list from which should be used as base for filtering. + * @return array The list of the results matching the specified keyword and options + */ + public function find(string $keyword, array $options = [], ?array $base_list = []) : array + { + if (empty($base_list)) { + $base_list = $this->getListOfRessources(); + } $resolver = new OptionsResolver(); $this->configureOptions($resolver); $options = $resolver->resolve($options); + + + /* if (empty($options['placeholders'])) { return []; - } + } */ - //We search only files - $finder->files(); - //Add the folder for each placeholder - foreach ($options['placeholders'] as $placeholder) { - $tmp = $this->pathResolver->placeholderToRealPath($placeholder); - //Ignore invalid/deactivated placeholders: - if ($tmp !== null) { - $finder->in($tmp); + if ($keyword === '') { + if ($options['empty_returns_all']) { + $keyword = '.*'; + } else { + return []; } + } else { + //Quote all values in the keyword (user is not allowed to use regex characters) + $keyword = preg_quote($keyword, '/'); } - //Apply filter if needed - if (!empty($options['filename_filter'])) { - $finder->name($options['filename_filter']); - } - - $finder->path($keyword); - - $arr = []; - - $limit = $options['limit']; - - foreach ($finder as $file) { - if ($limit <= 0) { - break; + /*TODO: Implement placheolder and extension filter */ + /* if (!empty($options['allowed_extensions'])) { + $keyword .= "\.("; + foreach ($options['allowed_extensions'] as $extension) { + $keyword .= preg_quote($extension, '/') . '|'; } - $arr[] = $this->pathResolver->realPathToPlaceholder($file->getPathname()); - $limit--; - } + $keyword .= ')$'; + } */ - return $arr; + //Ignore case + $regex = '/.*' . $keyword . '.*/i'; + + return preg_grep($regex, $base_list); } } \ No newline at end of file diff --git a/tests/Services/Attachments/BuiltinAttachmentsFinderTest.php b/tests/Services/Attachments/BuiltinAttachmentsFinderTest.php new file mode 100644 index 00000000..ce26ea60 --- /dev/null +++ b/tests/Services/Attachments/BuiltinAttachmentsFinderTest.php @@ -0,0 +1,81 @@ +get(BuiltinAttachmentsFinder::class); + } + + public function dataProvider() + { + return [ + //No value should return empty array + ['', [], []], + ['', ['empty_returns_all' => true], static::$mock_list], + //Basic search for keyword + ['test', [], ['%FOOTPRINTS%/test/test.jpg', '%FOOTPRINTS%/test/test.png', '%FOOTPRINTS_3D%/test.jpg']], + ['%FOOTPRINTS_3D%', [], ['%FOOTPRINTS_3D%/test.jpg', '%FOOTPRINTS_3D%/hallo.txt']], + ['.txt', [], ['%FOOTPRINTS_3D%/hallo.txt'] ], + //Filter extensions + //['test', ['allowed_extensions' => ['jpeg', 'jpg']], ['%FOOTPRINTS%/test/test.jpg', '%FOOTPRINTS%/123.jpeg', '%FOOTPRINTS_3D%/test.jpg']], + //['test.jpg', ['allowed_extensions' => ['jpeg', 'jpg']], ['%FOOTPRINTS%/test/test.jpg', '%FOOTPRINTS_3D%/test.jpg']] + + ]; + } + + /** + * @dataProvider dataProvider + */ + public function testFind($keyword, $options, $expected) + { + $value = static::$service->find($keyword, $options, static::$mock_list); + //$this->assertEquals($expected, static::$service->find($keyword, $options, static::$mock_list)); + $this->assertEquals([], array_diff($value, $expected), 'Additional'); + $this->assertEquals([], array_diff($expected, $value), 'Missing:'); + } +} \ No newline at end of file