Jump to content

Learn & Launch project


MilenKo
 Share

Recommended Posts

26 minutes ago, Robin S said:

There is no built-in method to do this efficiently in PW

Another way... adapt this SQL query by Ryan:

$table = $fields->get('my_page_field')->getTable();
$query = $database->query("SELECT data FROM $table");
$ids = $query->fetchAll(PDO::FETCH_COLUMN);
$count_values = array_count_values($ids);
// use the pages IDs and counts as needed, for example:
foreach($count_values as $key => $value) {
    $p = $pages->findOne($key);
    echo "<p>{$p->title} (selected in $value pages)</p>";
}

 

Link to comment
Share on other sites

@flydev & @szabesz I have already placed the keywords in place for every page I need them and now am able to select already existing or add a new if needed so now I am aiming at showing some "top" keywords on the main page. And to be clear, by "top" I most probably would select to choose the tags that were used the most. Btw 'reading the morning coffee first' made me laugh ;)

@Robin S thanks for your suggestions. I would give a try to the  Connect Page Fields & Ryans SQL query and see how can I fit in the keywords appearance.

  • Like 1
Link to comment
Share on other sites

  • 2 weeks later...

Hello again. After a while of being away it is time to renew my work and bring it to the end now.

I had some digging in google to search for any good approach that I can use for the tags cloud and I got a module that might work, however in the forum here there was a guess that it would overload the site at one point when 1000-5000 pages are added and some have 5-6 tags. As far as the speed of the load is a concern for me, I am still looking for a best approach to just get the most used 10 tags ordered and that is all. For the moment I have managed to setup the tags as pages and can use existing one or new ones so that is good. I am also needing to link the existing tags to a search result of all pages containing the tag but that should not be a problem and will just work it out.

So if anyone has a working example that does not overkill the system, would be good to share it. I looked into a few blog profiles to check about that, but am still not able to find my answer :) I am not going to give up and while being away I even started working on another personal project of mine (PW of course).

I am trying to modify Ryan's query suggested by @Robin S but am experiencing some difficulties, so should work a bit harder I guess :)

Link to comment
Share on other sites

23 hours ago, MilenKo said:

I am trying to modify Ryan's query suggested by @Robin S but am experiencing some difficulties, so should work a bit harder I gues

What is causing you difficulty exactly?

Another example with a few more comments:

$table = $fields->get('tags')->getTable(); // enter the name of your "tags" Page field
$query = $database->query("SELECT data FROM $table");
$ids = $query->fetchAll(PDO::FETCH_COLUMN);
$count_values = array_count_values($ids); // count the frequency of values in the array
arsort($count_values); // sort highest to lowest
$count_values = array_slice($count_values, 0, 10, true); // we only want the top 10 tags
// use the pages IDs and counts as needed, for example:
foreach($count_values as $key => $value) {
    $p = $pages->get($key);
    // output whatever you want using $p (Page object) and $value (number of times tag is selected)
    echo "<p><a href='/search/?tag={$p->name}'>{$p->title}</a> (selected in $value pages)</p>";
}

And in your search template you could do something like:

$tag = $sanitizer->name($input->get->tag);
if($tag) {
    $results = $pages->find("tags.name=$tag");
    // output your results for pages with this tag
}

 

  • Like 2
Link to comment
Share on other sites

Just a small correction on @Robin S code:

If you're using PW 3.0+ will need to reference the PDO class outside ProcessWire namespace:

$ids = $query->fetchAll(\PDO::FETCH_COLUMN);

Or, put this in the beginning of the file:

<?php namespace ProcessWire;
use \PDO;

 

  • Like 1
Link to comment
Share on other sites

@fbg13 For sure the caching would speed up the page loading, however, in my development, I am not activating this as to avoid some style/coding issues. Once I know the functionality is all sorted out, I will activate the caching and cleanup of the code etc.

Thank you @Robin S and @Sérgio I will give it a try and see how it fits. Once I get a working example, every next project would be easier...

Link to comment
Share on other sites

OK. Testing out the @Robin S code made the tags cloud working on the main page. The only thing that I need to change is the order of the tags where I would need to have the most used ones to appear first. As of now, I am not sure how the tags are sorted but for sure the counter works fine. I need to find a way to sort the results by $value and it would all be good.

