Monday, August 26, 2013

Highlight 'em all

So it has been a while now that I switched from marking to mapping.
The new approach has it's ups and downs of course, the ups are that it is crazy and it works, the downs are... everything else. Lucky for me, everything else is fixable :D

First of all I want to point out this thing: http://beta.phpformatter.com/ it is the best formatter I found out there, has just enough options, and makes the code much more readable. If you have a lot of nested conditional statements or loops it is a life saver as it adds comments after each } on what the conditional is.

Saving the original

I have, in detail, explained the saving of the original phrases here. The previous approach worked, but had a few bugs or rather it relied on certain formatting paradigms. I have updated this so now it uses actual values instead of file manipulation.
The trade-of here is that the plugin can not be installed while Geeklog is installing. Since the chances that this plugin will ever ship with Geeklog this is not a big problem.


<?php
define( 'XHTML', '" . XHTML . "' );
require_once $_CONF[ 'path_html' ] . "lib-common.php";
require_once $_CONF[ 'path_system' ] . 'lib-database.php';
global $_CONF;
$real = array( );
$real[ 'site_url' ] = $_CONF[ 'site_url' ];
$real[ 'commentspeedlimit' ] = $_CONF[ 'commentspeedlimit' ];
$real[ 'site_name' ] = $_CONF[ 'site_name' ];
$real[ 'speedlimit' ] = $_CONF[ 'speedlimit' ];
$real[ 'site_admin_url' ] = $_CONF[ 'site_admin_url' ];
$real[ 'mysqldump_path' ] = $_DB_mysqldump_path;
$real[ 'backup_path' ] = $_CONF[ 'backup_path' ];
$real[ 'username' ] = $_USER[ 'username' ];
$GLOBALS[ 'real_values' ] = $real;
$_CONF[ 'site_url' ] = "{\$_CONF['site_url']}";
$_CONF[ 'commentspeedlimit' ] = "{\$_CONF['commentspeedlimit']}";
$_CONF[ 'site_name' ] = "{\$_CONF['site_name']}";
$_CONF[ 'speedlimit' ] = "{\$_CONF['speedlimit']}";
$_CONF[ 'site_admin_url' ] = "{\$_CONF['site_admin_url']}";
$_DB_mysqldump_path = "{\$_DB_mysqldump_path}";
$_CONF[ 'backup_path' ] = "{\$_CONF['backup_path']}";
$_USER[ 'username' ] = "{\$_USER['username']}";
require_once $_CONF[ 'path' ] . "language/english_utf-8.php";
function add_identifier_to_lanugage_file( )
{
global $_TABLES, $_CONF;
$language_array_names = get_language_array_names();
$db_entries = array( );
$current_object = array( );
foreach ( $language_array_names as $key => $value ) {
if ( strpos( $value, "['" ) !== false ) {
$array = substr( $value, 0, strpos( $value, "['" ) );
$index = substr( $value, strpos( $value, "['" ) + 2, strpos( $value, "']" ) - strpos( $value, "['" ) - 2 );
$current_array = $GLOBALS[ $array ][ $index ];
} //strpos( $value, "['" ) !== false
else {
$current_array = $GLOBALS[ $value ];
$array = $value;
$index = '';
}
if ( $index != '' && !empty( $index ) ) {
$current_object[ 'array' ] = "{$array}";
$current_object[ 'index' ] = "{$index}";
foreach ( $current_array as $key2 => $value2 ) {
if ( is_array( $value2 ) ) {
foreach ( $value2 as $key_sub => $value_sub ) {
//create a object for the array element
$current_object[ 'line' ] = $value_sub;
//this will host the html and php tags
$current_object[ 'tags' ] = array( );
$current_object[ 'line' ] = remove_tags( $current_object[ 'line' ], $current_object[ 'tags' ] );
//encode tags for saving to db
$current_object[ 'tags' ] = json_encode( $current_object[ 'tags' ] );
$current_object[ 'sub_index' ] = $key_sub;
array_push( $db_entries, $current_object );
} //$value2 as $key_sub => $value_sub
} //is_array( $value2 )
else {
$current_object[ 'line' ] = $value2;
//this will host the html and php tags
$current_object[ 'tags' ] = array( );
$current_object[ 'line' ] = remove_tags( $current_object[ 'line' ], $current_object[ 'tags' ] );
//encode tags for saving to db
$current_object[ 'tags' ] = json_encode( $current_object[ 'tags' ] );
$current_object[ 'sub_index' ] = $key2;
array_push( $db_entries, $current_object );
}
} //$current_array as $key2 => $value2
} //$index != '' && !empty( $index )
else {
$current_object[ 'array' ] = "{$array}";
foreach ( $current_array as $key2 => $value2 ) {
if ( !is_array( $value2 ) ) {
//create a object for the array element
$current_object[ 'line' ] = $value2;
//this will host the html and php tags
$current_object[ 'tags' ] = array( );
$current_object[ 'line' ] = remove_tags( $current_object[ 'line' ], $current_object[ 'tags' ] );
//encode tags for saving to db
$current_object[ 'tags' ] = json_encode( $current_object[ 'tags' ] );
$current_object[ 'index' ] = $key2;
$current_object[ 'sub_index' ] = -1;
array_push( $db_entries, $current_object );
} //!is_array( $value2 )
} //$current_array as $key2 => $value2
}
} //$language_array_names as $key => $value
//save the edited arrays to the database
foreach ( $db_entries as $key => $value ) {
$value[ 'index' ] = DB_escapeString( $value[ 'index' ] );
$value[ 'sub_index' ] = DB_escapeString( $value[ 'sub_index' ] );
$value[ 'line' ] = DB_escapeString( $value[ 'line' ] );
$value[ 'tags' ] = DB_escapeString( $value[ 'tags' ] );
$query = "INSERT INTO {$_TABLES['originals']} (`id`, `language`, `plugin_name`, `language_array`, `array_index`, `sub_index`, `string`, `tags`)
VALUES ('', '{$_CONF['language']}', 'core', '{$value['array']}', '{$value['index']}' , '{$value['sub_index']}' , '{$value['line']}', '{$value['tags']}' ) ";
DB_query( $query );
} //$db_entries as $key => $value
}
?>
The trick is that the LANG array names are still parsed from the language file, however the variables used (I have checked and additional variables are not likely to be added) are redefined , instead of the real value the variable gets the variable name e.g.
$_CONF[ 'site_url' ] = "{\$_CONF['site_url']}";

