Archive for the ‘PHP’ Category

Okay, it’s not blogging, but it took me forever to track down how to do this, so I figured I’d post it for others to use.

The setting: An ecommerce site.
The need: Searching for items in the inventory, but returning the same result whether the search terms are singular or plural, without messing up an exact phrase.
The solution: create two sets of keywords, singular and plural and search both literal and for the individual words via a regular expression.

I’m sure there are more efficient ways of doing this, but I couldn’t find any, and this is what I cam up with.

//$get_search is the search keyword(s) submitted via a
//  GET and filtered to block any hacking attempts

// $catSort is the sort criteria 'relevance',
//    'price:lo to hi', 'price:ho to lo', etc.
  $catSort=$_GET['catSort'];
  if(!$catSort){$catSort='relevance'; $order=' ORDER BY `rel` DESC';}
  $itemsPerPage=$_GET['itemsPerPage'];
  if(!$itemsPerPage){$itemsPerPage='20';}
  if($catSort=='l2h'){$order=' ORDER BY `price` ASC';}
  if($catSort=='h2l'){$order=' ORDER BY `price` DESC';}
  if($catSort=='item'){$order=' ORDER BY `productname`';}

//clean up the keyword some
  $get_search=rtrim(ltrim(urldecode($get_search

//  this array helps converts the key word (or last keyword in a phrase) to the plural form
$plural_rules = array(
'/(x|ch|ss|sh)$/' => '\1es',            # search, switch, fix, box, process, address
'/series$/' => '\1series',              #series is the same
'/([^aeiouy]|qu)y$/' => '\1ies',        # query, ability, agency
'/(?:([^f])fe|([lr])f)$/' => '\1\2ves', # half, safe, wife
'/sis$/' => 'ses',                      # basis, diagnosis
'/ex$/' => 'ices',                      # index
'/([ti])um$/' => '\1a',                 # datum, medium
'/person$/' => 'people',                # person, salesperson
'/man$/' => 'men',                      # man, woman, spokesman
'/foot$/' => 'feet',
'/louse$/' => 'lice',
'/mouse$/' => 'mice',
'/tooth$/' => 'teeth',
'/child$/' => 'children',
'/([nti])on$/' => 'a',                  # criterion
'/(.*)status$/' => '\1statuses',
'/s$/' => 's',                          # no change (compatibility)
'/$/' => 's'                            # everything else
);

//  this array helps converts the key word (or last keyword in a phrase) to the singular form
$singular_rules = array(
'/(xes|ches|sses|shes)$/e' =>           # the /e allows an executable phrase within the replacement
    "str_replace('es','',$1)",          # boxes, classes
'/series$/' => 'series',
'/([^aeiouy]|qu)ies$/' => '\1y',
'/([lr])ves$/' => '\1\2f',              # halves, scarves
'/([^f])ves$/' => '\1fe',               # wives
'/ses$/' => 'sis',                      # bases, diagnoses
'/ices$/' => 'ex',                      # indices
'/([ti])a$/' => '\1um',                 # data, media
'/people$/' => 'person',                # people, salespeople
'/men$/' => 'man',                      # men, women, spokesmen
'/feet$/' => 'foot',
'/lice$/' => 'louse',
'/mice$/' => 'louse',
'/teeth$/' => 'tooth',
'/children$/' => 'child',
'/([nti])a$/' => 'on',                  # criteria
'/(.*)statuses$/' => '\1status',
'/[aious]s$/' => '',                    # no change (compatibility)
'/s$/' => ''                            # everything else that ends in an 's'
);

$string=$get_search;
$match=false;
foreach($plural_rules as $pattern => $replacement)
  {
  if(!$match and preg_match($pattern, $string))
    {
    $out=preg_replace($pattern, $replacement, $string);
    $match=true;
    }
  }
if($out and $out!=$get_search){$plural=$out;}
else{$plural=$get_search;}

$match=false;$out='';
foreach($singular_rules as $pattern => $replacement)
  {
  if(!$match and preg_match($pattern, $string))
    {
    $out=preg_replace($pattern, $replacement, $string);
    $match=true;
    }
  }
if($out and $out!=$get_search){$singular=$out;}
else{$singular=$get_search;}
}

// the regexp will crash if we send it an empty
//   variable. So we do this
if(!$get_search){$get_search='[[[]]]';}

//I typical throw in a capital letter to distinguish
//  arrays from variables
$get_Search=explode(' ',$get_search);

//Yes, I know there's an easier way to weed out repeated
//   spaces, but I was in a hurry
$Temp='';
foreach($get_Search as $el)
  {
  if($el){$Temp[]=$el;}
  }
$get_Search=$Temp;

//we include all the keywords for the regexp part
$regexp=implode('|',$get_Search);

//these cell names are samples
// the first part (with all the IFs) allows us to create a
//   relevance score, customized to weight the individal
//   cells searched. We place a lot of weight on an exact
//   name match. and we do it once for the singular form
//   and once for the plural form and once for the regexp.
// $order we set previously so the search can be ordered
//   by price, item name or relevance
  $queryText = sprintf("
SELECT `productid`,
 IF(`productname` LIKE '%%%s%%',20,0)
+IF(`description` LIKE '%%%s%%',4,0)
+IF(`manufacturer` LIKE '%%%s%%',1,0)
+IF(`productcode` LIKE '%%%s%%',1,0)
+IF(`productname` LIKE '%%%s%%',20,0)
+IF(`description` LIKE '%%%s%%',4,0)
+IF(`manufacturer` LIKE '%%%s%%',1,0)
+IF(`productcode` LIKE '%%%s%%',1,0)
+IF(`productname` REGEXP '[[:<:]]%s[[:>:]]' ,2,0)
+IF(`description` REGEXP '[[:<:]]%s[[:>:]]' ,2,0)
+IF(`manufacturer` REGEXP '[[:<:]]%s[[:>:]]' ,1,0)
+IF(`productcode` REGEXP '[[:<:]]%s[[:>:]]' ,1,0) AS `rel`
FROM `ecommerce_products`
WHERE (`productname` LIKE '%%%s%%'
   OR `description` LIKE '%%%s%%'
   OR `manufacturer` LIKE '%%%s%%'
   OR `productcode` LIKE '%%%s%%'
   OR `productname` LIKE '%%%s%%'
   OR `description` LIKE '%%%s%%'
   OR `manufacturer` LIKE '%%%s%%'
   OR `productcode` LIKE '%%%s%%'
   OR `productname` REGEXP '[[:<:]]%s[[:>:]]'
   OR `description` REGEXP '[[:<:]]%s[[:>:]]'
   OR `manufacturer` REGEXP '[[:<:]]%s[[:>:]]'
   OR `productcode` REGEXP '[[:<:]]%s[[:>:]]') $order;
",
         mysql_real_escape_string($plural),
         mysql_real_escape_string($plural),
         mysql_real_escape_string($plural),
         mysql_real_escape_string($plural),
         mysql_real_escape_string($singular),
         mysql_real_escape_string($singular),
         mysql_real_escape_string($singular),
         mysql_real_escape_string($singular),
         mysql_real_escape_string($regexp),
         mysql_real_escape_string($regexp),
         mysql_real_escape_string($regexp),
         mysql_real_escape_string($regexp),
         mysql_real_escape_string($plural),
         mysql_real_escape_string($plural),
         mysql_real_escape_string($plural),
         mysql_real_escape_string($plural),
         mysql_real_escape_string($singular),
         mysql_real_escape_string($singular),
         mysql_real_escape_string($singular),
         mysql_real_escape_string($singular),
         mysql_real_escape_string($regexp),
         mysql_real_escape_string($regexp),
         mysql_real_escape_string($regexp),
         mysql_real_escape_string($regexp));

// I use a db link variable to allow error messages
  $query=mysql_query($queryText, $link);
  if(mysql_errno($link)){echo ": " . mysql_error($link) . "\n<hr>";}

  if(mysql_num_rows($query))
    {
    while ($dbRow = mysql_fetch_array($query))
      {
      $id=$dbRow[0];
// this is to weed out duplicates
      $return[$id]++;
      }

// this creates a nice array of product ids for us to work with.
// using only the product ids we can begin with the entire list,
//   without over burdening the server
// from here we simply grab the ones we need (based on the
//    start # and pagelength #)
// using the product id, I use a function that then grabs
//    all the pertinent data about that item.
  foreach($return as $key=>$value)
    {
    $prodList[]=$key;
    }

I’ve installed the plugin Facebook Connect and it works great. One thing I wanted, though, was a smaller connect button to put on the sidebar. So here are the hacks I made.

In the /wp-content/plugins/wp-facebookconnect directory edit fbconnect.php. Locate the function “fbc_display_login_button”. Copy the entire function and paste it beneath the original one, renaming it “fbc_display_login_button2″. Change the line…

$button = render_fbconnect_button();

…to…

$button = render_fbconnect_button2();

…and change…

echo <<<EOF <div $visibility id="fbc_login">
 <span><small>Connect with your Facebook Account</small></span>
 <br/> $button </div>
EOF;

…to… 

  echo <<<EOF <li id="fbc_login">$button</li> EOF;

…and save it.

Next edit common.php and locate the function render_fbconnect_button. Copy it and make a duplicate just beneath it named render_fbconnect_button2. Now make the following changes to the new function.

Change…

  return <<<EOF
 <div>
   <fb:login-button size="large" background="white" length="short" $onlogin_str>
   </fb:login-button>
 </div> EOF;

…to…

return <<<EOF
   <fb:login-button size="small" background="white" length="short"
 $onlogin_str v="2">Login/register with Facebook
   </fb:login-button> EOF;

…and save the file.

We’re still not finished.

If you don’t use widgets, then you simply have to add…

<?php fbc_display_login_button2(); ?>

…in the sidebar.php of your theme beneath the login (the login will look like…

<?php wp_loginout(); ?>

However if you use widgets, then it’ll be more difficult. What I did was edit wp-includes/default-widgets.php and located the class WP_Widget_Meta. I then added the…

<?php fbc_display_login_button2(); ?>

…just below the…

<?php wp_loginout(); ?>

Make sure you backup all the files your editing, both the original and the updated versions, because A. if you mess up you’ll need the original and B. once an update of WP or the plugin occurs, you’ll need to make the changes all over again.

Occasional you’ll want to control how people leave your site. Maybe it’s to let them know they will be going to a different site, maybe it’s just to control who gets linking credit for search engine spiders, or maybe you want the to see one more ad before leaving. In any case, here’s how I’ve solved the problem when asked by clients.

First some javascript to turn all links into redirects to a specific page…

<script>
function catchOutLinks()
  {
  for (i=0; i < document.links.length; i++)
    {
    var url = window.location.protocol + "//"
        + window.location.host;
    if((document.links[i].href.indexOf(url)>-1))
      {
      // ignore local links
      }
    else
      {
      oldref=document.links[i].href;
      document.links[i].href='/leaving.php?url='+oldref;
      }
    }
  }
</script>

Place that in the <head></head> portion of whatever template will be used to display pages you want outlinks on. To activate it you can either use an onLoad statement in your <body> tag, i.e….

<body onLoad="catchOutLinks()">

…or use this in the footer, below any other links…

<script>
catchOutLinks();
</script>

Next you’ll want to create a page using your regular template, but for the content use the following…

<?
$url=$_GET['url];
?>
You will be redirect to <?=$url; ?>
<META http-equiv="refresh" content="3;URL=<?=$url;?>>

Save the file, and make sure it’s a php file (has the extension .php) and in the javascript above, change “/leaving.php” to the url of the file you just saved.

There’s a simple way and a hard way to do this. The hard way also adds a shortened url, allowing the reader to include more of the headline in the 140 characters twitter allows.

First the easy way.

WordPress…

<a href="http://twitter.com/home?status=<?
the_title('','',FALSE);
?>%20<?
the_permalink();
?>" target="_blank">re-Tweet this</a>

Movable Type…

<a href="http://twitter.com/home?status=
<$MTEntryTitle$>%20<$MTEntryPermalink$>
" target="_blank">re-Tweet this</a>

Now the hard way involves going to http://bit.ly/pages/tools/developer-tools/ and joining. Get your API Key and username. Then create the following PHP file (you can name it whatever you want, but just remember the name, because we’ll be using it). Make sure to replace [bitly username] with your bitly username and [API Key] with your bitly API Key.

<?
$link=urldecode($_GET['l']);
$title=urldecode($_GET['t']);
$rtData=implode('',file("http://api.bit.ly/shorten?
version=2.0.1&longUrl=$link&login=
[bitly username]&apiKey=[API Key]"));
$out=json_decode($rtData, true);
$results=$out['results'];
$articleLink=$artData['article_link'];
$myLink=$results[$articleLink];
$shortUrl=$myLink['shortUrl'];
header("Location: http://twitter.com/home?
status=$title%20$shortUrl");
exit();
?>

Once that’s created with the correct username and API Key, save it to your server in a location reachable from the web. Let’s say you save it at http://myserver.com/retweet.php. We’ll then use the following for out links.

WordPress…

<a href="http://myserver.com/retweet.php?t=<?
the_title('','',FALSE);
?>&l=<? the_permalink();
?>" target="_blank">re-Tweet this</a>

Movable Type…

<a href="http://myserver.com/retweet.php?
t=<$MTEntryTitle$>&l=<$MTEntryPermalink$>
" target="_blank">re-Tweet this</a>

This then will take the title and url, convert the url into a shortened url and make your twitter status look something like this…

How to make a "Retweet this post" link http://bit.ly/ddmFZr

Maybe this is really simple for many, but I still see people without an email link on their individual posts. The reason for it is if a reader finds the post very useful, then they can easily email it to a friend, helping increase the readership of your blog.

For WordPress…

<?
$emailTitle =urlencode(the_title('','',FALSE));
$emailTitle = str_replace('+', ' ', $emailTitle);
?>
<a href="mailto:?subject=<? echo $emailTitle;
?>&body=Check this out!%0A%0A<?php the_permalink()
?>">Email this Post</a>

For Movable Type (if your static pages are PHP)…

<?
$emailTitle =urlencode('<$MTEntryTitle encode_php="q"$>');
$emailTitle = str_replace('+', ' ', $emailTitle);
?>
<a href="mailto:?subject=<? echo $emailTitle;
?>&body=Check this out!%0A%0A<$MTEntryPermalink$>"
>Email this Post</a>

If your MT static pages are not PHP, then I doubt it will work for you.

If you want to use a little envelope graphic, there’s a tons of icons at http://famfamfam.com/. Grab the “Silk” package, and make sure you give him a link back. You’d want to format the image part like this…

<img src="envelope.png" title="Email this Post">

The title tag is needed so the text will appear when the image is mousedover.

About me

I've been developing web sites for over 12 years. I started with HTML, moved on to Perl and now do mostly PHP with a lot of MySQL and Javascript as well.

The purpose of this blog is to write about many of the simpler scripting solutions bloggers are either unaware of or unable to implement. Hopefully I'll have something you can use

Danny Carlton

Advertising
Advertising