Tags cloud.jpg

Link to comment
Share on other sites

14 hours ago, Sérgio said:

If you're using PW 3.0+ will need to reference the PDO class outside ProcessWire namespace

I don't experience any issue with this when the ProcessWire namespace is not declared - the file compiler seems to be smart enough not to wrongly insert \ProcessWire\ before the PDO class. If the ProcessWire namespace is manually declared then you would need the backslash, \PDO::FETCH_COLUMN. I guess it wouldn't hurt to include the backslash in either case.

30 minutes ago, MilenKo said:

I need to find a way to sort the results by $value and it would all be good.

The code was missing an arsort() - I have updated it now.

  • Like 2
Link to comment
Share on other sites

Hello @Robin S

I tried today to implement your suggested modification in the search.php however I got two results none of which is what is needed:

1. if I search for existing tag, I am shown the page with the tag name as title and if I click on it brings me to 404.

2. If I search for non-existing tag, I get twice the message that nothing is found (first time it shows because it is looking for a match in title|body and second time - in the tag).

From what I understood so far, the issue comes with the fact that the page title|body search would also make the tag available as far as it is also a page. I need to separate the two but so far I am unable to figure out how to implement the tag search separately from the title|body one.

I am attaching my original search.php code in case somebody have some time to suggest the fix or at least point me to the right direction.

Spoiler

<?php

/**
 * Search template
 *
 */

$out = '';

if($q = $sanitizer->selectorValue($input->get->q)) {

	// Send our sanitized query 'q' variable to the whitelist where it will be
	// picked up and echoed in the search box by the head.inc file.
	$input->whitelist('q', $q); 

	// Search the title, body and sidebar fields for our query text.
	// Limit the results to 50 pages. 
	// Exclude results that use the 'admin' template. 
	$matches = $pages->find("title|body~=$q, limit=10");  

	$count = count($matches); 

	if($count) {
		
		$found = "Found $count articles containing text <strong class='text-danger'>\"$q\"</strong>";

		foreach($matches as $m) {
			
			$date = date('Y-j-d, G:i', $m->created);
			$relative = wireRelativeTimeStr($m->created);
			$cat_link = $m->rootParent->url;
			$cat_title = $m->rootParent->title;
			
			//Search result header block
			$out .= "<article class='hentry'>" .
					"<header class='entry-header'>" .
					"<i class='fa fa-list-alt fa-2x fa-fw pull-left text-muted'></i>" .
					"<h2 class='entry-title h4'><a href='{$m->url}' rel='bookmark'>{$m->title}</a></h2>" .
					"</header>" .
					"<footer class='entry-footer'>" .
					"<div class='entry-meta text-muted'>" .
					"<span class='date'>" .
					"<i class='fa fa-clock-o fa-fw'></i>" .
					"<time datetime='$date'>$relative</time>" .
					"</span>" .
					"<span class='category'><i class='fa fa-folder-open-o fa-fw'></i> <a href='$cat_link'>$cat_title</a></span>" .
					"</div>" .
					"</footer>" . 
					"</article>";
		}

	} else {
		$found = "There are no records found in the database...";
		$out .= "</article>I am sorry for the inconvenience, however no matter how hard I tried digging deep in my database, there were no matches found matching <strong class='text-danger'>'$q'</strong> as a keyword. Maybe you could try to refine the search terms and try me again? <p><br />Another option would be to write down an <strong class='text-danger'>article, faq or news</strong> on the topic yourself so that the next search comes with a positive result <i class='fa fa-smile-o fa-1'></i></p>";
	}
	} else {
		$found = "I am missing a search term in the search box...";
		$out .= "</article>In order to allow me to search the database and find some useful stuff I need to have some <strong class='text-danger'>search terms</strong>. Otherwise it would be just a pointless waste of mine and your time trying to pull some articles that are not important for your goals!";
}

// Note that we stored our output in $out before printing it because we wanted to execute
// the search before including the header template. This is because the header template 
// displays the current search query in the search box (via the $input->whitelist) and 
// we wanted to make sure we had that setup before including the header template. 

