Jump to content
evan

sorting by page title, with some complexity

Recommended Posts

Hi,

Is there an easy way to add rules to the "sort" selector, or otherwise manipulate search results directly with PHP?  I'm trying to sort results by Page title, but I want to ignore any initial "the", "an", or "a."

I know I can export the results into an array, manipulate them, and then display them, but that would disrupt pagination, and seems clunky. Surely there's an elegant solution?

Thanks!

-evan

Share this post


Link to post
Share on other sites

Hi evan,

maybe this does work?

$pages->find('!title^=the |an |a , sort=title');

Just an idea. There are some limits on the length of words, i'm not an expert here.

If it doesn't work, I'd go with the array.

Share this post


Link to post
Share on other sites

Hi evan,

maybe this does work?

$pages->find('!title^=the |an |a , sort=title');

Just an idea. There are some limits on the length of words, i'm not an expert here.

If it doesn't work, I'd go with the array.

Not really. This excludes pages such as "their", "another", "about"

Share this post


Link to post
Share on other sites

Maybe: $pages->find('!title^="the "|"an "|"a ", sort=title');   ?

Share this post


Link to post
Share on other sites

Not really. This excludes pages such as "their", "another", "about"

Tested? I added a space after the keywords in my selector for this cases

Share this post


Link to post
Share on other sites

Just to clarify, I'm not looking to exclude Pages that begin with the/a/an, but rather alphabetize them by the second word if they do start with any of those.  For example, given these Page titles:

Aguirre, the Wrath Of God
The Wild Blue Yonder
Cave of Forgotten Dreams
Stroszek
Lessons of Darkness
A Heart Of Glass

It should produce:

Aguirre, the Wrath Of God
Cave of Forgotten Dreams
A Heart Of Glass
Lessons of Darkness
Stroszek
The Wild Blue Yonder

What would be amazing if there was an optional field in find() that would let you call up a PHP function, in which you could manipulate the results.   Unless, of course, you can do something like that already.

-evan

Share this post


Link to post
Share on other sites

Add a text field to add an alternative sort title to sort by it instead. No other way if you still want to keep it easy with pager.

Share this post


Link to post
Share on other sites

Tested? I added a space after the keywords in my selector for this cases

Yes, tested. Should have said that ;)

Share this post


Link to post
Share on other sites

Add a text field to add an alternative sort title to sort by it instead. No other way if you still want to keep it easy with pager.

Yeah, I was thinking that.  I guess that'll have to do.

-evan

Share this post


Link to post
Share on other sites

what about a simple string replace on the search results (get rid of leading the, an, a)..

$sort_title = preg_replace("/^(the¦a¦an)\b\ */i", "", "Some title");

Share this post


Link to post
Share on other sites

what about a simple string replace on the search results (get rid of leading the, an, a)..

$sort_title = preg_replace("/^(the¦a¦an)\b\ */i", "", "Some title");

That'll work, but as soma said, I'll have to store that value in the Page in order for it to be alphabetized as I want it to.  Thanks!

-evan

Share this post


Link to post
Share on other sites

Maybe: $pages->find('!title^="the "|"an "|"a ", sort=title');   ?

Nope. Don't work. Tested.

Share this post


Link to post
Share on other sites

Maybe you can get InputfieldPageName module to the site/modules/ folder and change the JS name behaviour. 

( making the name fields javascript remove the / an / a / )

 
$title = "The tralalala";
$title = trim(preg_replace("/^(the¦a¦an)\b\ */i", "", $title));
$iLookLikeName = $sanitizer->pageName($title);
$pages->find("name=$iLookLikeName, sort=name");
  • Like 1

Share this post


Link to post
Share on other sites

I ended up going with soma's suggestion, and just storing the alternate title as field sort_title in the Page.

Here's the function I made, triggered by the hourly LazyCron:

function createSortTitles(HookEvent $e) {
	$items = wire('pages')->find('template=item');
	foreach ($items as $item) {
		if ($item->sort_title=="") {
		$item->setOutputFormatting(false);
		$new_title = (preg_match( "/^(the|a|an)\s/i", $item->title)) ?
			substr(strstr($item->title," "), 1) : $item->title;
		$item->sort_title = $new_title;
		$item->save();
		}
	}
}

For some reason I couldn't get the selector sort_title='' to work correctly -- it would only return a portion of the Pages.  Hence the conditional instead.

-evan

  • Like 1

Share this post


