=====Compatibility Code===== >>See also: ~-WikkaCore ~-AdvancedFormatter ~-AdvancedCategoryAction >>**Compatibility code** is code that serves to "hide" differences in coding to accommodate different versions, such as different PHP versions or different MySQL versions. By providing compatibility code with a public interface we can maintain a clean and simple API while dealing with version differences automatically.::c:: ====Different types of compatibility code==== Compatibility code can be classified into three kinds, depending on what kind of differences we are hiding; the aim is always to hide the complexity of working around differences from calling code that needs the functionality: ===Classification=== ==Missing functions== ~-Code to compensate for PHP functions that may be missing in older versions; this allows (almost) equivalent functionality when Wikka is running on an older PHP version ~-Code to compensate for PHP MySQL functions that may be be available only for a newer MySQL version than is installed; this allows for (almost) equivalent functionality when Wikka is using an older MySQL version ==Hiding decision logic== ~-Code that decides which of two possible functions or methods to call depending on PHP or MySQL version installed; by wrapping such decision logic in a single method, the version-dependent decision logic can be hidden with single API ==Configuration== ~-Code that compensates for differences in PHP configuration that may not be under the control of the WikiAdmin or may not be to changed in order not to break other PHP applications. ====Compatibility code in Wikka 1.1.6.0==== Wikka 1.1.6.0 already has some compatibility code in place, but it could do better... See below for some (proposed) new additions. ===##mysql_real_escape_string()##=== ==Classification== Missing function. ==Code== The function ##mysql_real_escape_string()## is available in PHP as of version 4.3; Wikka specifies PHP 4.1.0 as minimum PHP version so we need to provide a (near) equivalent for this. The code is found near the start of ##wikka.php##: %%(php;50)if ( ! function_exists("mysql_real_escape_string") ) { function mysql_real_escape_string($string) { return mysql_escape_string($string); } } %% This clearly shows the basic **pattern** for "missing function" compatibility code: ~-check whether a function of that name exists ~-if not, define a function with the same name and as much as possible the same interface ~-in the function body imitate as much as possible the functionality of the missing function This ensures that the intended function can be called as normal: if the function does **not** exist in the current installation, the compatibility code will kick in. //Note that ##mysql_escape_string()## is not **exactly** equivalent with ##mysql_real_escape_string()##; possibly the code here could be extended a bit to provide better compaitibilty.// ===##Magic quotes##=== ==Classification== Configuration ==Code== PHP may be configured to use "magic quotes". When magic_quotes are on, all ' (single-quote), " (double quote), \ (backslash) and NUL's are escaped with a backslash automatically for GPC (Get/Post/Cookie) operations. To avoid having to check for these backslashes in every string we get via cookies or POST and GET requests the following code turns off magic quotes and removes them where found, so any further code does not have to deal with them. The code is (currently) found near the end of ##wikka.php##: %%(php;1029)// workaround for the amazingly annoying magic quotes. function magicQuotesSuck(&$a) { if (is_array($a)) { foreach ($a as $k => $v) { if (is_array($v)) magicQuotesSuck($a[$k]); else $a[$k] = stripslashes($v); } } } set_magic_quotes_runtime(0); if (get_magic_quotes_gpc()) { magicQuotesSuck($_POST); magicQuotesSuck($_GET); magicQuotesSuck($_COOKIE); } %%--- In a future version this would be better placed together with other compatibility code near the start and maybe get a more "polite" name. :) ====Proposed new compatibility code==== This is compatibility code needed for or used used by new code development presented on this site. ===##html_entity_decode()##=== //Installed to support a [[WikkaBetaFeatures | beta feature]] on this server as of 2005-06-12.// ==Classification== Missing function. ==Code== The function ##html_entity_decode()## is available in PHP as of version 4.3. The [[AdvancedFormatter | "advanced" formatter]] makes use of it when generating ids for headings. The following code provides a //near// equivalent. Insert in ##wikka.php## immediately after the **##mysql_real_escape_string()##** compatibility code shown above: %%(php;1)if (!function_exists('html_entity_decode')) { // based on http://php.net/html-entity-decode.php // third parameter (charset) ignored: only (default) ISO-8859-1 character set supported function html_entity_decode($string,$quote_style=ENT_COMPAT) { $trans_tbl = get_html_translation_table(HTML_ENTITIES,$quote_style); $trans_tbl = array_flip($trans_tbl); return strtr($string, $trans_tbl); } } %% Note that it is not completely equivalent: ##html_entity_decode()## in PHP 4.3+ provides support for a range of character sets (including UTF-8); the character set can be specified in a third parameter. The function ##get_html_translation_table()## we use to provide compatibility only uses the default charset ISO-8859-1; when this code is used in versiosn of PHP lower than 4.3 any third parameter will be ignored. //Note that for the case this was written (generating ids for headings) we actually **need** ISO-8859-1, so for that application lack of equivalence it is not a problem; used in another context it may be and one should be aware of the limitations.// ===##getCatMembers()##=== //Installed to support a [[WikkaBetaFeatures | beta feature]] on this server as of 2005-06-12 (including the **efficiency** change).// ==Classification== Hiding decision logic. ==Code== The current (version 1.1.6.0) [[Docs:CategoryActionInfo | category action]] uses the following bit of code to gather the the members of a category: **##./actions/category.php##** %%(php;18) if ($this->CheckMySQLVersion(4,0,1)) { $results = $this->FullCategoryTextSearch($page); } else { $results = $this->FullTextSearch($page); } %% This type of decision logic doesn't belong in an action (or a handler for that matter): if we'd decide to support a different set of versions of PHP and MySQL we'd have to trail through all of our code to find ths kind of decision logic. Instead, there should be a single method call to a method that hides the decision process about what function to use when. So for the [[AdvancedCategoryAction | advanced category action]] this code was spirited away in a new core function **##getCatMembers()##** - insert this right after ##""FullCategoryTextSearch()""## in ##wikka.php## at line 438: %%(php;1) /** * Find category members, hiding version-dependent implementation from caller. * * @uses CheckMySQLVersion() * @uses FullCategoryTextSearch() * @uses FullTextSearch() * * @todo - replace FullTextSearch() with a query that returns only tag * - replace CheckMySQLVersion() with PHP's generiic function * * @param string $category required: category to find members of. * @return array rows with page data (for pages that refer to the category name) */ function getCatMembers($category) { if ($this->CheckMySQLVersion(4,0,1)) { return $this->FullCategoryTextSearch($category); } else { return $this->FullTextSearch($category); # @@@ could be more efficient by returning only 'tag' } } %%--- This not only hides //which// function to call, but also //how// that decision is made. In fact, it's now completely hidden how category members are gathered at all: if we'd install a different system such as a categories table, the //content// of the method would change to query that table, but the category action would not need to be changed at all. ==Efficiency== The method ##""FullCategoryTextSearch()""## is (indirectly now via ##""getCatMembers()""##) used //only// by the category action. All the action needs to work with is **page names** - but if we look at the query used by the action we see it's actually retrieving all columns (*), including the whole page body (line 438 in the release version): %%(php) function FullCategoryTextSearch($phrase) { return $this->LoadAll("select * from ".$this->config["table_prefix"]."pages where latest = 'Y' and match(body) against('".mysql_real_escape_string($phrase)."' IN BOOLEAN MODE)"); }%% It's only a trivial change to get only the page name ('tag'): %%(php) function FullCategoryTextSearch($phrase) { return $this->LoadAll("SELECT tag FROM ".$this->config["table_prefix"]."pages WHERE latest = 'Y' AND MATCH(body) AGAINST('".mysql_real_escape_string($phrase)."' IN BOOLEAN MODE)"); }%% ''Looking at ##""FullTextSearch()""##, this is not only used (indirectly) by the category action but also the **textsearch** and **textsearchexpanded** actions. This method also asks for all columns (*) but only the **textsearchexpanded** action actually uses the 'body' field as well as the 'tag' field. This could be made more efficient (for the **textsearch** action at least) by adding an extra parameter to the method to tell it which field(s) to retrieve. However, that would also involve a change in at least one of those actions; that is outside the scope of this page.'' Refer to WikkaOptimization for more optimization issues. ---- CategoryDevelopmentCore