So when the value is parsed I still get what I need. In order not to render the page useless (at least before a reload) the original variable values have to be returned.

<?php
function language_maper( )
{
...
add_identifier_to_lanugage_file();
global $_CONF;
$real = $GLOBALS[ 'real_values' ];
$_CONF[ 'site_url' ] = $real[ 'site_url' ];
$_CONF[ 'commentspeedlimit' ] = $real[ 'commentspeedlimit' ];
$_CONF[ 'site_name' ] = $real[ 'site_name' ];
$_CONF[ 'speedlimit' ] = $real[ 'speedlimit' ];
$_CONF[ 'site_admin_url' ] = $real[ 'site_admin_url' ];
$_DB_mysqldump_path = $real[ 'mysqldump_path' ];
$_CONF[ 'backup_path' ] = $real[ 'backup_path' ];
$_USER[ 'username' ] = $real[ 'username' ];
}
?>
view raw mapper.php hosted with ❤ by GitHub
The main reason I re-implemented this (so close to deadline) is because I worked on the packing of the translations. The same logic will be used there, but with a small intermediate step - if there is a translation for a certain phrase it is retrieved from the database and used, otherwise the English phrase is used.



Highlighting


The biggest problem with the new approach was highlighting the string on the page. Of course for phrases such as "Directory" the implementation could be trivial. However Geeklog phrases are not that simple, there are variables, there is HTML and so on and so forth.




I have tried a lot of implementations for this, and they all failed. Until I realized that the perfect implementation was something which already exists. It is naive , it will highlight "Contributed" when searching for "Contribute" but it is the best implementation I could use.


function doSearch(text, value)
{
if (window.find && window.getSelection) {
document.designMode = "on";
var sel = window.getSelection();
sel.collapse(document.body, 0);
while (window.find(text)) {
document.execCommand("backcolor", false, "yellow");
sel.collapseToEnd();
}
document.designMode = "off";
} else if (document.body.createTextRange) {
var textRange = document.body.createTextRange();
while (textRange.findText(text)) {
textRange.execCommand("backcolor", false, value);
textRange.collapse(false);
}
while (textRange.findText(text)) {
textRange.execCommand("hiliteColor", false, value);
textRange.collapse(false);
}
}
}
function prep_doSearch( text, value )
{
text = text.replace( /&lttag&gt/g, "" );
text = text.replace( /&ltvar&gt/g, "" );
text = text.replace(/\s+/g, " ");
doSearch( text, value );
console.log(text);
}
/**
* adds CSS class to highligh selected string(s) on page
*/
function highlight(id)
{
prep_doSearch(language_strings[id].string, "yellow");
}
/**
* removes CSS class of highlighted string(s) on page
*/
function remove_highlight(id)
{
prep_doSearch(language_strings[id].string, "inherit");
}
view raw doSearch.js hosted with ❤ by GitHub
It relies on execCommand which will flatten any HTML between string parts, all I had to do was to make sure that the search string has no <var> or <tag> tags the plugin uses, which was easy enough to do.

Finally the plugin is getting where it need to be, by week end I hope to have finished the packing functionality and therefore completing a minimalistic implementation. After that, there is a list of things I was asked to added to increase the usability of the plugin.


Cheers