Link to post
Share on other sites

With just a little more work you could put that function into a module that runs on page save which would then be ideal for your needs :)

For example, something like this:

<?php
class SortPageTitles extends WireData implements Module {

    /**
     * getModuleInfo is a module required by all modules to tell ProcessWire about them
     *
     * @return array
     *
     */
    public static function getModuleInfo() {

        return array(

            // The module's title, typically a little more descriptive than the class name
            'title' => 'Sort Page Titles',

            // version: major, minor, revision, i.e. 100 = 1.0.0
            'version' => 100,

            // summary is brief description of what this module is
            'summary' => 'Sorts pages by title by second word if first word is "a, the, an" etc.',
            
            // Optional URL to more information about the module
            'href' => 'http://processwire.com/talk/topic/3306-sorting-by-page-title-with-some-complexity/',

            // singular=true: indicates that only one instance of the module is allowed.
            // This is usually what you want for modules that attach hooks.
            'singular' => true,

            // autoload=true: indicates the module should be started with ProcessWire.
            // This is necessary for any modules that attach runtime hooks, otherwise those
            // hooks won't get attached unless some other code calls the module on it's own.
            // Note that autoload modules are almost always also 'singular' (seen above).
            'autoload' => true,
            );
    }

    /**
     * Initialize the module
     *
     * ProcessWire calls this when the module is loaded. For 'autoload' modules, this will be called
     * when ProcessWire's API is ready. As a result, this is a good place to attach hooks.
     *
     */
    public function init() {

        // add a hook after the $pages->save, to sort certain pages if necessary
        $this->pages->addHookAfter('save', $this, 'sorttitles');
    }

    /**
     * Hooks into the pages->save method and sorts pages with a certain template based on certain criteria
     *
     */
    public function sorttitles($event) {
        $page = $event->arguments[0];
        
        // Only run if the page we just saved has the "item" template
        if ($page->template == 'item') {
            $items = wire('pages')->find('template=item');
            foreach ($items as $item) {
                if ($item->sort_title=="") {
                $item->setOutputFormatting(false);
                $new_title = (preg_match( "/^(the|a|an)\s/i", $item->title)) ?
                    substr(strstr($item->title," "), 1) : $item->title;
                $item->sort_title = $new_title;
                $item->save();
                }
            }
        }
    }
}

I've not tested it, but it's based on the helloworld module, so if you download it (attached) and put it in your /site/modules folder it should work for you, and hopefully be a good example of how easy it is to write modules too :)

SortPageTitles.module

  • Like 3

Share this post


Link to post
Share on other sites

Hey, nice!  I did notice that there was a page->save() hook after the fact, maybe I'll try that out.  It was just quicker for me to do LazyCron, as I'd done it before.  But it's always good to know a little more about PW!

-evan

Share this post


Link to post
Share on other sites

No probs. On a few sites I've ended up creating a little site-specific module like this to check for various templates and do certain things after page save among other things and they can be really useful.

Share this post


Link to post
Share on other sites

Whoa. What perfect timing Pete - just came here looking for this exact same thing. Should definitely be submitted to Modules.

Share this post


Link to post
Share on other sites

Well it's sort of specific to the template/fields evan has on his site, so no point in submitting it really unless you're reproducing his exact same structure.

All it is is his function inside the default helloworld module that comes with every PW install (check inside site/modules directory, and read more here: http://processwire.com/api/modules/ ). You'll be up and running with your own little helper modules in no time :)

Share this post


Link to post
Share on other sites

... and end up in endless loop land. ;-)

Better just save the field only $item->save('sort_title') so the hook doesnt get called again and again and...

  • Like 2

Share this post


Link to post
Share on other sites

... and end up in endless loop land. ;-)

Better just save the field only $item->save('sort_title') so the hook doesnt get called again and again and...

Ah yes, Soma is of course correct that since it runs on page save and saves the page at the end it will go on FOREVER. Like I said, it wasn't tested :P

Changing $item->save to $item->save('sort_title') will of course work like he says.

Share this post


Link to post
Share on other sites

If you hook onto page->save(), it's no longer necessary to loop over pages->find():

    public function sorttitles($event) {
        $page = $event->arguments[0];
        
        // Only run if the page we just saved has the "item" template
        if ($page->template == 'item') {
                $new_title = (preg_match( "/^(the|a|an)\s/i", $page->title)) ?
                    substr(strstr($page->title," "), 1) : $page->title;
                $page->sort_title = $new_title;
                $page->save('sort_title');
                }
            }
        }
    }
}