include ('./includes/header.php');
		
include ('./includes/breadcrumbs.php'); ?>
		
		<div id="main" class="site-main clearfix">
			<div class="container">
	
				<div class="content-area">
					<div class="row">

						<div id="content" class="site-content col-md-9">
						
							<header class="archive-header">
								<h1 class="archive-title"><?php echo $title;?></h1>
							</header><!-- .archive-header -->
							
							<blockquote class="archive-description">
								<p><?php echo $found;?></p>
							</blockquote><!-- .archive-description -->
							
							<div class="archive-list archive-article">

								<?php echo $out; ?>

							</div><!-- .archive-list -->

						</div><!-- #content -->

<?php include ('./includes/sidebar.php');?>

					</div>
				</div><!-- .content-area -->
	
			</div>
		</div><!-- #main -->
		
<?php include ('./includes/footer.php');?>

 

Looking at the code further, it is not clear how I would need to organize the query so that I can search for tags and page title|body text using the same query. For the moment anything I search within the search form on the main page has the structure: localhost/search/?q=enzymes

Link to comment
Share on other sites

17 hours ago, MilenKo said:

Looking at the code further, it is not clear how I would need to organize the query so that I can search for tags and page title|body text using the same query.

Do you need to search for both using the same query? The title|body search is coming from user input, the tag search is coming only from links that you are outputting in your tag cloud. So you can treat these as entirely separate selectors:

$tag = $sanitizer->name($input->get->tag);
$q = $sanitizer->selectorValue($input->get->q));
if($tag) {
    $matches = $pages->find("tags.name=$tag, limit=10"); // assuming your tags field is named "tags"
    // output your matches for pages with this tag
} elseif($q) {
    $matches = $pages->find("title|body~=$q, limit=10");
    // output your matches for the search query
}

You don't have to do these in the same template even - you could create a different template and page for listing tag matches if you prefer to separate them like that. In any case you would need to use separate markup for tag results than search results because some of your results output doesn't make sense for a tag search...

// ...

$found = "Found $count articles containing text <strong class='text-danger'>\"$q\"</strong>";

// ...

$found = "I am missing a search term in the search box...";

 

  • Like 2
Link to comment
Share on other sites

@Robin S If I am aiming to extend the search to title|body and tags, would that make sense as the first two are fields containing just a text input where the tags are pages. It would be nice to make the search form to search for a text string within the title, body and tags field however I am not sure how easy that is to achieve because of the fields differences.

I thought your example was to be added to the search form however started understanding your point - to use the query to link the top X tags and link them to the pages only in the tag cloud. In such a scenario, I need to find the relation between the tag cloud template and the results showing in the same structure as Articles. But will see how it goes.

One thing I was thinking also, would it be easier to have the tags as a text line and separate them by coma? Doing so should allow me to have the search easily modified by just changing the $matches to search in title|body|tags. As to show the cloud, it might not be really necessary even though I am sure I could grab the tags of every page again with some modifications to the @Robin S code but that would require to use explode to extract every tag from the text.

 

Link to comment
Share on other sites

3 hours ago, MilenKo said:

It would be nice to make the search form to search for a text string within the title, body and tags field however I am not sure how easy that is to achieve because of the fields differences.

There's no harm in just trying these things. ;)

$matches = $pages->find("title|body|tags~=$q, limit=10");

 

Link to comment
Share on other sites

Ok. I tried to implement your last suggestion as I really liked the approach, however for some reason the IF statement does not work for me. To simplify the things, I created a simple search.php file containing the following code (please note that my tags field is called keywords. Same thing applies for template and page container):

<?php

/**
 * Search template
 *
 */

$out = '';

$keywords = $sanitizer->name($input->get->keywords);
$q = $sanitizer->selectorValue($input->get->q);

if($keywords) {
    $matches = $pages->find("keywords.name=$keywords, limit=10"); // assuming your tags field is named "tags"
    // output your matches for pages with this tag
	echo "Tag Match";
} elseif($q) {
    $matches = $pages->find("title|body~=$q, limit=10");
    // output your matches for the search query
	echo "Title|Body Match";
	}
