Jump to content

Simple tagging for dummy (me)


alan
 Share

Recommended Posts

I am trying to implement a KISS (Keep It Simple Stupid) tagging system, one depth, i.e tags not hierarchical categories.

Each page would display any tag that was added to it as a link; the link goes to a page where all other pages sharing that tag are listed, quite basic.

In my attempts to do this I have been reading up, posting, getting help (thank you patient helpers), trying, failing and have slightly run aground. I know instinctively from the little I've done with PW that this is going to be easy, when I can see 'how'. These are the three threads I've been looking at, but perhaps each was not quite what I am trying to do and that, plus my PHP weakness, stopped me succeeding.

These are my assumptions as to what I would do:

  1. have an AsmSelect field called "tags" on each page in the Admin so content authors may select or add tags per page
  2. have a page below Home called "tags" which will have a child page per tag
  3. don't have a .php file for the "tags" page (is that right?)
  4. don't have a .php file for each children page of the "tags" page
  5. use a file-less template for the "tags" page and children pages and that template to include only the title field (is that right?)
  6. use my main/single .php template file for all pages and detect if it is being used to display tag-related content and change its outs put accordingly.

If you're able to point me to a thread I may have missed containing code samples so someone like me can get going, or post a few snippets to show me the basic code and correct any of my assumptions that look wrong I would be very grateful. Cheers, -Alan

Link to comment
Share on other sites

You're fine up until 4.

4. Create a tags template and make sure your tag pages use that template - you can link tags to it from your other pages that have the tagging field by doing something like:

foreach ($page->tags as $tag) {
echo "<a href='{$tag->url}'>{$tag->title}</a> ";
}

The tags will be separated by a space and link to something like /tags/tagtitle/

Then in the tags template you can do:

$tagged_pages = $pages->find("tag=$page"); // assuming "tag" is your tag field name. $page is the current page object, so this may seem odd but when echo'd or used as part of a selector this actually outputs  the page ID, so this is the same as $page->id but shorter, which is nice
foreach ($tagged_pages as $tagged) {
 echo "<p><a href='{$tagged->url}'>{$tagged->title}</a></p>";
}

Then I think you can skip your remaining steps.

Hope that helps.

In terms of other examples, I posted some earlier that do similar things in a different scenario: http://processwire.c...ndpost__p__9564 That set of examples doesn't really tell you what to do with templates, but does hopefully give you a few more ideas about how to find related data.

Link to comment
Share on other sites

Brilliant :D works perfectly, thanks Pete! And thanks for the link to the other post, gonna go read.

I have one problem that is likely down to me creating/deleting pages, templates, fields called tag and tags all afternoon — each time I go to edit any page I am getting:

Invalid value sent to Page::setTemplate

Do you think I would be well advised restoring from a db backup? Or should I try to work out a fix in case this happens again or is of interest?

Correction, this happens for some pages, just working out what the common link is...

Update: Backed up and saved 'broken' db in case of help to you Pete/Ryan etc and then restored and re-created the items I needed and all works perfectly, thanks again!

Edited by alan
  • Like 1
Link to comment
Share on other sites

Thanks Alan - I'm limited to mobile today but will do more research on that error when I get back to the computer this weekend. Thanks for saving that DB, that may be helpful to determine what happened.

Link to comment
Share on other sites

Welcome Ryan, happy if it helps at all. And I had been adding/removing/adding/removing/adding/removing fields, templates, pages called "tags" in my (feeble) search to work out how to do what in the end Pete showed me was wonderfully simple (as I knew it would be! :)) So I have zero loss in confidence from that error condition.

  • Like 1
Link to comment
Share on other sites

I've made real progress (big smiles here :D) but have run into a silly problem, any pointers anyone please?

Background: Taking Pete's most helpful advice as a starting point, I wanted to make the focus of the tagging not the tag names but the titles of 'pages-like-this-one'. E.g. My Home page is tagged "butter" and also my Food page is tagged "butter", I wanted on my Home page to say "Other related pages: Food" and my Food page to say "Other related pages: Home".

I should note in case it's relevant all pages are using one template for now (I have dropped the 'tags' template).

<?php
// Link to title of each page that shares one or more tags with this one
// ---

// find this pages tags

$this_pages_tags = $page->tags;

// each tag, find other pages that match the tag
// TODO limit the find?

foreach ($this_pages_tags as $tag) {
 $other_pages = $pages->find("tags=$tag")->not($page);
}

// output title of

foreach ($other_pages as $matched) {
 echo "<a href='{$matched->url}'>{$matched->title}</a> ";
}

?>

