Search for files

Another problem I see here is your use of $page in the controller, because like that you only search in the current page, which in this case would be the search page that is likely not to contain any files?

Thank you you’re right…
silly mistake

I managed to display pictures through tag search that’s great.
Will look if I can combine it with search into text as well.

Thanks!

Hi,

I’m trying to search for files by looking into the text file attached to it.
I try to look through all the fields of the files.
I’ve been trying different thing and it’s not working.

//$results = $pages->files()->search($query);
would be nice but it is not working.

I managed to make this solution work, but it filters only the tag field:

<?php return function($site, $pages, $page) { $query = get('q'); $results = $pages->files()->filterBy('tags', '*=', $query); return array( 'query' => $query, 'results' => $results, ); }; Any ideas ? thank you for your help.

There is no search method for files, but you could create a custom files method.

An alternative would be to stick with filtering, but instead of using the filterBy method, use the filter() method.

Ok thank you, I will try to do a method by myself.
If anyone would have a start point for this it would be great (still beginner in php).
Thank you

You could use the Kirby search method for pages (/kirby/core/pages.php) as a starting point and adapt that to searching the file meta data instead. But as I said above, if you don’t need to many options, a filter with a callback will do as well.

Ok so in my case it could be something like this?
It doesn’t work but seems logic to me…
The thing is that I would like to look at several fields.
Thanks again for your help.

<?php

return function($site, $pages, $page) {

	$query   = get('q');
	$files = $pages->files()->filter(function($file) {
	  return $file->caption() == '$query';
	});
	$results = $pages->files()->search($query);

  return array(
    'query'   => $query,
    'results' => $results,
  );
};

Not quite. As I said above, there is no search function for files. And if you use a variable, you may not put it between quotes, otherwise it’s interpreted as a string.

<?php

return function($site, $pages, $page) {

	$query   = get('q');
	$results = $pages->files()->filter(function($file) {
	  return $file->caption() == $query;
	});

  return array(
    'query'   => $query,
    'results' => $results,
  );
}

But for a simple filter like that, you don’t need the callback.

But if you use the callback like above, you can implement more complicated logic, like loop through all fields to check if they contain the search value etc. or look for the search string in more than one field, e.g.

<?php

return function($site, $pages, $page) {

	$query   = get('q');
	$results = $pages->files()->filter(function($file) {
	  return $file->caption() == $query || in_array($query, $file->tags()->split(',');
	});

  return array(
    'query'   => $query,
    'results' => $results,
  );
}

Or maybe you want to check if the search term is a substring of the caption:

<?php

return function($site, $pages, $page) {

	$query   = get('q');
	$results = $pages->files()->filter(function($file) {
	  return str_pos($file->caption(), $query) || in_array($query, $file->tags()->split(',');
	});

  return array(
    'query'   => $query,
    'results' => $results,
  );
}
1 Like

Thank you for the help.

Yes, this is exactly what i try to achieve.

So If I get it right:

$results = $pages->files()->filter(function($file) {
return $file->caption() == $query || in_array($query, $file->tags()->split(‘,’);
});
This will look into the field named ‘caption’ of a file and check if it contains the query(‘q’) OR it will check the other field named ‘tags’. So it return a result if the search value is contained in one of these two fields.
I’m trying to make it work, kirby debug mode says there is a syntax error, unexpected ‘;’

Thank you very much for your time and help, learning a lot here.

Well, one of my favorites is leaving out parentheses… :wink:

$results = $pages->files()->filter(function($file) use($query){
return $file->caption() == $query || in_array($query, $file->tags()->split(','));
});

Edited.

Looking great!
Thank you very much for your help!
Happy to learned the right way!

I tried to implement a custom files methods only to realize that files methods only work with $page->files() files objects, while $pages->files() returns a collection object. However, you could use the following function, which is a simple port of the pages` search function:

function search($files, $query, $params = []) {

  if(is_string($params)) {
    $params = array('fields' => str::split($params, '|'));
  }

  $defaults = array(
    'minlength' => 2,
    'fields'    => array(),
    'words'     => false,
    'score'     => array()
  );

  $options     = array_merge($defaults, $params);
  $collection  = clone $files;
  $searchwords = preg_replace('/(\s)/u',',', $query);
  $searchwords = str::split($searchwords, ',', $options['minlength']);

  if(!empty($options['stopwords'])) {
    $searchwords = array_diff($searchwords, $options['stopwords']);
  }

  if(empty($searchwords)) return $collection->limit(0);

  $searchwords = array_map(function($value) use($options) {
    return $options['words'] ? '\b' . preg_quote($value) . '\b' : preg_quote($value);
  }, $searchwords);

  $preg    = '!(' . implode('|', $searchwords) . ')!i';
  $results = $collection->filter(function($file) use($query, $searchwords, $preg, $options) {

    $data = $file->meta()->toArray();
    $keys = array_keys($data);

    if(!empty($options['fields'])) {
      $keys = array_intersect($keys, $options['fields']);
    }

    $file->searchHits  = 0;
    $file->searchScore = 0;

    foreach($keys as $key) {

      $score = a::get($options['score'], $key, 1);

      // check for a match
      if($matches = preg_match_all($preg, $data[$key], $r)) {

        $file->searchHits  += $matches;
        $file->searchScore += $matches * $score;

        // check for full matches
        if($matches = preg_match_all('!' . preg_quote($query) . '!i', $data[$key], $r)) {
          $file->searchScore += $matches * $score;
        }

      }

    }

    return $file->searchHits > 0 ? true : false;

  });

  $results = $results->sortBy('searchScore', SORT_DESC);

  return $results;

}

Usage:

$query = get('q');
$files = $pages->files();
$results = search($files, $query);

tnx in your help…
$results = $page->files()->search($query);

This looks great, I’m going to test it. A big Thank You texnixe!

The function search is used as a plugin right?

Thanks

Yes, just put it in a file in your plugins folder (e.g. functions.php or methods.php, whatever you want to call it), then it is accessible from anywhere.

Let me know if you get the expected results. You can use the parameters just like in the pages search method, i.e. limit the fields to search in etc.

Texnixe, the result is really great.
The search goes through the text files and get pictures as results.
Thank you very much for your help.

I’m trying now to filter the results.
Exemple: if you make a search with the keywords :“blue” and “round”, it displays only the files having the two keywords into the text file.

Do you have an idea about how can I get such a result?
Thanks

With the current function, that is not possible, unless you take apart the query string and then search for the first word and then search for the next in the results of the first, which is probably not very performant. Otherwise you would have to rewrite the function.

But what exactly is your use case? Maybe there is a better way to achieve what you want to do, maybe by filtering stuff.

I setting up a search bar, able to look through files and display results based on request.
So it goes through the text files of the files and look for keywords (you made this and it works great).
And ultimately when the user put two keywords into the search bar, let say “blue” and “big”, the result displayed is all the files containing the two keywords. It does not display the files containing only blue or big as keywords.
Only the files who have both keywords into their text files.

But maybe I should try to generate a JSON from the text files and look into with JS in order to display the result.

Thanks again for the conversation and your help!

That’s an option. But you could also adapt the PHP search function above in such a way that it does not loop through single fields but uses all text fields as a single unit, and change the regex to not use “OR” but “AND”.

Ok, could you explain me a bit how to do that?
I mean to loop through all the fields as a single unit?
(sorry to ask so much still learning php).

thank you