?>

So I browsed the main page and searched for a keyword that I am 100% sure it is used for page. So far the result was: Title|Body Match , even though I do not have any page title or body content with the same keyword. Am I missing something here?

Link to comment
Share on other sites

To refresh your understanding of GET variables, see this tutorial: http://html.net/tutorials/php/lesson10.php

$input->get is a PW-specific way of retrieving GET variables but it's essentially the same as the native PHP $_GET.

So these two lines...

$keywords = $sanitizer->name($input->get->keywords);
$q = $sanitizer->selectorValue($input->get->q);

...look for GET variables in the query string named "keywords" and "q", and then pass their values through a sanitizer. The result is assigned to variables $keywords and $q.

And these two lines...

if($keywords) {
//...
if($q) {
//...

...check to see if the variables are "truthy": that is, if they evaluate as true when converted to a boolean.

You are seeing "Title|Body Match" but this does not mean there actually is a title or body match - your if($q) test is passed so long as $q is not empty.

And also consider that if($keywords) can never be true if there is no "keywords" variable present in your query string.

  • Like 1
Link to comment
Share on other sites

@Robin S I decided to redo the tags again as it only takes a few steps to achieve that. So the first thing to try again was to include the keywords in $matches as I knew it is all working fine but to show keywords results. And suddenly, I can see both results correctly - the pages matching the text in title, body and those, matching keywords. I am not aware why it did not work at first place but my thoughts are that for different tests I might have changed a field, template or page from keywords to tags.

Now I need to only exclude the Keywords pages itself from the selectors query and it is all completed.

Thank you @Robin S mostly for your patience and simple solutions. It meant a lot to me to have a working first theme the way I like it as any other one that would follow will match in one way or the other what I already have.

Now I only have to dive in to selectors and find the way to exclude something from the query. That should not be a problem (I guess ;) ).

Link to comment
Share on other sites

Alright, we got it all sorted out now using the following query: 

$matches = $pages->find("template!=keywords,title|body|keywords~=$q, limit=10");

where !=keywords is to exclude any page using "keywords" as template and |keywords is the name of the field used to contain the tags.

I am super happy now as the cloud works fine sorting out the used tags by most used (which I needed) and the search page shows any result that contains the search text in title, body and keywords. Now I will only have to modify the text shown if no results are found to be suitable and that is all ;)

Link to comment
Share on other sites

For those who might be looking for a similar solution, here is my search php that is all done now.

Spoiler

<?php

/**
 * Search template
 *
 */

$out = '';

if($q = $sanitizer->selectorValue($input->get->q)) {

	// Send our sanitized query 'q' variable to the whitelist where it will be
	// picked up and echoed in the search box by the head.inc file.
	$input->whitelist('q', $q); 

	// Search the title, body and sidebar fields for our query text.
	// Limit the results to 50 pages. 
	// Exclude results that use the 'admin' template. 
	//$matches = $pages->find("title|body~=$q, limit=10");  
	$matches = $pages->find("template!=keywords,title|body|keywords~=$q, limit=10");

	$count = count($matches); 

	if($count) {
		
		$found = "Found $count articles containing text <strong class='text-danger'>\"$q\"</strong>";

		foreach($matches as $m) {
			
			$date = date('Y-j-d, G:i', $m->created);
			$relative = wireRelativeTimeStr($m->created);
			$cat_link = $m->rootParent->url;
			$cat_title = $m->rootParent->title;
			
			//Search result header block
			$out .= "<article class='hentry'>" .
					"<header class='entry-header'>" .
					"<i class='fa fa-list-alt fa-2x fa-fw pull-left text-muted'></i>" .
					"<h2 class='entry-title h4'><a href='{$m->url}' rel='bookmark'>{$m->title}</a></h2>" .
					"</header>" .
					"<footer class='entry-footer'>" .
					"<div class='entry-meta text-muted'>" .
					"<span class='date'>" .
					"<i class='fa fa-clock-o fa-fw'></i>" .
					"<time datetime='$date'>$relative</time>" .
					"</span>" .
					"<span class='category'><i class='fa fa-folder-open-o fa-fw'></i> <a href='$cat_link'>$cat_title</a></span>" .
					"</div>" .
					"</footer>" . 
					"</article>";
		}

	} else {
		$found = "There are no records found in the database...";
		$out .= "</article>I am sorry for the inconvenience, however no matter how hard I tried digging deep in my database, there were no matches found matching <strong class='text-danger'>'$q'</strong> in title, body text and keywords. Maybe you could try to refine the search terms and try me again? <p><br />Another option would be to write down an <strong class='text-danger'>article, faq or news</strong> on the topic yourself so that the next search comes with a positive result <i class='fa fa-smile-o fa-1'></i></p>";
	}
	} else {
		$found = "I am missing a search term in the search box...";
		$out .= "</article>In order to allow me to search the database and find some useful stuff I need to have some <strong class='text-danger'>search terms</strong>. Otherwise it would be just a pointless waste of mine and your time trying to pull some articles that are not important for your goals!";
}

