comparing variables in PHP with different language files - php

I have a select menu for users.
It is populated by PHP variables, which are all language variables. For example:
$word = $lang['word'];
$select = array($word);
Therefore, the select menu options will change based on the language the user has chosen. I need to be able to compare users' selections to each other. For example:
if($user1word == $user2word) ...
But because of the language files, this doesn't work. Obviously "one" != "Uno" even though they're the same.
My first fix was to change everything to a numeric value before posting it to the database. Example:
if($_POST['word'] == $lang['word']) { $userWord = 1 }
This worked perfectly for all words except those that contained special characters (å, æ, é...) and nothing I did could resolve this (I tried normalizer; language-specific accept-char onchange events for the form; utf8_encode. It was hopeless.
Currently everything saves to the database as text, dependent on the language the user is in. So if "Language" is an option, but you're in Norwegian, it saves as "Språk".
I need a simple solution that doesn't crush my mind - I am new to PHP.

Currently everything saves to the database as text, dependent on the language the user is in.
This is a design flaw in my opinion. Ideally your data would be as agnostic as possible to language and translations would be performed just for the UI with tools like gettext. Typically items like select values would be stored with keys or IDs.

Use a map that is based on a common index, this index can be english, spanish or numeric. i'd suggest numeric. Store the numeric index in your database.
A step in the right direction:
$lang['en'][0] = 'Hello';
$lang['de'][0] = 'Hallo';
$lang['es'][0] = 'Hola';
$lang['en'][1] = 'Sup?';
$lang['de'][1] = 'Wie gehts?';
$lang['es'][1] = 'Que pasa?';
$userLang = 'en';
// show the select
echo '<select name="word">';
foreach ( $lang[$userLang] as $index => $word {
echo '<option value="'.$index.'">'.$word.'</option>';
}
echo '</select>';
// show the selected word:
echo 'You chose to say "'.$lang[$userLang][$_POST['word']].'".';
// compare the word to what is in the db
if ( $_POST['word'] === $dbRow['word'] ) {
// expression matches!
// assume a column in the db "language" describes the language the user chose, e.g. 'en', 'de', or 'es'
echo 'You previously chose "'.$lang[$dbRow['language']][$dbRow['word']].'" in the language "'.$dbRow['language'].'".;
}
Depending on how you want to organize it, you may favor grouping by phrase instead of grouping by language, i.e.:
$lang[0]['en'] = 'Hello';
$lang[0]['de'] = 'Hallo';
$lang[0]['es'] = 'Hola';

Related

Filter a php array to only elements that have a matching value to $name in $data

I have a php script getting all folders in a posts folder and making them into a list.
I have a $postinfo_str variable assigned to a json file for each folder which I am using to store post date and category/tag info etc in.
I also have a $pagetitle variable assigned to a title.php include file for each folder. So say I am on a "June 2018" archive page, the text in that file will be "June 2018". If I am on say a "Tutorials" category page, that will be the text in the title.php.
In the json file, I have:
{
"Arraysortdate": "YYYYMMDD",
"Month": "Month YYYY",
"Category": ["cat1", "cat2", "etc"]
}
I am ordering the array newest to oldest using krsort with Arraysortdate as key.
How do I filter the array using $pagetitle as input, finding if there is a match in $postinfo_str, and if there isn't, remove that folder from the array?
All I can seem to find regarding array sorting is where the info in the $pageinfo_str is basically the array and so by that, the $title is the input and the output is the matching text from the $postinfo_str, whereas I want the output to be the folders that only have the matching text in the $postinfo_str to what the input ($pagetitle) is.
Here is my code I have.. Keep in mind this is flat file, I do not want a database to achieve this. See comments if you want an explaination.
<?php
$BASE_PATH = '/path/to/public_html';
// initial array containing the dirs
$dirs = glob($BASE_PATH.'/testblog/*/posts/*', GLOB_ONLYDIR);
// new array with date as key
$dirinfo_arr = [];
foreach ($dirs as $cdir) {
// get current page title from file
$pagetitle = file_get_contents("includes/title.php");
// get date & post info from file
$dirinfo_str = file_get_contents("$cdir/includes/post-info.json");
$dirinfo = json_decode($dirinfo_str, TRUE);
// add current directory to the info array
$dirinfo['dir'] = $cdir;
// add current dir to new array where date is the key
$dirinfo_arr[$dirinfo['Arraysortdate']] = $dirinfo;
}
// now we sort the new array
krsort($dirinfo_arr);
foreach($dirinfo_arr as $key=>$dir) {
$dirpath = $dir['dir'];
$dirpath = str_replace('/path/to/public_html/', '', $dirpath);
?>
<!--HTML HERE SUCH AS--!>
TEXT <br>
<?php
};
?>
I have difficulties following your problem description. Your code example is slightly confusing. It appears to load the same global includes/title.php for each directory. Meaning, the value of $pagetitle should be the same every iteration. If this is intended, you should probably move that line right outside the loop. If the file contains actual php code, you should probably use
$pagetitle = include 'includes/title.php';
or something similar. If it doesn't, you should probably name it title.txt. If it is not one global file, you should probably add the path to the file_get_contents/include as well. (However, why wouldn't you just add the title in the json struct?)
I'm under the assumption that this happened by accident when trying to provide a minimal code example (?) ... In any case, my answer won't be the perfect answer, but it hopefully can be adapted once understood ;o)
If you only want elements in your array, that fulfill certain properties, you have essentially two choices:
don't put those element in (mostly your code)
foreach ($dirs as $cdir) {
// get current page title from file
$pagetitle = file_get_contents("includes/title.php");
// get date & post info from file
$dirinfo_str = file_get_contents("$cdir/includes/post-info.json");
$dirinfo = json_decode($dirinfo_str, TRUE);
// add current directory to the info array
$dirinfo['dir'] = $cdir;
// add current dir to new array where date is the key
// ------------ NEW --------------
$filtercat = 'cat1';
if(!in_array($filtercat, $dirinfo['Category'])) {
continue;
}
// -------------------------------
$dirinfo_arr[$dirinfo['Arraysortdate']] = $dirinfo;
array_filter the array afterwards, by providing a anonymous function
// ----- before cycling through $dirinfo_arr for output
$filtercat = 'cat1';
$filterfunc = function($dirinfo) use ($filtercat) {
return in_array($filtercat, $dirinfo['Category']));
}
$dirinfo_arr = array_filter($dirinfo_arr, $filterfunc);
you should read up about anonymous functions and how you provide local vars to them, to ease the pain. maybe your use case is bettersuited for array_reduce, which is similar, except you can determine the output of your "filter".
$new = array_filter($array, $func), is just a fancy way of writing:
$new = [];
foreach($array as $key => $value) {
if($func($value)) {
$new[$key] = $value;
}
}
update 1
in my code samples, you could replace in_array($filtercat, $dirinfo['Category']) with in_array($pagetitle, $dirinfo) - if you want to match on anything that's in the json-struct (base level) - or with ($pagetitle == $dirinfo['Month']) if you just want to match the month.
update 2
I understand, that you're probably just starting with php or even programming, so the concept of some "huge database" may be frightening. But tbh, the filesystem is - from a certain point of view - a database as well. However, it usually is quite slow in comparison, it also doesn't provide many features.
In the long run, I would strongly suggest using a database. If you don't like the idea of putting your data in "some database server", use sqlite. However, there is a learning curve involved, if you never had to deal with databases before. In the long run it will be time worth spending, because it simplifys so many things.