No problem: Home and Food pages share the "butter" tag and each shows a link to the other as expected. If I also add the "marmite" tag to the Contact page then as expected the other pages do not show a link to the Contact page (as they don't share a tag).

Problem: When I add the "marmite" tag to the Home page it starts showing a link to the Contact page (good as they now share a tag) but no longer shows a link to the Food page even though they also still share the "butter" tag.

It's feels sort of like I am building a one-depth list when it needs to be N-depth, but I can't work out the code to address this. Any help/comments most appreciated.

Link to comment
Share on other sites

Try this:

<?php
// Link to title of each page that shares one or more tags with this one
// ---

// find this pages tags

//$this_pages_tags = $page->tags; // Not really needed as we can just use $page->tags as below

// each tag, find other pages that match the tag
// TODO limit the find?

$other_pages = $pages->find("tags=$page->tags"); // $page->tags when used in a selector will be something like 123|32|667 (page IDs separated by a pipe character, which means OR)

// Following is not needed as we grabbed them all via the above find()
/*foreach ($this_pages_tags as $tag) {
 $other_pages = $pages->find("tags=$tag")->not($page);
}*/

// output title of

foreach ($other_pages as $matched) {
 echo "<a href='{$matched->url}'>{$matched->title}</a> ";
}

?>

It'll display all pages linked by tags this way, but won't give you access (easily anyway) to what they matched on. That would be harder to accomplish, but it would maybe allow you to prioritise and have pages with more related tags appear higher up the list. I'd question how useful it would be beyond just being cool though :)

Link to comment
Share on other sites

This KISS tagging is working really well, with just one feature that I can't crack; a summary view where each tag is shown and under it the pages that have that tag applied. I've skinned my PHP/PW-knees trying to do this but failed :( The following code is nearly what I want, producing this output which shows each page and under it the tags that have been applied (the opposite of what I want):

echo "<h1>Tags pages</h1>";
$tag_pages = $pages->get("/tags/"); // TODO can this line and the one below be combined?
$all_tags_pages = $tag_pages->children; // TODO can this line and the one above be combined?
$tagged_pages = $pages->find("tags.count>0, sort=title");
echo "<dl>";
 foreach ($tagged_pages as $matched) {
  foreach ($matched->tags as $tag_pages) {
echo "<dt>{$tag_pages->title}</dt>";
  }
  echo "<dd><a href='{$matched->url}'>{$matched->title}</a></dd>";
 }
echo "</dl>";

In trying to solve this I've been working on the assumption I need three loops/nested loops/tests

# a list of the tag pages (the pages used to represent the tag names)

# the list of tagged pages (the pages that have one tag or more applied)

# for each tag page, the tagged pages that include it's tag -> output the page title

I'm still working on solving this as it would round off a great little soft sub-indexing strategy, but if anyone can see the answer and can drop me a clue I would be most grateful, thanks in advance!

Link to comment
Share on other sites

Still didn't read everything, but I'll answer to the TODOs. They can be conbined like this:

$all_tags_pages = $pages->get("/tags/")->children; // they can 

edit: i was trying to answer your main question, but i won't have time right now and i don't understand how you have the pages organized on the tree.

just two quick things:

1. Right now you are printing two <dt> for each <dd>. You will have to bring the <dd> out of the loop, and organize the things inside on a new list inside it. (sorry folks, i gave a wrong information, you can indeed have multiple terms per definition as well as the opposite)

2. you are not using the variable all_tag_pages anywhere in your code

sorry, for not being able to help more for now. i'm sure someone will jump in soon

Link to comment
Share on other sites

Try this:

echo "<h1>Tags pages</h1>";
foreach ($pages->find("tags.count>0, sort=title") as $tag_pages) {
echo "<h3>{$tag_pages->title}</h3>";
echo "<dl>";
foreach ($tag_pages->tags as $tag) {
	echo "<dt>{$tag->title}</dt>";
}
echo "</dl>";
}

Not tested it but it should work.

Basically, it should be iterating through the pages that have tags, and then the tags on those pages.

This might seem like it's getting repetitive in that 10 pages might have the same tag and it feels like it might be fetching those tags 10 times, but if I'm right (and ryan or someone else more knowledgeable than me will have to confirm) those pages stay in memory until the end of the script.

Link to comment
Share on other sites

Oo! Thanks @diogo and @Pete.

@diogo thanks for concatenating those lines for me, I was sure they were wrong/inefficient as I had them and thanks the re HTML placement.

@Pete thanks a lot for the code to try, I'll go do that know, I must admit at the back of my mind I was wondering if when it's working it might be a bit intensive and/or really need care as regards caching, and interested in your point about arrays(?) staying in memory while executing. When I get it working completely I will post the code and see if anyone thinks it's gonna be 'bad' for the server and/or need particular care re caching.

Link to comment
Share on other sites

@Pete. IT WORKS! I learnt some stuff I am sure, but I was loop-de-looping for a long time trying to work that out. Thanks SO much, I'll go read through your code now and try to learn! Cheers! :D

Link to comment
Share on other sites

Good news for my little learning curve; thanks to the help I've received from this thread I hit another problem and solved it myself \o/

I realized I've been committing the cardinal sin of trying to solve a problem that was not fully fully defined. @Pete's last code taught me some stuff so I then went on and produced this code:

$tags = $pages->get("/tags/")->children->find("sort=title");
$tagged_pages = $pages->find("tags.count>0, sort=title");
echo "<dl>";
foreach ($tags as $tag) {
echo "<dt>{$tag->title}</dt>";
foreach ($tagged_pages as $tagged_page) {
 foreach ($tagged_page->tags as $tag_to_test) {
  if($tag_to_test->title == $tag->title)
   echo "<dd><a href='{$tagged_page->url}'>{$tagged_page->title}</a></dd>";
 }
}
}
echo "</dl>";

which now produces this output; which was really what I needed.

One improvement I am going to add is to check the URL to see if we're on /tags/ in which case the above will be used or /tags/a-tag in which case I will only show the pages related to a-tag. So — thanks everyone who has helped me, when this is fully finished I'll write it up for any poor souls who are are PHP-weak as I am ;) Cheers!

  • Like 1
Link to comment
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
 Share

×
×
  • Create New...