// Note that we stored our output in $out before printing it because we wanted to execute
// the search before including the header template. This is because the header template 
// displays the current search query in the search box (via the $input->whitelist) and 
// we wanted to make sure we had that setup before including the header template. 

include ('./includes/header.php');
		
include ('./includes/breadcrumbs.php'); ?>
		
		<div id="main" class="site-main clearfix">
			<div class="container">
	
				<div class="content-area">
					<div class="row">

						<div id="content" class="site-content col-md-9">
						
							<header class="archive-header">
								<h1 class="archive-title"><?php echo $title;?></h1>
							</header><!-- .archive-header -->
							
							<blockquote class="archive-description">
								<p><?php echo $found;?></p>
							</blockquote><!-- .archive-description -->
							
							<div class="archive-list archive-article">

								<?php echo $out; ?>

							</div><!-- .archive-list -->

						</div><!-- #content -->

<?php include ('./includes/sidebar.php');?>

					</div>
				</div><!-- .content-area -->
	
			</div>
		</div><!-- #main -->
		
<?php include ('./includes/footer.php');?>

 

 And here is the sidebar.php code related to the tag cloud that makes the keywords in the cloud showing the search results:

Spoiler

$table = $fields->get('keywords')->getTable(); // 'keywords' contains the name of my Page field
$query = $database->query("SELECT data FROM $table");
$ids = $query->fetchAll(\PDO::FETCH_COLUMN);
$count_values = array_count_values($ids); // count the frequency of values in the array
arsort($count_values); // sort highest to lowest
$count_values = array_slice($count_values, 0, 20, true); // we only want the top 20 tags
// use the pages IDs and counts as needed, for example:
foreach($count_values as $key => $value) {
$p = $pages->get($key	);
// output whatever you want using $p (Page object) and $value (number of times tag is selected)
echo "<a href='/search/?q={$p->name}' class='btn btn-tag btn-xs'>{$p->title}-$value</a>";
}

 

 

Link to comment
Share on other sites

Today I got back to the project again and following the already existing arrays to make dynamic the Latest News block:

Spoiler

<div class="col-md-6">
  <section id="section-lastest-articles" class="section">
    <h2 class="section-title h4 clearfix">Latest News</h2>

    <?php 

$result = $pages->get("/news/")->children("limit=4"); //Limitted array to /news/ with 4 pages

foreach ($result as $news): ?> <!-- Assign the result to $news variable -->

    <ul class="fa-ul">
      <li>
        <i class="fa-li fa fa-list-alt fa-fw text-muted"></i>
        <h3 class="h5"><a href="#"><?=$news->title("sort=-date");?></a></h3>
        <small class="meta text-muted">
          <span class="time"><i class="fa fa-clock-o fa-fw"></i> <?php echo wireRelativeTimeStr($news->created);?></span>
        </small>
      </li>
    </ul>

    <?php endforeach; ?>

  </section><!-- #section-lastest-articles -->
</div>

 

Basically there was nothing difficult/different other than creating a limited array (4 pages as per the design) and sort the result by -date. The rest has been already discussed earlier, so am not going to repeat it.

Besides setting up the Latest news, I decided to remove the Twitter block from the footer as far as the theme would be used on a local server in the office.