-evan

  • Like 1

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By ttttim
      Is there a way to add a (new or existing) page to the top of the asmSelect list? By default they're added at the bottom. 
    • By Leftfield
      Hi,

      First, this is production realestate site, big one. So please, if any chanse to fix this without touching database, it would be great

      I have field "pstatus" as a PageReference,
      In the tree under Status as children:
      sold unavailable paused In the template "property" I have dropdown were I choose: empty (nothing); sold; unavailable or paused. If you do not choose anything, property will have active selling status.

      I need help sorting property template in this order: show all empty and than show all sold.

      1031 is Status, parent of the sold unavailable and paused.
       
      $status = $pages->get('1031')->children('sort=pstatus'); $selector[] = "sort=price, pstatus!=$status"; But I got there all except from status. I need to list "sold" too.

       
    • By j__
      On a site that lists events, I am using a repeater field 'event_time', which contains two Inputfield Time fields 'event_time_start' and 'event_time_end'. Some events take place multiple times, others just once. Here's an example for the data structure:
      event1 title: First Event event_time (1): event_time_start: 08:00 event_time_end: 09:00 event2 title: Second Event event_time (1): event_time_start: 08:00 event_time_end: 09:00 event_time (2): event_time_start: 14:00 event_time_end: 18:00 event3 title: Third Event event_time (1): event_time_start: 07:00 event_time_end: 09:30 First, I'd like to generate a list of all events, sorted by event_time_start, with every repeater item added as an actual event (similar to a SQL JOIN clause). The desired output would be:
      07:00-09:30 Third Event 08:00-09:00 First Event 08:00-09:00 Second Event 14:00-18:00 Second Event A selector like
      $pages->find('template=event,event_time.event_time_start!=,sort=event_time.event_time_start'); would only return each event page once:
      07:00-09:30 Third Event 08:00-09:00 First Event 08:00-09:00 Second Event without
      14:00-18:00 Second Event Is there a possible alteration of the selector to take all different occurrences into account?
      Also I'd like to filter for events taking place in the morning, using a selector like:
      $pages->find('template=event,event_time.event_time_start>=06:00,event_time.event_time_start<=12:00,sort=event_time.event_time_start'); However, this would only return each 'event' page, which then contains also the afternoon version of event2.
      While I have the impression these are rather simple tasks, I struggle finding a selector-based solution to it. In this example on opening times are some similarities, but it does not deal with multiple occurrences. I'd appreciate your ideas to it.
    • By Michael Steinmann
      Hello everyone,
      i guess my php knowledge is not good enough to find a solution for the following problem:
      I'm building a website where i have projects. Each project has tags like "webdesign, responsive, cms, print, flyer" etc. At the end of a project i want to recommend other projects, which have similar tags. I want to limit the recommendations to three other projects sorted by the hightest amount of same tags.
      This is how far i got:
      I'm using
      <?php $interessantes = $pages->find("tags=$page->tags, id!=$page->id"); ?> to find other projects with the same tags, excluding the page i'm already on.
      Then i'm using three foreach-functions to go through the projects i found, the tags they have and compare them with the tags of the project i'm looking at. If i have two identical tags, i count up a variable.
      <?php // Projects i have found foreach($interessantes as $interessant): $i = 0; // Tags of the projects i have found foreach($interessant->tags as $tag): // Tags of the project i'm looking at foreach($page->tags as $tagreferenz): if($tag == $tagreferenz): $i++; endif; endforeach; endforeach; endforeach; ?> You can see what it looks like in the screenshot with a bit of HTML. I marked the tags of the current project green and the identical tags of other projects red with the amount of hits below. So in this example i would want to have three projects with three similar tags and get rid of those with only two and one.

      I guess i have to put my pages into an array (with the number of hits?), sort them and echo them with "limit=3"? Unfortunately i have no idea how to do this. You probably have an even better/shorter solution. Links to other topics are welcome to and i'll try to get my head around it.
      If you need further explanation, i'll try my best.
      Best regards
    • By Cengiz Deniz
      Sorting with different selectors, pagination works good When I logged in as Admin.
      But  for visitors it does not work. I think it is cache problem.
      Any idea ?
      Thank You.
       
      https://cdeniz.com/kutuphane/kisiler/?sort=dogumtarihi
×
×
  • Create New...