I expected that $item->path will change the current path, as it does for the title.

  function decorate__node__($path, $item) {
    if ($serapi = serapi_get_search()) {
      $item->title = t('Search: ') . $serapi['string'];
      $item->path = 'node';
    }
}

But it doesn't.

CommentFileSizeAuthor
#1 crumbs-926266.patch896 byteskenorb
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

kenorb’s picture

Status: Active » Needs review
FileSize
896 bytes

Note: This patch is to version which has already applied this patch: http://drupal.org/node/919166#comment-3488932

Test code:

  function decorate__node__($path, $item) {
    if ($serapi = serapi_get_search()) {
      $keywords = $serapi['string'];
      $item->title = t('Search: ') . $keywords;
      $item->path = array('doc_search/all', array('query' => array('s' => $keywords), ));
    }
  }

serapi module is needed.

From now $item->path could contain string or array. String for simple path, or array if you need to pass additional $options into link generation.
See: http://api.drupal.org/api/function/url/6

This patch solves followed issue as well:
#920510: Support $_GET parameters

donquixote’s picture

Look.. I really don't want that people can change the path from within the decorate() method.
What exactly is your use case?

kenorb’s picture

When user is referrer from any search engine, populate the previous keywords into breadcrumb.

So instead of standard breadcrumb: Home -> Node Title
It will be e.g.: Home -> Search: $keywords
Where $keywords are from an external source.
And the last item will have internal path like: /?search=$keyword

donquixote’s picture

Hm. Please tell me:
- The visitor clicks a link from a search engine. What is the url? Ideally, choose an example with an interesting (=3 items or more) breadcrumb.
- Now he ends up on a page with "?search=$keyword" ? Is this the result of a redirect? And now we want to display a breadcrumb on this page?
- Again, what is the url of the page we are on, where you want to display a breadcrumb? (i assume the same as above, but want to make sure).
- What is the breadcrumb (paths + titles) as produced by crumbs?
- What is the breadcrumb (paths + titles) that you want instead?

I need this to get a better idea of what you want.

kenorb’s picture

In #1 there is working code example describing everything that I want, because I've tested it already with attached patch and it does work perfectly and I'm happy with it.
I'm using serapi_get_search()
See source: http://drupalcode.org/viewvc/drupal/contributions/modules/serapi/serapi....
This function returns already parsed HTTP_REFERER and in $serapi['string'] you have already parsed keywords.
If you type in external search engine: nice cars, $keywords will have "nice cars".
The easier way will be to commit the patch, or if you have other proposition.
In my opinion it's weird to have two separate methods, one for the title, and the other one for the link.
Title of this breadcrumb:
$item->title = t('Search: ') . $keywords;
Path:
$item->path = array('doc_search/all', array('query' => array('s' => $keywords), ));
Where first element is the path, second is the query passed later into l() in link().

donquixote’s picture

In crumbs plugins, you only ever set the path of the parent, never the path of the item itself. If you want to set a different path, you are supposed to do that from the child item, which is processed before the parent.

The patch above is not going to be part of crumbs.
And I don't think you want to maintain a hacked module for long.

I would like to help you, but I need some example urls and breadcrumbs, as mentioned in #4. You can invent them if you like.

kenorb’s picture

Test code:

  function decorate($path, $item) {
    if ($serapi = serapi_get_search()) {
      $keywords = $serapi['string'];
      $item->title = t('Search: ') . $keywords;
      $item->path = array('doc_search/all', array('query' => array('s' => $keywords), ));
    }
  }

1. SEARCH ENGINE
Example engine user request:
http://www.google.co.uk/search?q=key1%20key2
To test it, in Inspector (Chrome) or Firebug (Firefox) I'm replacing the first link of the result to: http://doc/node/123

2. LANDING PAGE
User clicked the link and the landing page is:

URL: http://doc/node/123
$_SERVER['HTTP_REFERER'] = "http://www.google.co.uk/search?q=key1%20key2"

serapi_get_search() from serapi module parse it into:

array(5) { ["keywords"]=> array(2) { [0]=> string(4) "key1" [1]=> string(4) "key2" } ["string"]=> string(9) "key1 key2" ["url"]=> string(44) "http://www.google.co.uk/search?q=key1%20key2" ["engine"]=> string(6) "google" ["host"]=> string(16) "www.google.co.uk" }

Our $keywords are in ['string'] key.