Efficient way to make a (PHP) redirection functionality for multiple (128) database values

Currently I am working on a webpage where certain data from a database is being fetched and displayed to the user. This information is about certain project information and the lead partners of those projects. I want to implement a functionality where the user can click on a lead partner (link) and the link will redirect this user to another page (where the database information of all the organisations is on) BUT with a predefined search for only the organisations that are equal to the clicked lead partner link.
I found a solution on how to do this but the problem is that the solution (which i describe further below) isn't that good/efficient for multiple (128) organisations.. So the question is, do you know a better/more efficient solution to achieve this. To answer this question you probably also need some background information:
BACKGROUND INFORMATION
In the outputted database information (which is done in a table) there are several information columns/titles such as:
Project name
Organisations involved
Leader partner
Project website
And so on...
The fetching of the data is being done with a simple query where certain database columns have a specific identifier. For example, the project website columns is obviously a link, so in the query it is being done as follows: $SomeQueryVar = ("SELECT project_website as LINK FROM xxxx WHERE project_website ('$some_website') "); -- Just a short example to clarify things.
For the displaying, the data is being 'catched' like so:
if(count($SomeQueryVar)>0){
for($i=0;$i<count($SomeQueryVar);$i++){
echo "<tr>";
foreach($SomeQueryVar[$i] as $key=>$value){
echo "<td>";
$b=unserialize($value);
if($key =='LINK' && $value != NULL){
$first = true;
array_filter($b);
foreach($b as $y){
echo ''."Project website".'';
$first = false;
if(!$first) break;
}
} else {
echo $value;
}
echo "</td>";
}
echo "</tr>";
}
}
As you can see in the above code, certain database columns need other displaying as the rest. For example, links must be clickable instead of just being plain text. These 'exceptions' are being catched with the if $key ==, for the data that just needs regular displaying (plain text) there is the last else inserted that just echo's the $value.
MY FOUND SOLUTION
So regarding the question, i found out that i can create redirection links using the ?SomePage on the projects page and using this 'added link value' on the organisations page to compare it. If the link is equal, then do the specific query. But it is probably easier to paste the code here:
The 'CATCHING' part
To catch a specific lead partner is also used an identifier in my query called ORG (which stands for organisation). So here is the code where i catch the organisations table:
if($key =='ORG' && $value != NULL){
$needle = array('Brainport','Development','BRAINPORT',
'brainport','DEVELOPMENT','development');
$needle2 = array('Casa','CASA', 'casa');
if (strpos_arr($value,$needle) !== false) {
echo '<a href="http://portal.e-ucare.eu/database/organisations/?a=brainport" >'.$value.'</a>';
}
if (strpos_arr($value,$needle2) !== false) {
echo '<a href="http://portal.e-ucare.eu/database/organisations/?a=casa" >'.$value.'</a>';
}
}
In the above code i just created it for 2 organisations (brainport and casa). For this solution i used a function called strpos_arr which searches for the needle in the haystack;) So in the code above i set the needles to the names in the database where it has to create a link for. So if for example a company with the word Brainport exists in the database, this 'catcher' will see this and display the organisation and make the name clickable.
The strpos_arr function for the $needle is as follows:
function strpos_arr($value, $needle) {
if(!is_array($needle)) $needle = array($needle);
foreach($needle as $what) {
if(($pos = strpos($value, $what))!==false) return $pos;
}
return false;
}
In the redirection page the code will also catch certain links to make queries for that link -- so for the brainport link this is http://portal.e-ucare.eu/database/organisations/?a=brainport -- in the second page code this link is catched like so:
$host = $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'];
if($host == 'portal.e-ucare.eu/database/organisations/?a=brainport')
{
$link_word = "Brainport";
$tmp = $wpdb->get_results("
SELECT
name_of_company__organization, company_location,
company_website as LINK, organisation_type,
organisation_active_in_, organisation_scope,
organisation_files as DOC
FROM
wp_participants_database
WHERE
name_of_company__organization REGEXP ('$link_word')
ORDER BY
name_of_company__organization ASC
");
}
With this solution i can do what i want, BUT like i said in the beginning, i need this for not just 2 organisations but 128!! This would mean i have to copy the catching code blocks on both pages 128 times!! This is obviously not very efficient..
So is there any more efficient way to achieve this? Sorry if it is a bit unclear but i found it quite hard to easily wright this down;)
Anyway, thank you in advance!
The firts part you can rewrite to
if($key =='ORG' && $value != NULL){
$needles = array(
'brainport'=>array('Brainport','Development','BRAINPORT','brainport','DEVELOPMENT','development'),
'casa'=>array('Casa','CASA', 'casa')
);
foreach($needles AS $nkey => $needle){
if(strpos_arr($value,$needle) !== false) {
echo "<a href='http://portal.e-ucare.eu/database/organisations/?a={$nkey}' >{$value}</a>";
}
}
}
For the second part make an array like
[EDIT]
$link_words = array(
'portal.e-ucare.eu/database/organisations/?a=brainport'=>'Brainport',
'portal.e-ucare.eu/database/organisations/?a=casa'=>'Casa',
);
then you can use
$host = $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'];
if(!empty($link_words[$host])){
$link_word = $link_words[$host];
$tmp = $wpdb->get_results("
SELECT
name_of_company__organization, company_location, company_website as LINK, organisation_type, organisation_active_in_, organisation_scope, organisation_files as DOC
FROM
wp_participants_database
WHERE
name_of_company__organization REGEXP ('$link_word')
ORDER BY
name_of_company__organization ASC
");
}

PHP: Most efficient way to display a variable within text when the text could be one of many possibilities

Below is a link to my original question:
PHP: How to display a variable (a) within another variable(b) when variable (b) contains text
Ok here's more to the problem, all your suggestions work but now I'm looking for the most efficient method to my specific problem.
In my database I have several blocks of text. When a user(described as $teamName) logs in to the site, they are randomly assigned one of these blocks of text. Each block of text is different and may have different variables in it.
The problem is I don't have knowledge of which block of text is assigned to the user without actually viewing the database or running a query. So at the moment I have to query the database and select the $newsID that corresponds to the block of text that the user has been assigned.
Because I have preset the blocks of text, I know what they contain so I can know do a switch($newsID) and depending on the value of the $newsID I then run the correct values inserted into the sprintf() function.
There is however, many many blocks of text so there will be many instances of case "": and break;. I wish to have the site working so that if at any stage I change a block of text to something different, then the variables within sprintf() are automatically updated, rather than me manually updating sprintf() within the switch() case:.
Sorry for the long post, hope it makes sense.
EDIT:
I have these predetermined blocks of text in my database in my teamNews table:
For $newsID = 1:
"$teamName is the name of a recently formed company hoping to take over the lucrative hairdryer design
$sector"
For $newsID = 2:
"The government is excited about the potential of ".$teamName.", after they made an annoucement that they have hired $HoM"
For $newsID = 3:
"It is rumored that $teamName are valuing their hairdryer at $salePrice. People are getting excited.
When a user($teamName) logs into the game they are randomly assigned one of these blocks of text with $newsID of 1,2 or 3.
Lets say the user is assigned the block of text with $newsID = 2. So now their username($teamName) is inserted into the database into the same row as their selected text.
Now I want to display the text corresponding to this user so I do the following:
$news = news ($currentStage,$teamName);
switch ($ID)
{
case "1":
sprintf($teamName,$sector)
echo $news."<br/><hr/>";
break;
case "2":
sprintf($teamName,$Hom)
break;
case "3":
sprintf($teamName,$saleprice)
break;
}
$currentStage--;
}
With the function
function news($period,$teamName)
{
$news = mysql_query("
SELECT `content`,`newsID` FROM `teamnews` WHERE `period` = '$period' && `teamName` = '$teamName'
") or die($news."<br/><br/>".mysql_error());
$row = mysql_fetch_assoc($news);
$news = $row['content'];
$ID = $row ['newsID'];
return $news,$ID;
}
The problem is that in reality there are about 20 different blocks of text that the user could be assigned to. So I will have many case:'s.
Also if I want to change all the text blocks in the database I would have to also manually change all the variables in the sprintf's in each ``case:`
I am wondering is there a better way to do this so that if I change the text in the database then the paramaters passed to sprintf will change accordingly.
So if I use
$replaces = array(
'teamName' => 'Bob the team',
'sector' => 'murdering',
'anotherSector' => 'giving fluffy bunnies to children'
);
is it possible to do this:
$replaces = array(
'$teamName' => '$teamName',
'$sector' => '$sector',
'$anotherSector' => '$anothersector'
);
I suggest you have fixed set of named placeholders, and use either the str_replace() or eval() (evil) methods of substitution.
So you would (for example) always have a $teamName and a $sector - and you might only sometimes use $anotherSector. And you have these two strings:
1 - $teamName, is the name of a recently formed company hoping to take over the lucrative $sector.
2 - The people at $teamName hate working in $sector, they would much rather work in $anotherSector
If you were to do:
$replaces = array(
'$teamName' => 'Bob the team',
'$sector' => 'murdering',
'$anotherSector' => 'giving fluffy bunnies to children'
);
$news = str_replace(array_keys($replaces),array_values($replaces),$news);
You would get
1 - Bob the team, is the name of a recently formed company hoping to take over the lucrative murdering.
2 - The people at Bob the team hate working in murdering, they would much rather work in giving fluffy bunnies to children
As long as your placeholders have known names, they don't all have to be present in the string - only the relevant ones will be replaced.
You could create a simple template language, and store templates in your database.
You can use strtr for this.
function replaceTemplateVars($str, $data) {
// change the key format to correspond to the template replacement format
$replacepairs = array();
foreach($data as $key => $value) {
$replacepairs["{{{$key}}}"] = $value;
}
// do the replacement in bulk
return strtr($str, $replacepairs);
}
// store your teamNews table text in this format
// double curly braces is easier to spot and less ambiguous to parse than `$name`.
$exampletemplate = '{{teamName}} is {{sector}} the {{otherteam}}!!'
// get $values out of your database for the user
$values = array(
'teamName' => 'Bob the team',
'sector' => 'murdering',
'otherteam' => 'fluffy bunnies'
);
echo replaceTemplateVars($exampletemplate, $values);
// this will echo "Bob the team is murdering the fluffy bunnies!!"
If you have needs more ambitious than this, such as looping or filters, you should find a third-party php template language and use it.
What about function eval?
http://php.net/eval

MySQL/PHP/JS Dynamically Populated Drop Down Menu

I'm working on a DB/web based frontend, and have encountered an issue. First off, I have a form with a drop down menu containing a list of contracts. Upon selection of a contract, I'd like for the jobs associated with that contract (fetched from the MySQL DB) to populate a second drop down menu below the first.
I would have just had all the info in one menu, but an 8000 entry drop down menu is a little unwieldy.
My PHP and HTML are barely passable, but enough for my purposes, however my ECMA experience is limited to a little bit of ActionScript in Flash MX, many moons ago.
I'd like to avoid using third party JS libraries (such as jQuery) if at all possible, and I don't mind writing more code. I just need to know whether this is doable, and a little shove in the right direction.
I'll shut up now, heres the form to fetch the contract ID (and the associated client), and the incomplete job menu.
<select name='idcontract' onchange=''>
<!--fetch/display contracts/clients-->
<?php
include 'sqldb.php';
$cntqres = mysqli_query($dbc, 'SELECT * FROM contract');
while ($cntrow = mysqli_fetch_array($cntqres))
{
$cliqres = mysqli_query($dbc, "SELECT * FROM client WHERE idclient = '$cntrow[idclient]'");
while ($clirow = mysqli_fetch_array($cliqres))
{
echo "<option value='$cntrow[idcontract]'>$cntrow[idcontract] $clirow[name]</option>";
}
}
?>
</select>
<select name='idjob'>
<option value='NULL'>Please select a contract</option>
<!--here goes the magical piece of code I don't know how to write-->
</select>
Any help would be much appreciated.
Edit:
Here's the PHP called by FeatherAJAX:
<?php
include 'sqldb.php';
$cnt = mysqli_real_escape_string($dbc, $_GET['cnt']);
$sql = "SELECT * FROM job WHERE idcontract='$cnt' ORDER BY job.idjob";
$jqres = mysqli_query($dbc, $sql);
$i = 1;
while (($jrow = mysqli_fetch_array($jqres)) && ($i < count($jrow)))
{
echo "idjob=><option value='$jrow[idjob]' id='$jrow[idjob]'>Job-$i $jrow[part_desc]</option>";
$i++;
}
?>
First off, you may want to rewrite the chunk of code that produces the contract options. Looping through query results and performing another query for each record is inefficient. Based on your queries, you might be able to use this code, which does a single query and then generate the options based on that. (I had to use made-up column names in the ORDER clause. In general, you should always sort your recordset so that results are in a determinate order -- even if you don't care what that order is.
<select name="idcontract" id="idcontract">
<!--fetch/display contracts/clients-->
<?php
include 'sqldb.php';
$clients = mysqli_query($dbc, '
SELECT ct.idcontract, ct.idclient, cl.name
FROM contract ct LEFT OUTER JOIN client cl ON ct.idclient = cl.idclient
ORDER BY ct.contractname, cl.clientname
');
while ($client = mysqli_fetch_array($clients)) {
echo "<option value=\"{$client[idcontract]}\">{$client[idcontract} {$client[name]}</option>";
}
?>
</select>
<select name="idjob" id="idjob">
<option value="NULL">Please select a contract</option>
</select>
To your question, the code you're looking for actually doesn't go where that comment is. What you need is an event handler that responds to the user picking an option in the first SELECT; it should then grab the value of that option and request from the server a set of key-value pairs to stuff into the second SELECT.
Something like this:
document.getElementById('idcontract').onchange = function(event) {
// grab currently selected value
var sValue = null;
for(var i = 0, imax = this.childNodes.length; i < imax; i++) {
var eOption = this.childNodes[i]; // shorthand
if(eOption.selected) {
sValue = eOption.value;
break;
}
}
if(!sValue) return;
// get the sub-options for this value
getSubOptions(sValue, function(XHR) {
// this code runs once the response comes back from the server
var aPairs = [];
var nlJobs = XHR.getElementsByTagName('jobs'); // assumptions #1 & #2: response is XML, includes <job> tag for each job
// extract key-value pairs from XML
for(var i = 0, imax = nlJobs.length; i < imax; i++) {
var xJob = nlJobs[i]; // shorthand
/*
assumption #3: <job> tag has "id" property
assumption #4: job name appears inside <job> tag
assumption #4.5: you've got an abstraction layer that normalizes XML node interfaces so that "text" and "textContent" are folded into "textContent"
*/
aPairs.push({ 'key': xJob.getAttribute('id'), 'value': xJob.textContent });
}
// given array of key-value pairs, rebuild select box
var eJobs = document.getElementById('idjob');
setOptions(eJobs, aPairs);
});
}
function setOptions(eNode, aPairs) {
if(!eNode || !eNode.nodeName || eNode.nodeName.toUpperCase() !== 'SELECT') return false;
// empty SELECT of all options
while(eNode.firstChild) {
eNode.removeChild(eNode.firstChild);
}
// build up new nodes
var eOpt = null;
for(var i = 0, imax = aPairs.length; i < imax; i++) {
eOpt = document.createElement('OPTION');
eOpt.value = aPairs[i].key;
eOpt.appendChild(document.createTextNode(aPairs[i].value));
eNode.appendChild(eOpt);
}
return true;
}
Of course, this is missing an important piece: you need some kind of AJAX abstraction layer. You don't need to get that from a framework, and a good library for this can be less than 50 lines of code (e.g. see PPK's ajax script on quirksmode.org), but you absolutely need something. That layer will provide two benefits: (1) cross-browser compatibility; (2) syntactic sugar.
For example, the code above doesn't include the definition of getSubOptions. That's because the logic will vary based on the interface provided by your AJAX abstraction. The idea, though, is that you'll perform a GET request against a script you write that accepts arguments and returns data satisfying that request. In the code above, I pretended that the script you write will return properly-formed XML data, with a MIME type identifying it as such. Alternatively, you could use JSON (or JSONP), straight text (e.g. CSV-style data), or even raw HTML that you'll just insert into the page.
The benefit of using a full framework is that they all provide convenient ways of doing DOM manipulation (i.e. syntactic sugar again).
The bottom line: you can absolutely do this with a homegrown approach (and I'm proud to say I've done it myself). But it will take longer -- not just because it's less convenient, but also because you'll have to re-invent the wheel === finding and fixing bugs in your code instead of leveraging well-tested core components from some library.
EDIT: If you want to use JSON as a data interchange format instead of XML, you'd modify the response handler being passed to getSubOptions like so:
getSubOptions(sValue, function(XHR) {
// this code runs once the response comes back from the server
var aPairs = eval(XHR.responseText); // assumes JSON defines an array of key-value pairs
// given array of key-value pairs, rebuild select box
var eJobs = document.getElementById('idjob');
setOptions(eJobs, aPairs);
});
And here's a sample of what that JSON might look like:
[ { key: '1234', value: 'Job #1' },
{ key: '2345', value: 'Job #2' },
...
];
In this example, the JSON structure conveniently mirrors the property names expected by setOptions; that said, key and value seem pretty inoffensive.
If you're set on using JSON for data, you may want to look into JSONP as a more secure alternative. It's real similar, but the design pattern is a little different from the anonymous callback technique above.
EDIT 2: Modified sample code for the responder:
<?php
include 'sqldb.php';
$cnt = mysqli_real_escape_string($dbc, $_GET['cnt']);
$sql = "SELECT * FROM job WHERE idcontract='$cnt' ORDER BY job.idjob";
$jqres = mysqli_query($dbc, $sql);
$i = 1;
// prepare the response
header('Content-Type: text/html');
while (($jrow = mysqli_fetch_array($jqres)) && ($i < count($jrow))) {
echo "<option value=\"$jrow[idjob]\" id=\"$jrow[idjob]\">Job-$i ${htmlentities(jrow[part_desc])}</option>";
$i++;
}
?>

PHP/mysql array search algorithm

I'd like to be able to use php search an array (or better yet, a column of a mysql table) for a particular string. However, my goal is for it to return the string it finds and the number of matching characters (in the right order) or some other way to see how reasonable the search results are, so then I can make use of that info to decide if I want to display the top result by default or give the user options of the top few.
I know I can do something like
$citysearch = mysql_query(" SELECT city FROM $table WHERE city LIKE '$city' ");
but I can't figure out a way to determine how accurate it is.
The goal would be:
a) find "Milwaukee" if the search term were "milwakee" or something similar.
b) if the search term were "west", return things like "West Bend" and "Westmont".
Anyone know a good way to do this?
You should check out full text searching in MySQL. Also check out Zend's port of the Apache Lucene project, Zend_Search_Lucene.
More searching led me to the Levenshtein distance and then to similar_text, which proved to be the best way to do this.
similar_text("input string", "match against this", $pct_accuracy);
compares the strings and then saves the accuracy as a variable. The Levenshtein distance determines how many delete, insert, or replace functions on a single character it would need to do to get from one string to the other, with an allowance for weighting each function differently (eg. you can make it cost more to replace a character than to delete a character). It's apparently faster but less accurate than similar_text. Other posts I've read elsewhere have mentioned that for strings of fewer than 10000 characters, there's no functional difference in speed.
I ended up using a modified version of something I found to make it work. This ends up saving the top 3 results (except in the case of an exact match).
$input = $_POST["searchcity"];
$accuracy = 0;
$runner1acc = 0;
$runner2acc = 0;
while ($cityarr = mysql_fetch_row($allcities)) {
$cityname = $cityarr[1];
$cityid = $cityarr[0];
$city = strtolower($cityname);
$diff = similar_text($input, $city, $tempacc);
// check for an exact match
if ($tempacc == '100') {
// closest word is this one (exact match)
$closest = $cityname;
$closestid = $cityid;
$accuracy = 100;
break;
}
if ($tempacc >= $accuracy) { // more accurate than current leader
$runner2 = $runner1;
$runner2id = $runner1id;
$runner2acc = $runner1acc;
$runner1 = $closest;
$runner1id = $closestid;
$runner1acc = $accuracy;
$closest = $cityname;
$closestid = $cityid;
$accuracy = $tempacc;
}
if (($tempacc < $accuracy)&&($tempacc >= $runner1acc)) { // new 2nd place
$runner2 = $runner1;
$runner2id = $runner1id;
$runner2acc = $runner1acc;
$runner1 = $cityname;
$runner1id = $cityid;
$runner1acc = $tempacc;
}
if (($tempacc < $runner1acc)&&($tempacc >= $runner2acc)) { // new 3rd place
$runner2 = $cityname;
$runner2id = $cityid;
$runner2acc = $tempacc;
}
}
echo "Input word: $input\n<BR>";
if ($accuracy == 100) {
echo "Exact match found: $closestid $closest\n";
} elseif ($accuracy > 70) { // for high accuracies, assumes that it's correct
echo "We think you meant $closestid $closest ($accuracy)\n";
} else {
echo "Did you mean:<BR>";
echo "$closestid $closest? ($accuracy)<BR>\n";
echo "$runner1id $runner1 ($runner1acc)<BR>\n";
echo "$runner2id $runner2 ($runner2acc)<BR>\n";
}
This can be very complicated, and I am not personally aware of any good 3rd party libraries although I'm sure they exist. Others may be able to suggest some canned solutions, though.
I have written something similar from scratch a few times in the past. If you go down that route, it is probably not something you'd want to do in PHP by itself as every query would involve getting all of the records and performing your calculations on them. It will almost certainly involve creating a set of index tables that meet your specifications.
For instance, you would have to come up with rules for how you imagine that "Milwaukee" could end up spelled "milwakee." My solution to this was to do vowel compression and duplication compression (not sure if these are actually search terms). So, milwaukee would be indexed as:
milwaukee
m_lw__k__
m_lw_k_
When the search query came in for "milwaukee", I would run the same process on the text input, and then run a search on the index table for:
SELECT cityId,
COUNT(*)
FROM myCityIndexTable
WHERE term IN ('milwaukee', 'm_lw__k__', 'm_lw_k_')
When the search query came in for "milwakee", I would run the same process on the text input, and then run a search on the index table for:
SELECT cityId,
COUNT(*)
FROM myCityIndexTable
WHERE term IN ('milwaukee', 'm_lw_k__', 'm_lw_k_')
In the case of Milwaukee (spelled correctly), it would return "3" for the count.
In the case of Milwakee (spelled incorrectly) ,it would return "2" for the count (since it would not match the m_lw__k__ pattern as it only had one vowel in the middle).
If you sort the results based on the count, you would end up meeting one of your rules, that "Milwaukee" would end up being sorted higher as a possible match than "Milwakee."
If you want to build this system in a generic way (as hinted by your use of $table in the query) then you'd probably need another mapping table somewhere in there to map your terms to the appropriate table.
I'm not suggesting this is the best (or even a good) way to go about this, just something I've done in the past that might prove useful to you if you plan to try and do this without a third party solution.
Most maddening result with LIKE is this one "%man" this will return all woman in file!
In case of listing perhaps a not too bad solution is to keep on shortening the searching needle. In your case a match will come up when your searching $ is as short as "milwa".

Categories