Link to comment
Share on other sites

Hello again.

Today I worked on fixing the "class" active of the home menu and ended up removing the Menu module discussed earlier and using the code from the default demo profile:

Spoiler

<!-- // top navigation consists of homepage and its visible children -->
<!-- // $home is defined in _init.php and is equal to $pages->get('/') -->
<?	foreach($home->and($home->children) as $item) {
if($item->id == $page->rootParent->id) {
echo "<li class='active'>";
} else {
echo "<li>";
}
echo "<a href='$item->url'>$item->title</a></li>";
} ?>

 

After the menu fix and some block contents moving around to avoid duplicates, it was the time to start digging into the comments functionality. Initially I expected much bigger resistance, however it took me 2 minutes to install two necessary modules: FieldtypeComments and Comments to allow the adding and approving them. As far as the default form was not close to my styling, I had to use the custom calls to achieve it:

Spoiler

<div id="comments" class="comments-area">
	
	<?php	foreach($page->comments as $comment) {
				if($comment->status < 1) continue; // skip unapproved or spam comments
				$cite = htmlentities($comment->cite); // make sure output is entity encoded
				$text = htmlentities($comment->text);
				$date = date('m/d/y g:ia', $comment->created); // format the date ?>
						
	
				<ol class="comment-list">
					<li class="comment">
						<article class="comment-body">
							<footer class="comment-meta">
								<div class="comment-author">
									<img src="<?=$templateurl;?>assets/img/avatar.png" alt="" height="50" width="50" class="avatar" />
									<b class="fn"><a href="#" rel="external" class="url"><?=$cite;?></a></b>
								</div><!-- .comment-author -->

								<div class="comment-metadata">
									<small class="date text-muted">
										<time datetime="<?=$date;?>"><?=$date;?></time>
									</small>
								</div><!-- .comment-metadata -->
							</footer><!-- .comment-meta -->

							<div class="comment-content">
								<p><?=$text;?></p>
							</div><!-- .comment-content -->

							<div class="comment-reply text-right">
								<a class="btn btn-custom btn-sm" href="#">Reply</a>
							</div><!-- .comment-reply -->
						</article><!-- .comment-body -->
					</li><!-- .comment -->

					
				</ol><!-- .comment-list -->

				<? } 
				
				
				
				if ($user->isGuest()) { ?>	
				
				<div id="respond" class="comment-respond">
					<div class="alert alert-warning alert-custom">
						<i class="fa fa-warning text-danger"></i>
						<h4>BECOME A COMMENTERS?</h4>
						You must be <a href="#">logged in</a> to post a comment.
					</div>
				</div><!-- #respond -->
				
				<? } else {
					
					echo $page->comments->renderForm(); 
				} ?>
				
</div><!-- #comments -->

 

Then I added a few comments and I was extremely happy to see them appearing properly 100% matching the original styling.

Where I am having a bit of confusion is how to apply the custom styling to the comment submition form as the original call of echo $page->comments->renderForm(); shows a form that is having different styling. Also as my form has a button REPLY, I was thinking is there a quick way to link to the post submition form?

For sure I can use the styles of the original form and modify them to fit my needs, however I am wondering is there an easier way to achieve that using the theme code and some hooks or else?

 

Link to comment
Share on other sites

Working on comments this morning I noticed that in the menu there is something that should not be there - I had the Tags page reference appearing. It was normal for the query I used so I decided to dig a bit to see how can I exclude it (I don't need to show the tags as a menu link pointing to 404).

So I modified a bit the query and am sure I will use this approach for any future templates, so here it is:

Spoiler

<ul class="nav navbar-nav navbar-menu">

<!-- // top navigation consists of homepage and its visible children -->
<!-- // $home is defined in _init.php and is equal to $pages->get('/') -->
<?	foreach($home->and($home->children("template!=keywords")) as $item) { // 'keywords' is my page template I need to exclude from menu
	if($item->id == $page->rootParent->id) {
		echo "<li class='active'>";
	} else {
		echo "<li>";
	}
	echo "<a href='$item->url'>$item->title</a></li>";
} ?>

 

 

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...