3. BREADCRUMB
Normally breadcrumb is generated as: _Home_ -> _Node Title_
case A.
When user came from known search engine (in this case $serapi = serapi_get_search() returns the array),
Breadcrumb in our case should be:
_Home_ -> _Search: key1 key2_
Where _Search: key1 key2_ has the link to:
/doc_search/all?s=key1%20key2 (that's: t('Search: ') . $keywords;)
which points to our View which executing the SOLR request.
case B.
When user came from not-known search engine or external source and HTTP_REFERER contain external source,
Breadcrumb in our case should be:
_Home_ -> _More like this_
Where _More like this_ has the link to different search path/View, which handle the request.
...
etc., more similar cases
...
case C.
In any other cases, generate the standard breadcrumb defined by the other rules.
e.g. _Home_ -> _Node Title_
or some other defined hierarchy, etc.

donquixote’s picture

What if the "normal breadcrumb" would be longer than that? How much of the original breadcrumb do you want to keep?

kenorb’s picture

In case of the first 2 cases (A&B) I don't want anything in the middle, no any hierarchy.
Just absolute breadcrumb:
_Home_ -> _Search: key1 key2_
or:
_Home_ -> _More like this_

kenorb’s picture

Yes, I see now the problem.
When the landing page is i.e.: primary/teaching/literacy/progress
My breadcrumb is:
Home -> Search: key1 key2 -> Search: key1 key2 -> Search: key1 key2 -> Search: key1 key2

Maybe some method like breakInBreadcrumb(); similar to skipInBreadcrumb(), which will not continue to call the other methods?
Then decorate() method will execute only once.

donquixote’s picture

Well .. here is what I think:
- I don't like breadcrumbs based on "where do I come from". But that is me.
- I think you should rather do this in the theme layer. Hook into theme_breadcrumb, or hook_preprocess_page, and see what you can do.
- If you really want to do this as a crumbs plugin: Did you try the decorateTrail() method? I'm not sure if it allows to change a path, though..
- If you really want to do it with find() and decorate():
(i) Use find() to set "" as the parent path of "doc_search/all".
(ii) Set "doc_search/all?whatever" as the parent of the current page.
(iii) Use skipInBreadcrumb() on the current page, so it doesn't show up.

kenorb’s picture

  function decorate($path, $item) {
    static $only_once = TRUE;
    if ($only_once && $serapi = serapi_get_search()) {
      $keywords = $serapi['string'];
      $item->title = t('Search: ') . $keywords;
      $item->path = array('doc_search/all', array('query' => array('s' => $keywords), ));
      $only_once = FALSE;
    } else {
      $item->skipInBreadcrumb();
    }

This does work with patch.
$only_once preventing to run it couple of times.
skipInBreadcrumb() ignore all other crumbs in hierarchy.
It's doing absolute hierarchy, that's: _Home_ -> _Search: key1 key2_

I'll try to solve this somehow without hack, but it will make it more complex.

I just wonder why you want to force the code to not change the path of current item in special cases, if you can change the title?
Basically you add new line:
case 'path':
in __set()
Normally you don't need to change the path.
But in my opinion it will be more flexible.

donquixote’s picture

So far I have not seen a relevant use case for changing the path.
In your case, the resulting "breadcrumb" has very little to do with locations or parent-child relationships, so it's not really what this module is made for.

kenorb’s picture

Title: Changing path doesn't work » How to change the path of the crumbs?
Status: Needs review » Active

Sometimes it's virtual hierarchical user location, not exactly location in your menu router system.

More examples which use non-hierarchical ways (which are hierarchical from the user perspective):

1.
Landing page: node page
User has some bookmarked nodes.
In case when he is going to the node from Bookmark place, or the node is already Bookmarked by him, the breadcrumb should be:
_Home_ -> Bookmarked -> _Node Title_
Bookmarked could be linked to Bookmark place, or not linked (depends of the implementation).

2.
Landing page: node page
When the user came from the internal Search page, the breadcrumb should be:
_Home_ -> _Search: key1 key2_ -> _Node Title_
It's non-hierarchical from menu router, but hierarchical for the user. It's simple node page, but in this case user can go back to the results page. Not every user use a Back button, in this case browser will ask to send again the POST data, which could be confused which option to choose, it's easier to do this in the breadcrumb.

3.
Landing page: node page
For admins, breadcrumb can easily help to inform user about some important information. If the node is published or not, or some other info
_Home_ -> _Node Title_ -> Archived
_Home_ -> _Node Title_ -> Unpublished
_Home_ -> _Node Title_ -> Deleted
etc.
You could check it current status by expanding the menu, but it's a user laziness or it could be permission issue. Some of the node pages are so complicated, so it's difficult to find some of the data at the bottom.

Basically you just need to able to change the path, some of the control if specified crumb is link or not, it's bold or not, add some prefixes or suffixes (similar to form_alter).
It's called flexibility, otherwise limitation.
Now I see there are different approach that were mentioned: 'flexibly customized pattern of breadcrumbs'. Some of the things (which should) are not customized from the plugins.

kenorb’s picture

Status: Active » Needs work
donquixote’s picture

_Home_ -> Bookmarked -> _Node Title_
This can easily be done with the current system. End of the breadcrumb is the node. You define a find__node__() and set the parent path to "my-bookmarks/whatever". Then on find__bookmarks__(), you set the title to "Bookmarked" (or get it for free from the menu system), and set the parent to "". Done.

_Home_ -> _Search: key1 key2_ -> _Node Title_
Again, if you want to append the _Node Title_, you just have to define a find__node__() to set the parent to "search/something". The little problem is that crumbs does not handle $_GET parameters well, but this has nothing to do with changing the path of the item that is currently being processed.
If you don't want to have the node itself as the last item, you could hide it with skipInBreadcrumb().

_Home_ -> _Node Title_ -> Archived
_Home_ -> _Node Title_ -> Unpublished
_Home_ -> _Node Title_ -> Deleted
This doesn't seem very logical to me. A reasonable pattern would be:
_Home_ -> _Node Title_ (Archived)
_Home_ -> _Node Title_ (Unpublished)
_Home_ -> _Node Title_ (Deleted)
or
_Home_ -> Archived -> _Node Title_
_Home_ -> Unpublished -> _Node Title_
_Home_ -> Deleted -> _Node Title_
In the latter example, a click on "Archived" would get me a list of all archived posts.
The two alternative patterns can easily be done with existing crumbs.

kenorb’s picture

My solution without hacking the module (breadcrumb_doc module):

function breadcrumb_doc_preprocess_page(&$vars) {
  $trail = crumbs_get_trail();
  $vars['crumbs_trail'] = $trail;
  $breadcrumb = breadcrumb_doc_get_breadcrumb();
  $vars['breadcrumb'] = theme('breadcrumb', $breadcrumb);
  $finder = _crumbs_get_finder();
  $finder->invokePreprocessPage($vars, $trail);
}

function breadcrumb_doc_get_breadcrumb() {
  static $_breadcrumb;
  if (!isset($_breadcrumb)) {
    $trail = crumbs_get_trail();
    $_breadcrumb = breadcrumb_doc_build_breadcrumb($trail);
  }
  return $_breadcrumb;
}

function breadcrumb_doc_build_breadcrumb($trail) {
  $breadcrumb = array();
  foreach ($trail as $path => $item) {
    if (breadcrumb_is_serialized($item->title)) {
      $item = unserialize($item->title);
    }
    $breadcrumb[] = breadcrumb_make_link($item); // print a link or a plain text
  }
  return $breadcrumb;
}

function breadcrumb_is_serialized($string) {
  return $string[0] == 'O' && $string[1] == ':';
}

function breadcrumb_make_link($options = stdObject) {
    $path = is_array($options->path) ? $options->path[0] : $options->path;
    $args = is_array($options->path) ? $options->path[1] : array();
    return $options->nolink ? $options->title : l($options->title, $path, $args);
}

Then I could define class like this:

  function decorate__doc_search_all($path, $item) {
    if ($path == 'doc_search/all') {
      $options = new stdClass;
      $options->title = t('Search');
      $options->nolink = TRUE;
      $item->title = serialize($options);
    } else {
      $item->skipInBreadcrumb();
    }
  }

The class which change the path and set no link for the last item:

  function decorate($path, $item) {
    static $only_once = TRUE;
    if ($only_once && $serapi = serapi_get_search()) {
      $keywords = $serapi['string'];
      $options = new stdClass;
      $options->title = t('Search: ') . $keywords;
      $options->nolink = TRUE;
      $options->path = array('doc_search/all', array('query' => array('s' => $keywords), ));
      $item->title = serialize($options);
      $only_once = FALSE;
    } else if (!$only_once) { // run only when Search crumb is already applied
      $item->skipInBreadcrumb();
    }
  }

This solves as well:
#927340: theme callback, prefix & suffix for specified crumbs
In breadcrumb_doc_build_breadcrumb() you can do whatever you want with every crumb.
#925382: Method of override breadcrumb completely

If I'll find some time, I'll write some module extension which could change the path and will add more flexibility.

donquixote’s picture

Status: Needs work » Closed (works as designed)

I did totally not think about invokePreprocessPage() !!
Now you can get what you want much cheaper :)
The purpose of ->preprocessPage() method was to allow breadcrumb customization within hook_preprocess_page, without having to worry about module weight.

So:

<?php
class mymodule_class_CrumbsParentFinder {
  function preprocessPage(&$vars, $trail) {
    // $vars['breadcrumb'] does already contain the breadcrumb built by crumbs.
    $breadcrumb = mymodule_build_breadcrumb($trail);
    $vars['breadcrumb'] = theme('breadcrumb', $breadcrumb);
  }
  
  function find...() {..}

  function decorate..() {..}
}

function mymodule_build_breadcrumb($trail) {..}
?>
donquixote’s picture

We still need a way to hide some specific information in the crumbs items, in a non-hackish way.

kenorb’s picture

Great stuff, ->preprocessPage() does work. Now I don't need separate hook_preprocess_page and get_breadcrumb functions.
Thank you.

kenorb’s picture