Advertisement
bainternet

a-z

Aug 4th, 2011
79
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 62.35 KB | None | 0 0
  1. <?php
  2. /*  Copyright 2008  Michael J. Walker  (email : azindex@englishmike.net)
  3.  
  4.     This program is free software; you can redistribute it and/or modify
  5.     it under the terms of the GNU General Public License as published by
  6.     the Free Software Foundation; either version 2 of the License, or
  7.     (at your option) any later version.
  8.  
  9.     This program is distributed in the hope that it will be useful,
  10.     but WITHOUT ANY WARRANTY; without even the implied warranty of
  11.     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12.     GNU General Public License for more details.
  13.  
  14.     You should have received a copy of the GNU General Public License
  15.     along with this program; if not, write to the Free Software
  16.     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  17. */
  18. add_action('publish_page', 'az_cache_post_changed');
  19. add_action('publish_post', 'az_cache_post_changed');
  20. add_action('save_post', 'az_cache_post_changed');
  21. add_action('delete_post', 'az_pending_delete');
  22. add_action('deleted_post', 'az_cache_post_changed');
  23. add_action('wp_update_post', 'az_cache_post_changed');
  24. add_action('wp_insert_post', 'az_cache_post_changed');
  25. add_action('check_ajax_referer', 'az_cache_check_custom');
  26.  
  27. //add_action('wp_ajax_addmeta', 'az_cache_check');
  28. //add_action('edit_post', 'az_cache_check');
  29. //add_action('init', 'az_cache_check');
  30.  
  31. /**
  32.  * Creates a new cached object for the specified index.
  33.  *
  34.  * @param az_request $index the current index
  35.  * @param int $pageid current page id displaying index (can be a post)
  36.  * @param int $pageno current page number of index being displayed
  37.  * @return az_index_cache cached information for current index
  38.  */
  39. function az_cache_get($index, $pageid, $pageno) {
  40.     return new az_index_cache($index, $pageid, $pageno);
  41. }
  42.  
  43. /**
  44.  * Hook into the delete_post action so that we can get the
  45.  * details of the post to weed out the drafts and autosaves.
  46.  *
  47.  * @param int $postid
  48.  */
  49. function az_pending_delete($postid) {
  50.     global $az_deleted_post_type;
  51.     $post = get_post($postid);
  52.     $az_deleted_post_type[$postid] = $post->post_type;
  53.     //az_trace("az_pending_delete : ".(empty($post) ? "null" : "$post"));
  54. }
  55.  
  56. /**
  57.  * Main entry point for processing changed posts and pages.  Hooks into
  58.  * functions that modify posts and pages in some way call here so that
  59.  * the posts can be added to the dirty list of all the indexes.  Note
  60.  * that we have to filter out changes to revisions by checking the post_type/
  61.  *
  62.  * @param int $postid id of post changed
  63.  */
  64. function az_cache_post_changed($postid) {
  65.     global $az_deleted_post_type;
  66.     if (current_filter() == 'deleted_post') {
  67.         $post_type = $az_deleted_post_type[$postid];
  68.     } else {
  69.         $post = get_post($postid);
  70.         $post_type = $post->post_type;
  71.     }
  72.     //az_trace("HOOK CALLED az_cache_post_changed : $post_type : ".current_filter());
  73.     if ($post_type == 'post' || $post_type == 'page') {
  74.         //az_trace("-----------------------------------------------");
  75.         //az_trace("az_cache_post_changed:".current_filter()." - postid = $postid");
  76.         //az_trace("az_cache_post_changed: - post_type = $post_type");
  77.         az_add_dirty_post($postid);
  78.     }
  79. }
  80.  
  81. /**
  82.  * There is no easy way to capture changes to custom fields that are done via AJAX calls.
  83.  * This callback is called *before* any changes are made, so while we can log that a
  84.  * change has been made (which we do) there is no way to find out what the change is
  85.  * and whether it affects the index or not.  There is no consistency in the way the
  86.  * postid is passed on the AJAX call, which is the reason for the convoluted code.
  87.  *
  88.  * @param object $action - not used
  89.  */
  90. function az_cache_check_custom($action) {
  91.     global $wpdb;
  92.     //az_trace("az_cache_check:".current_filter()." - action = $action");
  93.     $action = $_REQUEST['action'];
  94.     if ($action == 'add-meta') {
  95.         $postid = $_REQUEST['post_id'];
  96.         if (empty($postid)) {
  97.             $meta = $_REQUEST['meta'];
  98.             $idmeta = current(array_keys($meta));
  99.         }
  100.     } else if ($action == 'delete-meta') {
  101.         $idmeta = $_REQUEST['id'];
  102.     }
  103.    
  104.     if (!empty($idmeta)) {
  105.         $postid = $wpdb->get_var("SELECT post_id FROM $wpdb->postmeta WHERE meta_id = '$idmeta'");
  106.     }
  107.    
  108.     if (!empty($postid)) {
  109.         $post = get_post($postid);
  110.         if ($post->post_type == 'post' || $post->post_type == 'page') {
  111.             //az_trace("-----------------------------------------------");
  112.             //az_trace("az_cache_check_custom: custom field modified - postid: $postid");
  113.             //az_trace("az_cache_check_custom: - post_type = $post->post_type");
  114.             az_add_dirty_post($postid);
  115.         }
  116.     }
  117. }
  118.  
  119. /**
  120.  * Fetch the list of dirty posts -- and only unserialize them if
  121.  * they haven't been unserialized already.
  122.  *
  123.  * @return array of dirty posts.
  124.  */
  125. function az_get_dirty_posts() {
  126.     //az_trace('az_get_dirty_posts');
  127.     $dirtyposts = get_option("az_cache_dirty");
  128.     if (is_serialized($dirtyposts)) {
  129.         $dirtyposts = unserialize($dirtyposts);
  130.     }
  131.     return $dirtyposts;
  132. }
  133.  
  134. function az_set_dirty_posts($dirtyposts) {
  135.     update_option("az_cache_dirty", serialize($dirtyposts));
  136.     //az_trace("az_set_dirty_posts: saving dirtyposts:");
  137. }
  138.  
  139. function az_add_dirty_post($postid) {
  140.     az_acquire_cache_mutex();
  141.     $dirtyposts = az_get_dirty_posts();
  142.     // Only process the dirty post if its the same post as before if
  143.     // a quarter of a second or more has elapse, otherwise we will
  144.     // process the same changes multiple time.
  145.     $elapsed = az_microtime_diff($dirtyposts[0][1]);
  146.     $elapsed = ($postid == $dirtyposts[0][0]) ? $elapsed : 1;
  147.     //az_trace("elapsed time : ".$elapsed);
  148.     if ($elapsed > 0.25) {
  149.         //az_trace("******************************************************");
  150.         $indexes = az_get_indexes("*");
  151.         // Add the dirty post to all the indexes since we have no idea which
  152.         // posts are displayed in which indexes.
  153.         foreach ($indexes as $index) {
  154.             // Add the post to the dirty list if necessary.
  155.             if (!(is_array($dirtyposts[$index->idindex]) && in_array(intval($postid), $dirtyposts[$index->idindex]))
  156.                                                          && !az_is_set($index->options, "disable-cache")) {
  157.                 //az_trace("az_add_dirty_post: adding postid $postid to the dirty list of $index->idindex");
  158.                 $dirtyposts[$index->idindex][] = intval($postid);
  159.             }
  160.             // The blog may be using an HTML cache (like WP-Super-Cache), so we
  161.             // have to check immediately if we need to invalidate the HTML cache
  162.             // since we won't be able to when the cached HTML of the index itself is accessed.
  163.             $dirtyposts = az_process_dirty_post($index, $postid, $dirtyposts);
  164.             //az_trace("dirty: id = ".$index->idindex.": ".(is_array($dirtyposts[$index->idindex]) ? implode(",", $dirtyposts[$index->idindex]) : 'empty'));
  165.         }
  166.         //az_dump_dirty($dirtyposts);
  167.     }
  168.     // Save the postid and timestamp for checking next time we hook a function.
  169.     $dirtyposts[0][0] = $postid;
  170.     $dirtyposts[0][1] = microtime();
  171.     az_set_dirty_posts($dirtyposts);
  172.     // Release the semaphore now that we are done.
  173.     az_release_cache_mutex();
  174. }
  175.  
  176. /**
  177.  * Cannot use microtime(true) because it is not available
  178.  * in PHP4.
  179.  *
  180.  * @param unknown_type $start
  181.  * @param unknown_type $end
  182.  * @return unknown
  183.  */
  184. function az_microtime_diff($start, $end = false) {
  185.     if (!$end) {
  186.         $end= microtime();
  187.     }
  188.     $start_sec = explode(" ", $start);
  189.     $start = floatval($start_sec[0]) + floatval($start_sec[1]);
  190.     $end_sec = explode(" ", $end);
  191.     $end = floatval($end_sec[0]) + floatval($end_sec[1]);
  192.     $diff = $end - $start;
  193.     //az_trace("microtime_diff : ".$diff);
  194.     return $diff;
  195. }
  196.  
  197.  
  198. /**
  199.  * For the specified index, find out if the post invalidates the
  200.  * cache.  If it does, then call the HTML cache function to invalidate
  201.  * the HTML cache for the page(s) the index is being displayed on.
  202.  *
  203.  * @param db_index $dbindex index settings loaded from the database
  204.  * @param int $postid id of the post that has just been modified in some way
  205.  */
  206. function az_process_dirty_post($index, $postid, $dirtyposts) {
  207.     //az_trace("az_process_dirty_post: idindex = $index->idindex - postid = $postid");
  208.     $req = new az_request();
  209.     $req->set_vars_from_index($index);
  210.     $cache = new az_index_cache($req, 0, 0, false);
  211.  
  212.     // First check to see if post is in index.  If it is, we assume that the
  213.     // change might affect the index page even if it might not.
  214.     //az_trace("az_process_dirty_post : postid = $postid");
  215.     //az_trace("az_process_dirty_post : cache = ".(is_array($cache->itemcache) ? implode(",", $cache->itemcache) : 'empty'));
  216.     $dirty = az_is_set($index->options, 'disable-cache') || (is_array($cache->itemcache['id']) && in_array($postid, $cache->itemcache['id']));
  217.     //az_trace("az_process_dirty_post: is post in index? ".($dirty ? "yes" : "no"));
  218.     if (!$dirty) {
  219.         // The post was not in the index, but could be a new one
  220.         // to be added to the index.  In this case we need to run
  221.         // the usual query against the postid to see if it should be
  222.         // added to the index.
  223.         $posts = $cache->query_index_items($req, array($postid), 0, true);
  224.         $dirty = is_array($posts) && count($posts) > 0;
  225.         //az_trace("az_process_dirty_post: is post to be added to index? ".($dirty ? "yes" : "no"));
  226.     }
  227.    
  228.     // If it looks as though the index is dirty, then tell the HTML
  229.     // cache to remove the index page.
  230.     if ($dirty) {
  231.         $dirtyposts = az_check_html_cache($index->idindex, $dirtyposts);
  232.     }
  233.     return $dirtyposts;
  234. }
  235.  
  236. /**
  237.  * This function must be called if a process outside the dirty mutex semaphore
  238.  * wants to flush the cache -- for example, two calls are made from the admin
  239.  * pages, since they do not have the semaphore lock.
  240.  *
  241.  * @param int $idindex id of index whose pages are to be flushed
  242.  */
  243. function az_flush_html_cache($idindex) {
  244.     az_acquire_cache_mutex();
  245.     $dirtyposts = az_get_dirty_posts();
  246.     az_check_html_cache($idindex, $dirtyposts);
  247.     unset($dirtyposts[$idindex]);
  248.     az_set_dirty_posts($dirtyposts);
  249.     az_release_cache_mutex();
  250. }
  251.  
  252. function az_check_html_cache($idindex, $dirtyposts) {
  253.     //az_trace("az_check_html_cache: $idindex");
  254.     $dirtypages = $dirtyposts['pages'][$idindex];
  255.     if (is_array($dirtypages)) {
  256.         for ($i = 0; $i < count($dirtypages); $i++) {
  257.             //az_trace("az_check_html_cache: removing page id ".$dirtypages[$i]." from HTML cache.");
  258.             if (function_exists('wp_cache_post_change')) {
  259.                 wp_cache_post_change($dirtypages[$i]);
  260.             }
  261.         }
  262.         unset($dirtyposts['pages'][$idindex]);
  263.     }
  264.     return $dirtyposts;
  265. }
  266.  
  267. function az_reset_dirty_posts($dirtyposts, $idindex) {
  268.     $dirtyposts[$idindex] = array();
  269.     az_set_dirty_posts($dirtyposts);
  270. }
  271.  
  272. class az_index_cache {
  273.     var $index;            // Current index
  274.     var $pageno;           // Current page number
  275.     var $pagecount;        // Number of pages in the index
  276.     var $items;            // Array of items;
  277.     var $itemcache;        // Cache of item ids stored in database
  278.     var $alphalinks;       // Array of alphabetical links
  279.     var $ignorechars;      // Characters to ignore in a sort
  280.     var $is_multipage;     // True if a multipage index
  281.     var $is_multibyte;     // True if the index contains multibyte strings
  282.     var $has_alphalinks;   // True if alphalinks being used
  283.     var $cache_disabled;   // True if caching is disabled for this index
  284.     var $convertchars;     // True if the index items must be converted to single-byte characters (for sorting)
  285.     var $charmapper;       // If required, used to map multi-byte characters to the equivalent base character
  286.     var $languagetable;    // Name of the language table to use when building the index grouping and alpha links
  287.     var $locale;           // Name of the locale to use during the sorting of the index
  288.        
  289.     /**
  290.      * Constructor for the az_index_cache class.  This function either rebuilds the
  291.      * cached index from cache data stored in the database or, if there is no stored
  292.      * cache, or changes to the blog make the cache out of date, then the index cache
  293.      * is built from scratch.
  294.      *
  295.      * @param az_request $index index to be displayed, include all the current settings
  296.      * @param int $pageid id of page or post displaying the index
  297.      * @param int $pageno the index of the page in the index to be displayed
  298.      * @param boolean $buildcache if true (default) then fully build the cache
  299.      * @return az_index_cache the index cache
  300.      */
  301.     function az_index_cache($index, $pageid, $pageno, $buildcache = true) {
  302.         //$time_start = microtime(true);
  303.         $this->cache_disabled = az_is_set($index->options, 'disable-cache');
  304.         $this->has_alphalinks = az_is_set($index->options, 'alpha-links');
  305.         $this->is_multipage = az_is_set($index->options, 'multipage') && $index->perpage > 0;
  306.        
  307.         $this->index = $index;
  308.         $this->pageno = $pageno;
  309.         $this->itemcache = unserialize($index->itemcache);
  310.        
  311.         // Set up NLS options.
  312.         $this->is_multibyte = az_is_set($index->options, 'nls') && function_exists('mb_strpos');
  313.        
  314.         if ($this->is_multibyte) {
  315.             if (az_is_set($index->options, 'nls-equiv') && !empty($index->nlsequiv)) {
  316.                 $this->languagetable = $index->nlsequiv;
  317.             } else {
  318.                 // Set this to be the default mapping table -- works in most instances.
  319.                 $this->languagetable = AZ_DEFAULT_LANGUAGE_TABLE;
  320.             }
  321.             // Only specify the locale if the index option is turned on and a locale is specified
  322.             if (az_is_set($index->options, 'nls-locale') && !empty($index->nlslocale)) {
  323.                 $this->locale = $index->nlslocale;
  324.             }
  325.         }
  326.        
  327.         $this->ignorechars = az_get_ignorechars($index, $this->is_multibyte);
  328.         $this->convertchars = false;
  329.  
  330.         if ($buildcache) {
  331.             // This is the main path for building or rebuilding the cache
  332.             $this->add_current_page_to_cache($index->id, $pageid);
  333.        
  334.             if ($this->cache_disabled || $this->is_dirty()) {
  335.                 $this->build_index();
  336.                 $this->write_cache();
  337.             } else {
  338.                 $this->build_index_from_cache();
  339.             }
  340.         }
  341.         //$time_end = microtime(true);
  342.         //az_println("Cache execution in: "+(($time_end - $time_start)*1000)."ms");
  343.     }
  344.  
  345.     /**
  346.      * Build the index from scratch.  All items in the index need to be loaded from the database
  347.      * and then sorted before the correct page of the index and its contents can be display. This
  348.      * can be quite slow for very large indexes.
  349.      */
  350.     function build_index() {
  351.         $this->itemcache = 0;
  352.         $this->items = $this->get_index_items($this->index);
  353.         if ($this->is_multipage) {
  354.             $this->pagecount = ceil(count($this->items) / $this->index->perpage);
  355.             $this->pageno = ($this->pageno < 0) ? 0 : ($this->pageno >= $this->pagecount ? $this->pagecount - 1 : $this->pageno);
  356.         }
  357.         if ($this->has_alphalinks) {
  358.             $this->alphalinks = $this->collect_links($this->items, $this->index->perpage);
  359.         }
  360.         $this->items = az_slice_array($this->items, $this->pageno, $this->pagecount, $this->index->perpage, $this->is_multipage);
  361.     }
  362.  
  363.     /**
  364.      * Rebuild the index to be displayed from the cache.  This can be much faster than
  365.      * doing if from scratch because the index does not have to be sorted, and so only the
  366.      * items on the current page of the index need to be queried from the database.
  367.      */
  368.     function build_index_from_cache() {
  369.         if ($this->has_alphalinks) {
  370.             $this->alphalinks = unserialize($this->index->linkcache);
  371.         }
  372.         if ($this->is_multipage) {
  373.             $this->pagecount = ceil(count($this->itemcache['id']) / $this->index->perpage);
  374.             $this->pageno = ($this->pageno < 0) ? 0 : ($this->pageno >= $this->pagecount ? $this->pagecount - 1 : $this->pageno);
  375.         }
  376.         $ids = az_slice_array($this->itemcache['id'], $this->pageno, $this->pagecount, $this->index->perpage, $this->is_multipage);
  377.         if (!empty($this->itemcache['key'])) {
  378.             //az_println("There are keys in the cache");
  379.             $keys = az_slice_array($this->itemcache['key'], $this->pageno, $this->pagecount, $this->index->perpage, $this->is_multipage);
  380.         }
  381.         $this->items = $this->get_index_items($this->index, $ids, $keys);
  382.     }  
  383.    
  384.     function write_cache() {
  385.         global $wpdb;
  386.        
  387.         $id = $this->index->id;
  388.        
  389.         if (!$this->cache_disabled) {
  390.             $itemcache = serialize($this->itemcache);
  391.             $linkcache = serialize($this->alphalinks);
  392.         }
  393.        
  394.         $query = "UPDATE ".AZ_TABLE." SET itemcache = '$itemcache', linkcache = '$linkcache' WHERE idindex = $id";
  395.         $rc = $wpdb->query($query);
  396.         //az_println("fn:write_cache : query = ".$query."<br/> rc = ".$rc);
  397.     }
  398.  
  399.     /**
  400.      * Save the page/post id of the page the index is being shown on.  This is done
  401.      * so that we can invalidate the page cached in WP-Super-Cache if it becomes dirty.
  402.      *
  403.      * @param int $idindex current index id
  404.      * @param int $pageid page id the current index is being displayed on
  405.      */
  406.     function add_current_page_to_cache($idindex, $pageid) {
  407.         //az_trace("add_current_page_to_cache: indexid = $idindex; pageid = $pageid");
  408.         if ($pageid > 0) {
  409.             az_acquire_cache_mutex();
  410.             $dirtyposts = az_get_dirty_posts();
  411.             if (!(is_array($dirtyposts['pages'][$idindex]) && in_array(intval($pageid), $dirtyposts['pages'][$idindex]))) {
  412.                  $dirtyposts['pages'][$idindex][] = $pageid;
  413.                  az_set_dirty_posts($dirtyposts);
  414.                  //az_trace("   added to dirtyposts: ".implode(",", $dirtyposts['pages'][$idindex]));
  415.             }
  416.             az_release_cache_mutex();
  417.         }
  418.     }
  419.  
  420.     /**
  421.      * Test to see if the index cache is dirty (i.e. out of date).  If there is no cache,
  422.      * or any of the dirty items causes a change to the content and/or order of the index
  423.      * then the index cache is dirty and needs to be updated.
  424.      *
  425.      * @return boolean true if the index cache is dirty and needs to be recreated
  426.      */
  427.     function is_dirty() {
  428.         $dirty = false;
  429.         $idindex = $this->index->id;
  430.         az_acquire_cache_mutex();
  431.         $dirtyposts = az_get_dirty_posts($idindex);
  432.         if (empty($this->itemcache)) {
  433.             // If there is no cache for the index then, by default, the index is dirty.
  434.             $dirty = true;
  435.             az_reset_dirty_posts($dirtyposts, $idindex);
  436.         } else if (!empty($dirtyposts)) {
  437.             // If there is a cache, then check to see if the items in the dirty list invalidate it
  438.             $dirty = $this->process_dirty_items($this->index, $this->itemcache, $dirtyposts[$idindex]);
  439.             az_reset_dirty_posts($dirtyposts, $idindex);
  440.         }
  441.         //az_dump_dirty(az_get_dirty_posts());
  442.         az_release_cache_mutex();
  443.         //az_trace("is_dirty: cache: ".(!empty($this->itemcache) ? implode(",", $this->itemcache) : "empty"));
  444.         //az_trace("is_dirty: dirty: ".(!empty($dirtyposts) ? implode(",", $dirtyposts[$idindex]) : "empty"));
  445.         //az_trace("is_dirty: cache is ".($dirty ? "dirty" : "clean"));
  446.         return $dirty;
  447.     }
  448.  
  449.     /**
  450.      * This function tests to see if an item in the dirty list has changed in such a way
  451.      * that invalidates the order and/or size of the index.  If the item is already in the
  452.      * index, then we put it and the previous and next items in the index (in their current
  453.      * order) and execute the index's sort.  If the resulting order is different, then we
  454.      * know that the cache is now invalid.  This function also works for the cases where
  455.      * an item has been deleted or removed from the index, or where a new item needs to be
  456.      * added.
  457.      *
  458.      * @param $index index being tested
  459.      * @param $indexitems current ordered array of cached items
  460.      * @param $dirtyitems array of dirty items to be checked out
  461.      * @return boolean true if the index cache is found to be dirty
  462.      */
  463.     function process_dirty_items($index, $indexitems, $dirtyitems) {
  464.         //az_trace("process dirty items: dirty items = ".implode(",", $dirtyitems));
  465.         $dirty = false;
  466.         foreach ($dirtyitems as $postid) {
  467.             $position = array_keys($indexitems['id'], $postid);
  468.             if (empty($position)) {
  469.                 //az_trace("process_dirty_items : empty array");
  470.                 $position[] = false;
  471.             }
  472.             //az_trace("   process dirty items: ids in index = ".count($indexitems['id'])." : ".implode(",", $indexitems['id']));
  473.             //az_trace("   process dirty items: positions = ".count($position)." : ".implode(",", $position));
  474.             foreach ($position as $pos) {
  475.                 //az_trace("   process_dirty_items : processing next item:".($pos === false ? "false" : $pos));
  476.                 unset($testhit);
  477.                 // Fetch the previous postid in the index (if not at the start)
  478.                 if ($pos > 0) {
  479.                     $testhit[] = $indexitems['id'][$pos - 1];
  480.                     if ($indexitems['key'] != 0) {
  481.                         $testkey[] = $indexitems['key'][$pos - 1];
  482.                     }
  483.                 }
  484.                 $testhit[] = $postid;
  485.                 if (pos !== false && $indexitems['key'] != 0) {
  486.                      $testkey[] = $indexitems['key'][$pos];
  487.                 }
  488.                 // Fetch the next postid in the index (if not at the end)                              
  489.                 if ($pos !== false && $pos < count($indexitems['id']) - 1) {
  490.                     $testhit[] = $indexitems['id'][$pos + 1];
  491.                     if ($indexitems['key'] != 0) {
  492.                         $testkey[] = $indexitems['key'][$pos + 1];
  493.                     }
  494.                 }
  495.                 // Now put them through the usual retrieval and sort.
  496.                 $items = $this->get_index_items($index, $testhit, $testkey, true, true);
  497.                 $result = $this->get_item_ids($items);
  498.                 // Now check to see if the sort order is the same as before or different.
  499.                 $diff = array_diff_assoc($testhit, $result);
  500.            
  501.                 //az_trace("     test: ".implode(",", $testhit));
  502.                 //az_trace("   result: ".implode(",", $result));
  503.                 //az_trace("     diff: ".implode(",", $diff));
  504.                 //az_trace("    test1: ".((count($diff) > 0 && $pos !== false) ? "true" : "false"));
  505.                 //az_trace("    test2: ".((count($diff) == 0 && $pos === false) ? "true" : "false"));
  506.                
  507.                 // If there is a difference, the index cache is now invalid.
  508.                 if ((count($diff) > 0 && $pos !== false) || (count($diff) == 0 && $pos === false)) {
  509.                     $dirty = true;
  510.                     //az_trace("   the result is that the cache is dirty");
  511.                     break 2;  // Break out of both loops.
  512.                 }
  513.             }
  514.             // No difference found, reset and go to the next dirty item.
  515.             unset($testhit);
  516.         }
  517.         return $dirty;
  518.     }
  519.    
  520.     /**
  521.      * Get the items to be added to the index and sort them in the specified order.
  522.      *
  523.      * @param $index the parameters specificed for the index
  524.      * @return items to be included in the index
  525.      */
  526.     function get_index_items($index, $ids = 0, $keys = 0, $revalidate = false, $revalidatesort = false) {
  527.         //az_println('fn:get_index_items: pageid: '.$post->ID);
  528.         $this->convertchars = AZ_OS_WIN && $this->is_multibyte && (empty($ids) || $revalidatesort);
  529.  
  530.         $posts = $this->query_index_items($index, $ids, $keys, $revalidate);
  531.         //az_println("fn:get_index_items : query = ".$query."<br/> rc = ".$posts." - count = ".count($posts));
  532.  
  533.         // Build an array of items from the query results.
  534.         $items = array();
  535.         $hasfilter = has_filter('azindex_item');
  536.        
  537.         foreach ($posts as $row) {
  538.             $item = $this->get_post_index_info($index, $row, $hasfilter);
  539.             if ($item != null) {
  540.                 $items[] = $item;
  541.             }
  542.         }
  543.        
  544.         // Sort the items into alphabetical order, as specified.
  545.         if ($items && (empty($ids) || $revalidatesort)) {
  546.             // Both globals only used in the compare function.
  547.             global $az_nonalphaend, $az_multibyte;
  548.             $az_multibyte = $this->is_multibyte;
  549.            
  550.             // Determine where to put the non-alpha starting entries by setting a signed variable.
  551.             $az_nonalphaend = az_is_set($index->options, 'non-alpha-end') ? -1 : 1;
  552.            
  553.             // Sort the index using the specified comparison function.
  554.             $comparefn = (az_is_set($index->options, 'custom-sort') && trim($index->customsort) != '') ? $index->customsort : 'az_compare';
  555.            
  556.             // If NLS support is turned on, then set the appropriate locale.
  557.             if ($this->is_multibyte) {
  558.                 if (!empty($this->locale)) {
  559.                     $defaultlocale = setlocale(LC_COLLATE, null);            
  560.                     setlocale(LC_COLLATE, $this->locale);
  561.                 } else {
  562.                     // This seems to improve ths chances of collating working
  563.                     // properly in the default case.
  564.                     setlocale(LC_COLLATE, setlocale(LC_COLLATE, null));
  565.                 }
  566.             }
  567.             usort($items, $comparefn);
  568.            
  569.             // Reset the locale to the default, if necessary.
  570.             if (!empty($defaultlocale)) {
  571.                 setlocale(LC_COLLATE, $defaultlocale);
  572.             }
  573.            
  574.             if (!$this->cache_disabled && !$revalidate) {
  575.                 // Add the ids of the sorted items to the item cache
  576.                 unset($this->itemcache);
  577.                 //az_println("caching the index: ".$index->head);
  578.                 if ($index->head == 'tags' || $index->head == 'cats') {
  579.                     $keys = true;
  580.                     //az_println("putting keys into the cached index : ");
  581.                 }
  582.                 for ($i = 0; $i < count($items); $i++) {
  583.                     $this->itemcache['id'][] = $items[$i]['id'];
  584.                     if ($keys) {
  585.                         $this->itemcache['key'][] = $items[$i]['key'];
  586.                     }
  587.                 }
  588.             }
  589.         }
  590.         return $items;    
  591.     }
  592.  
  593.     function get_item_ids($items) {
  594.         $ids = array();
  595.         if (is_array($items)) {
  596.             for ($i = 0; $i < count($items); $i++) {
  597.                 $ids[] = $items[$i]['id'];
  598.             }
  599.         }
  600.         return $ids;
  601.     }
  602.    
  603.     /**
  604.      * Query the items to be added to the index from the blog's database.
  605.      *
  606.      * @param $index the parameters specificed for the index
  607.      * @return items to be included in the index
  608.      */
  609.     function query_index_items($index, $idlist = 0, $keys = 0, $revalidate = false) {
  610.         global $wpdb;
  611.         //az_trace('fn:query_index_items: index id: '.$index->id);
  612.  
  613.         $fields = 'ID, post_title, post_excerpt, post_author';
  614.         $notags = false;
  615.         $nocats = false;
  616.         // If we're querying categories or tags we need to include
  617.         // the term_id field from the term_taxonomy table.
  618.         if ($index->head == 'cats') {
  619.             $fields .= ', tax2.term_id';
  620.             $nocats = true;
  621.         } else if ($index->head == 'tags') {
  622.             $fields .= ', tax1.term_id';
  623.             $notags = true;
  624.         }
  625.        
  626.         $query = "SELECT DISTINCT $fields FROM $wpdb->posts";
  627.         $selection = "'post'";
  628.         if (az_is_set($index->options, 'include-pages')) {
  629.             if (az_is_set($index->options, 'include-pages-exclude-posts')) {
  630.                 $selection = "'page'";
  631.             } else {
  632.                 $selection .= ",'page'";
  633.             }
  634.         }
  635.         $where = "post_status = 'publish' AND post_type IN ($selection)";
  636.        
  637.         // We just want to validate whether the specified post(s) belong in the index.
  638.         if ($revalidate && !empty($idlist)) {
  639.             $ids = implode(",", $idlist);
  640.             $where .= " AND ID IN (".$ids.")";
  641.         }
  642.         if (!$revalidate && !empty($idlist)) {
  643.             $ids = implode(",", $idlist);
  644.             if ($nocats || $notags) {
  645.                 $terms = new az_terms($index->catids, $index->tagids, az_is_set($index->options, 'child-cats'));
  646.             }
  647.             if (!$index->head == 'cats') {
  648.                 $where .= " AND tax2.taxonomy = 'category'";
  649.             } else if ($index->head == 'tags') {
  650.                 $where .= " AND tax1.taxonomy = 'post_tag'";
  651.             }
  652.             $where = " WHERE ID IN (".$ids.") AND $where ORDER BY FIELD(ID, $ids)";
  653.             //az_println("query_index_items - fast query for cached items");
  654.         } else {
  655.             // Process the terms in the index settings.
  656.             $terms = new az_terms($index->catids, $index->tagids, az_is_set($index->options, 'child-cats'));
  657.            
  658.             // Process the included and excluded categories.
  659.             if (!empty($terms->excats)) {
  660.                 $excats .= " AND $wpdb->posts.ID NOT IN (SELECT DISTINCT object_id FROM $wpdb->term_relationships"
  661.                           ." INNER JOIN $wpdb->term_taxonomy AS tax3 ON ($wpdb->term_relationships.term_taxonomy_id = tax3.term_taxonomy_id"
  662.                           ." AND tax3.taxonomy = 'category' AND tax3.term_id IN($terms->excats)))";
  663.             }
  664.             if (!empty($terms->incats)) {
  665.                 $query .= " INNER JOIN $wpdb->term_relationships AS rel2 ON ($wpdb->posts.ID = rel2.object_id";
  666.                 if (!empty($terms->excats)) {
  667.                     $query .= $excats;
  668.                 }
  669.                 $query .= ") INNER JOIN $wpdb->term_taxonomy AS tax2 ON (rel2.term_taxonomy_id = tax2.term_taxonomy_id"
  670.                          ." AND tax2.taxonomy = 'category' AND tax2.term_id IN ($terms->incats))";
  671.                 $nocats = false;
  672.             } else if (!empty($terms->excats)) {
  673.                 $query .= " INNER JOIN $wpdb->term_relationships AS rel2 ON ($wpdb->posts.ID = rel2.object_id";
  674.                 $query .= $excats.")";
  675.             }  
  676.  
  677.             // Process the included and excluded tags.
  678.             if (!empty($terms->extags)) {
  679.                 $extags .= " AND $wpdb->posts.ID NOT IN (SELECT DISTINCT object_id FROM $wpdb->term_relationships"
  680.                          ." INNER JOIN $wpdb->term_taxonomy AS tax4 ON ($wpdb->term_relationships.term_taxonomy_id = tax4.term_taxonomy_id"
  681.                          ." AND tax4.taxonomy = 'post_tag' AND tax4.term_id IN($terms->extags)))";
  682.             }
  683.                
  684.             if (!empty($terms->intags)) {
  685.                 $query .= " INNER JOIN $wpdb->term_relationships AS rel1 ON ($wpdb->posts.ID = rel1.object_id";
  686.                 if (!empty($terms->extags)) {
  687.                     $query .= $extags;
  688.                 }
  689.                 $query .= ") INNER JOIN $wpdb->term_taxonomy AS tax1 ON (rel1.term_taxonomy_id = tax1.term_taxonomy_id"
  690.                                               ." AND tax1.taxonomy = 'post_tag' AND tax1.term_id IN ($terms->intags))";
  691.                 $notags = false;
  692.             } else if (!empty($terms->extags)) {
  693.                 $query .= " INNER JOIN $wpdb->term_relationships AS rel1 ON ($wpdb->posts.ID = rel1.object_id";
  694.                 $query .= $extags.")";
  695.             }
  696.             $where = " WHERE $where";
  697.         }
  698.        
  699.         // If were sorting on tags or categories and we didn't specify any tags
  700.         // or categories to be included in the sort then we need to do these
  701.         // joins to get the term_id field included in the results.
  702.         if ($nocats) {
  703.             $query .= " INNER JOIN $wpdb->term_relationships AS rel4 ON ($wpdb->posts.ID = rel4.object_id)"
  704.                      ." INNER JOIN $wpdb->term_taxonomy AS tax2 ON (rel4.term_taxonomy_id = tax2.term_taxonomy_id"
  705.                      ." AND tax2.taxonomy = 'category')";
  706.         } else if ($notags) {
  707.             $query .= " INNER JOIN $wpdb->term_relationships AS rel3 ON ($wpdb->posts.ID = rel3.object_id)"
  708.                      ." INNER JOIN $wpdb->term_taxonomy AS tax1 ON (rel3.term_taxonomy_id = tax1.term_taxonomy_id"
  709.                      ." AND tax1.taxonomy = 'post_tag')";
  710.         }
  711.         $posts = $wpdb->get_results($query.$where);
  712.         //az_trace("fn:get_index_items : query = ".$query.$where."<br/> rc = ".$posts." - count = ".count($posts));
  713.        
  714.         // If this is an index where multiple items are allowed
  715.         // then we need to do some post-processing of the query
  716.         // to get the items in the correct order.
  717.         if (!empty($keys)) {
  718.             $posts = $this->process_key_results($posts, $idlist, $keys);
  719.         }
  720.  
  721.         // Now filter out the remaining posts that should not be in the list.
  722.         if (empty($idlist) && !empty($index->headkeyids)) {
  723.             $posts = $this->az_filter_posts($posts, $index->headkeyids, $index->head);
  724.         }
  725.         return $posts;    
  726.     }
  727.  
  728.     /**
  729.      * If sorting by tags/terms, filter out those that are not
  730.      * selected to be in the index.
  731.      *
  732.      * @param array $posts array of posts
  733.      * @param string $termids comma separated list of terms
  734.      * @param string $type type of terms -- cats/tags
  735.      */
  736.     function az_filter_posts($posts, $termids, $type) {
  737.         // Split the tag/cat ids into two lists.
  738.         if ($type == 'tags') {
  739.             $terms = new az_terms(null, $termids, false);
  740.             $exterm = $terms->intags;  // Note, exludes returned in intags (not a bug!)
  741.         } else if ($type == 'cats') {
  742.             $terms = new az_terms($termids, null, az_is_set($index->options, 'child-cats'));
  743.             $exterm = $terms->incats;  // Note, exludes returned in incats (not a bug!)
  744.         }
  745. //      // Remove all posts that don't have tag/cat in the include list.
  746. //      if (!empty($interm)) {
  747. //          $interm = explode(',', $interm);
  748. //          for ($i = count($posts) - 1; $i >= 0; $i--) {
  749. //              if (!in_array($posts[$i]->term_id, $interm)) {
  750. //                  unset($posts[$i]);
  751. //              }
  752. //          }
  753. //      }
  754.         // Remove all posts that do have tag/cat in the exclude list
  755.         if (!empty($exterm)) {
  756.             $exterm = explode(',', $exterm);
  757.             for ($i = count($posts) - 1; $i >= 0; $i--) {
  758.                 if (in_array($posts[$i]->term_id, $exterm)) {
  759.                     unset($posts[$i]);
  760.                 }
  761.             }
  762.         }
  763.         return $posts;        
  764.     }
  765.    
  766.     /**
  767.      * Clean up the results array if there are keys present.  The items need
  768.      * to be reordered and then truncated to remove excess results from the
  769.      * query.
  770.      *
  771.      * @param array $posts array posts from query
  772.      * @param array $ids ids from cache
  773.      * @param array $keys keys from cache
  774.      * @return sorted and truncated array
  775.      */
  776.     function process_key_results($posts, $ids, $keys) {
  777.  
  778.         //az_println("process_key_results");
  779.         for ($i = 0; $i < count($ids); $i++) {
  780.             //az_println($ids[$i].' : '.$posts[$i]->ID." : ".$posts[$i]->term_id." : ".$posts[$i]->post_title);
  781.             if ($posts[$i]->ID != $ids[$i]) {
  782.                 for ($j = $i; $posts[$j]->ID != $ids[$i] && $j < count($posts); $j++);
  783.                 $temp = $posts[$i];
  784.                 $posts[$i] = $posts[$j];
  785.                 $posts[$j] = $temp;
  786.             }
  787.             $posts[$i]->term_id = $keys[$i];
  788.         }
  789.         $count = count($posts);
  790.         while ($i < $count) {
  791.             unset($posts[$i++]);
  792.         }
  793.         return $posts;
  794.     }
  795.    
  796.     function get_post_index_info($index, $row, $hasfilter) {
  797.         $item = null;
  798.         $head = ltrim($this->get_post_item($row->ID, $row->post_title, $row->post_excerpt,
  799.                                           $row->post_author, $row->term_id, $index->head, $index->headkey));        
  800.         if (!empty($head)) {
  801.  
  802.             // Remove any characters we want to ignore during the search.
  803.             $sorthead = empty($this->ignorechars) ? $head : trim(az_ltrim($head, $this->ignorechars));
  804.  
  805.             $subhead = $this->get_post_item($row->ID, $row->post_title, $row->post_excerpt, $row->post_author, $row->term_id, $index->subhead, $index->subheadkey);
  806.             $desc = $this->get_post_item($row->ID, $row->post_title, $row->post_excerpt, $row->post_author, $row->term_id, $index->desc, $index->desckey);
  807.             $sortsubhead = empty($this->ignorechars) ? $subhead : ltrim(az_ltrim($subhead, $this->ignorechars));
  808.             $sortdesc = empty($this->ignorechars) ? $desc : ltrim(az_ltrim($desc, $this->ignorechars));
  809.  
  810.             // If we have a multi-entry index (indexed off tags, categories, or
  811.             // custom fields then we need to store a second key to distinguish
  812.             // between the post entries.
  813.             if ($index->head == 'cats' || $index->head == 'tags') {
  814.                 $key = $row->term_id;
  815.             }
  816.            
  817.             // Don't add item to the index if there is no heading.
  818.             $item = array('id' => $row->ID,
  819.                           // 'initial' => 0,  // Set after the filter is called.
  820.                           'head' => $head,
  821.                           'subhead' => $subhead,
  822.                           'desc' => $desc,
  823.                           'sort-head' => $sorthead,
  824.                           'sort-subhead' => $sortsubhead,
  825.                           'sort-desc' => $sortdesc,
  826.                           'key' => $key
  827.                          );
  828.                          
  829.             if ($hasfilter) {
  830.                 $item = apply_filters('azindex_item', $item, $index->id);
  831.             }
  832.             // Set the fields the filter is not allowed to change, again.
  833.             $item['id'] = $row->ID;
  834.             $item['key'] = $key;
  835.             // Fetch the equivalent initial for the entry, (i.e. unaccented characters)
  836.             $item['initial'] = $this->get_base_initial($item['sort-head']);
  837.            
  838.             // If we are running on windows then we have to deconvert before sorting.
  839.             if ($this->convertchars) {
  840.                 $item['sort-head'] = utf8_decode($item['sort-head']);
  841.                 $item['sort-subhead'] = utf8_decode($item['sort-subhead']);
  842.                 $item['sort-desc'] = utf8_decode($item['sort-desc']);
  843.             }
  844.         }
  845.         return $item;
  846.     }
  847.  
  848.     function get_base_initial($title) {
  849.         if ($this->is_multibyte) {
  850.             $char = mb_substr($title, 0, 1, "UTF-8");
  851.             // Check the length of the character in bytes.
  852.             if (strlen($char) > 1) {
  853.                 // If it's a multibyte character then obtain the
  854.                 // base unaccented character, if there is one.
  855.                 if (empty($this->charmapper)) {
  856.                     $this->charmapper = az_get_collation_mapper($this->languagetable);
  857.                 }
  858.                 $char = az_map_char($char, $this->charmapper);
  859.             } else {
  860.                 $char = strtoupper($title[0]);
  861.             }
  862.         } else if (function_exists("mb_substr")) {
  863.             $char = mb_strtoupper(mb_substr($title, 0, 1, "UTF-8"));
  864.         } else {
  865.             $char = strtoupper($title[0]);
  866.         }
  867.         return $char;
  868.     }
  869.    
  870.     /**
  871.      * Get the item for a post specified in the index as the heading,
  872.      * subheading, or description.
  873.      *
  874.      * @param $type type of item to retrieve
  875.      * @param $key item key, if the item is a custom field
  876.      * @return item's value
  877.      */
  878.     function get_post_item($postid, $title, $excerpt, $author, $term_id, $type, $key = false) {
  879.         switch ($type) {
  880.             case 'title':
  881.                 $item = $title;
  882.                 break;
  883.             case 'excerpt':
  884.                 $item = $excerpt;
  885.                 break;
  886.             case 'author':
  887.                 $item = get_author_name($author);
  888.                 break;
  889.             case 'cats':
  890.                 $item = get_term_field('name', $term_id, 'category', 'raw');
  891.                 break;
  892.             case 'tags':
  893.                 $item = get_term_field('name', $term_id, 'post_tag', 'raw');
  894.                 break;    
  895.             case 'custom':
  896.                 // Only the first custom field found with key is used.
  897.                 $item = get_post_custom_values($key, $postid);
  898.                 $item = $item[0];
  899.                 break;
  900.         }  
  901.         return $item;
  902.     }
  903.  
  904.     /**
  905.      * Collect the alphabetical links for the items in the index. Links are
  906.      * created for the first item to begin with each letter.
  907.      *
  908.      * @param $items the items in the index
  909.      * @param $perpage number of items per page
  910.      * @return an array characters and what page of the index they are on
  911.     */
  912.     function collect_links($items, $perpage) {
  913.         for ($i = 0; $i < count($items); $i++) {
  914.             $item = $items[$i];
  915.             if (!empty($item)) {
  916.                 $char = $item['initial'];
  917.                 // Now we should be byte compatible with prevhead if we're going to match.
  918.                 if (strcmp($char, $prevchar)) {
  919.                     $page = $this->is_multipage ? intval($i / $perpage) : 0;
  920.                     $indexchars[] = array('char' => $char, 'page' => $page);
  921.                 }
  922.                 $prevchar = $char;
  923.             }
  924.         }
  925.         return $indexchars;
  926.     }
  927. }
  928. /**
  929.  * Compare function for sorting the index into alphabetical order, first using the
  930.  * heading, then the subheading, then the description.  Non-alpha numbers will be
  931.  * sorted to the top of the list (could make this an option at some point).
  932.  *
  933.  * @param $in1 first index item to compare
  934.  * @param $in2 second index item to compare
  935.  * @return comparison result
  936.  */
  937. function az_compare($in1, $in2) {
  938.     global $az_nonalphaend, $az_multibyte;
  939.     $rc = az_strcoll($in1['sort-head'], $in2['sort-head'], $az_multibyte);
  940.     if ($rc == 0) {
  941.         $rc = az_strcoll($in1['sort-subhead'], $in2['sort-subhead'], $az_multibyte);            
  942.         if ($rc == 0) {
  943.             $rc = az_strcoll($in1['sort-desc'], $in2['sort-desc'], $az_multibyte);            
  944.         }
  945.     } else {
  946.         if (!empty($in1['initial']) && !empty($in2['initial'])) {
  947.             if ($az_nonalphaend != 0) {
  948.                 // Check to see if either heading starts with a non-alphanumeric character.
  949.                 if ($az_multibyte) {
  950.                     // Note this will still not work with some multi-byte character.
  951.                     // Solution is to exclude them from the headings using ignore option.
  952.                     $pos1 = mb_ereg_match('[[:upper:][:lower:][:digit:]]', $in1['initial']);
  953.                     $pos2 = mb_ereg_match('[[:upper:][:lower:][:digit:]]', $in2['initial']);
  954.                     //az_trace("COMP:".$in1['initial'].":".$in2['initial'].": $pos1 : $pos2");
  955.                 } else {                
  956.                     $pos1 = ereg('[[:alnum:]]', $in1['initial']);
  957.                     $pos2 = ereg('[[:alnum:]]', $in2['initial']);
  958.                     //az_trace("COMP:".$in1['initial'].":".$in2['initial'].": $pos1 : $pos2 : $az_nonalphaend");
  959.                 }
  960.                 if ($pos1 === false && !($pos2 === false)) {
  961.                     $rc = -1 * $az_nonalphaend;
  962.                 } else if ($pos2 === false && !($pos1 === false)) {
  963.                     $rc = 1 * $az_nonalphaend;
  964.                 }
  965.             }
  966.         }  
  967.     }
  968.     return $rc;
  969. }
  970.  
  971. function az_slice_array($array, $pageno, $pagecount, $perpage, $multipage) {
  972.        
  973.     $start = 0;
  974.     $length = count($array);
  975.    
  976.     if ($multipage) {
  977.         $length = $perpage;
  978.         if ($pageno > 0) {
  979.             // Keep the last item on the previous page in the front of the array if
  980.             // we are not on the first (or only) page of the index.
  981.             $start = $pageno * $perpage - 1;
  982.             $length++;
  983.         }
  984.         if ($pageno < $pagecount - 1) {
  985.             // Keep the first item on the next page at the end of the array if
  986.             // we are not on the last (or only) page of the index.
  987.             $length++;
  988.         }
  989.     }
  990.     return array_slice($array, $start, $length);
  991. }
  992.  
  993. function az_acquire_cache_mutex() {
  994.     global $az_mutex;
  995.     if (!isset($az_mutex)) {
  996.         $az_mutex = new az_mutex(1, "az_sem_cache");
  997.     }
  998.     return $az_mutex->acquire();
  999. }
  1000.  
  1001. function az_release_cache_mutex() {
  1002.     global $az_mutex;
  1003.     return $az_mutex->release();
  1004. }
  1005.  
  1006. class az_mutex {
  1007.     var $id;
  1008.     var $sem_id;
  1009.     var $is_acquired = false;
  1010.     var $use_flock = false;
  1011.     var $filename = '';
  1012.     var $filepointer;
  1013.  
  1014.     function az_mutex($id, $filename = '') {
  1015.         if (AZ_OS_WIN || !function_exists('sem_get')) {
  1016.             $this->use_flock = true;
  1017.         }
  1018.        
  1019.         $this->id = $id;
  1020.  
  1021.         if ($this->use_flock) {
  1022.             if (empty($filename)){
  1023.                 az_trace("Mutex:no filename specified");
  1024.             } else {
  1025.                 $this->filename = $filename;
  1026.             }
  1027.         } else {
  1028.             if (!($this->sem_id = sem_get($this->id, 1))) {
  1029.                 az_trace("Mutex:Error getting semaphore");
  1030.             }
  1031.         }
  1032.     }
  1033.  
  1034.     function acquire() {
  1035.         if ($this->use_flock) {
  1036.             if (($this->filepointer = @fopen($this->filename, "w+")) == false) {
  1037.                 az_trace("Mutex:error opening mutex file<br>");
  1038.                 return false;
  1039.             }
  1040.             if (flock($this->filepointer, LOCK_EX) == false) {
  1041.                 az_trace("Mutex:error locking mutex file");
  1042.                 return false;
  1043.             }
  1044.         } else {
  1045.             if (!sem_acquire($this->sem_id)) {
  1046.                 az_trace("Mutex:error acquiring semaphore");
  1047.                 return false;
  1048.             }
  1049.         }
  1050.         $this->is_acquired = true;
  1051.         return true;
  1052.     }
  1053.  
  1054.     function release() {
  1055.         if (!$this->is_acquired) {
  1056.             return true;
  1057.         }
  1058.  
  1059.         if ($this->use_flock) {
  1060.             if (flock($this->filepointer, LOCK_UN) == false) {
  1061.                 az_trace("Mutex:error unlocking mutex file");
  1062.                 return false;
  1063.             }
  1064.             fclose($this->filepointer);
  1065.         } else {
  1066.             if (!sem_release($this->sem_id)) {
  1067.                 az_trace("Mutex:error releasing semaphore");
  1068.                 return false;
  1069.             }
  1070.         }
  1071.  
  1072.         $this->is_acquired = false;
  1073.         return true;
  1074.     }
  1075.  
  1076.     function getId() {
  1077.         return $this->sem_id;
  1078.     }
  1079. }
  1080.  
  1081. /**
  1082.  * Class encapsulating the code for processing the terms (categories and tags)
  1083.  * specified in the settings for an index.  Responsible for separating out
  1084.  * the included and excluded terms and for finding all the children of the
  1085.  * specified categories.
  1086.  *
  1087.  */
  1088. class az_terms {
  1089.     var $incats; // Included category ids
  1090.     var $intags; // Included tag ids
  1091.     var $excats; // Excluded category ids
  1092.     var $extags; // Excluded tag ids
  1093.     var $tree;   // Category relationship tree extracted from database
  1094.    
  1095.     /**
  1096.      * Constructor for the az_terms class. Does all the necessary processing
  1097.      * on the terms ready to be used for querying the database for the index.
  1098.      *
  1099.      * @param string $cats comma separated string of category ids
  1100.      * @param string $tags comma separated string of tag ids
  1101.      * @param boolean $include_children true if child categories are to be included
  1102.      */
  1103.     function az_terms($cats, $tags, $include_children) {
  1104.         $terms = $this->split_terms($cats);
  1105.         $this->incats = $terms['include'];
  1106.         $this->excats = $terms['exclude'];
  1107.         $terms = $this->split_terms($tags);
  1108.         $this->intags = $terms['include'];
  1109.         $this->extags = $terms['exclude'];
  1110.        
  1111.         if ($include_children) {
  1112.             $this->tree = $this->get_cat_tree();
  1113.             $this->incats = $this->get_children($this->incats);
  1114.             $this->excats = $this->get_children($this->excats);
  1115.         }
  1116.         //az_println('incats = '.$this->incats);
  1117.         //az_println('excats = '.$this->excats);
  1118.         //az_println('intags = '.$this->intags);
  1119.         //az_println('extags = '.$this->extags);
  1120.     }
  1121.    
  1122.     /**
  1123.      * Split the list of terms into those for terms to be included
  1124.      * and those for terms to be excluded (prefixed with ~ sign)
  1125.      *
  1126.      * @param string $termids comma separated list of term ids (categories or tags).
  1127.      * @return array containing two strings for 'include' and 'exclude' term ids.
  1128.      */
  1129.     function split_terms($termids) {
  1130.         // Only do the split if there is a ~ sign in the string.
  1131.         if (!(strpos($termids, '~') === false)) {
  1132.             $ids = explode(',', $termids);
  1133.             foreach ($ids as $id) {
  1134.                 if ($id[0] == '~') {
  1135.                     $exclude .= trim($id, '~').',';
  1136.                 } else {
  1137.                     $include .= $id.',';
  1138.                 }
  1139.             }
  1140.             $termlist['exclude'] = trim($exclude, ',');
  1141.             $termlist['include'] = trim($include, ',');
  1142.            
  1143.         } else if (!empty($termids)) {
  1144.             // No exclude terms, just copy into the include array.
  1145.             $termlist['include'] = $termids;
  1146.         }
  1147.         return $termlist;
  1148.     }
  1149.  
  1150.     function get_children($idlist) {
  1151.         if (!empty($idlist) && !empty($this->tree)) {
  1152.             $ids = explode(",", $idlist);
  1153.             foreach ($ids as $id) {
  1154.                 $children = $this->find_children(null, $id);
  1155.                 if (!empty($children)) {
  1156.                     $idlist .= ','.implode(',', $children);
  1157.                 }
  1158.             }
  1159.         }
  1160.         return $idlist;
  1161.     }
  1162.    
  1163.     function find_children($children, $id) {
  1164.         foreach ($this->tree as $cat) {
  1165.             if ($cat['parent'] == $id) {
  1166.                 $children[] = $cat['id'];
  1167.                 $children = $this->find_children($children, $cat['id']);                                        
  1168.             }
  1169.         }
  1170.         return $children;
  1171.     }
  1172.  
  1173.     function get_cat_tree() {
  1174.         global $wpdb;
  1175.         $query = "SELECT * FROM $wpdb->term_taxonomy WHERE taxonomy = 'category' AND parent != 0";
  1176.         $result = $wpdb->get_results($query);
  1177.         foreach ($result as $row) {
  1178.             $tree[] = array('id' => $row->term_id, 'parent' => $row->parent);
  1179.         }
  1180.         return $tree;
  1181.     }
  1182. }
  1183.  
  1184. function az_map_char($char, $mapper) {
  1185.     $pos = mb_strpos($mapper[1], $char, 0, "UTF-8");
  1186.     if ($pos !== false) {
  1187.         $char = mb_substr($mapper[0], $pos, 1, "UTF-8");
  1188.     }
  1189.     return $char;
  1190. }
  1191.  
  1192. function az_get_collation_mapper($language) {
  1193.     switch ($language) {
  1194.         case "Czech":
  1195.             $map[0] = pack("H*", "41414141C48C44444545454549494E4E4F4F4F4FC598C5A054545555555555555959");
  1196.             $map[1] = pack("H*", "C381C384C3A1C3A4C48DC48EC48FC389C3A9C49AC49BC38DC3ADC587C588C393C396C3B3C3B6C599C5A1C5A4C5A5C39AC39CC3BAC3BCC5AEC5AFC39DC3BD");
  1197.             break;
  1198.         case "Danish":
  1199.             $map[0] = pack("H*", "4141414141414141C7BC4343C390454545454545454549494F4F4F4FC592C7BE555559595959C384C384C384C396C396C396C385");
  1200.             $map[1] = pack("H*", "C380C381C382C3A0C3A1C3A2C7BAC7BBC7BDC387C3A7C3B0C388C389C38AC38BC3A8C3A9C3AAC3ABC38DC3ADC393C394C3B3C3B4C593C7BFC39AC3BAC39CC39DC3BCC3BDC386C3A4C3A6C398C3B6C3B8C3A5");
  1201.             break;
  1202.         case "Esperanto":
  1203.             $map[0] = pack("H*", "C488C49CC4A4C4B4C59CC5AC");
  1204.             $map[1] = pack("H*", "C489C49DC4A5C4B5C59DC5AD");
  1205.             break;
  1206.         case "Estonian":
  1207.             $map[0] = pack("H*", "C5A0C5BDC395C384C396C39C");
  1208.             $map[1] = pack("H*", "C5A1C5BEC3B5C3A4C3B6C3BC");
  1209.             break;
  1210.         case "General European":
  1211.             $map[0] = pack("H*", "414141414141414141414141414141414141414141414141414141414141414141414242424242424343434343434343434343434444444444444444444444444545454545454545454545454545454545454545454545454545454545454545454546464747474747474747474747474747484848484848484848484848484848494949494949494949494949494949494949494949494949494949494A4A4A4B4B4B4B4B4B4B4B4B4B4C4C4C4C4C4C4C4C4C4C4C4C4C4C4D4D4D4D4D4D4E4E4E4E4E4E4E4E4E4E4E4E4E4E4E4E4E4E4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F4F505050505252525252525252525252525252525252525353535353535353535353535353535353535353535454545454545454545454545454545555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555556565656575757575757575757575757575858585859595959595959595959595A5A5A5A5A5A5A5A5A5A5A5AC386C386C386C386C386C390C398C398C398C39EC490C4A6C4B2C4BFC581C58AC592C5A6C682C684C687C68BC68EC691C698C6A2C6A4C6A7C6ACC6B3C6B5C6B7C6B7C6B8C6BCC784C784C787C787C78AC78AC7A4C7B1C7B1C695C6BFC89CC8A2C8A4CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE86CE92CE92CE93CE94CE88CE88CE88CE88CE88CE88CE88CE88CE88CE88CE88CE88CE88CE88CE88CE88CE88CE96CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE89CE98CE98CE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE8ACE9ACE9ACE9BCE9CCE9DCE9ECE8CCE8CCE8CCE8CCE8CCE8CCE8CCE8CCE8CCE8CCE8CCE8CCE8CCE8CCE8CCE8CCE8CCEA0CEA0CEA1CEA1CEA1CEA1CEA1CEA3CEA3CEA3CEA4CE8ECE8ECE8ECE8ECE8ECE8ECE8ECE8ECE8ECE8ECE8ECE8ECE8ECE8ECE8ECE8ECE8ECE8ECE8ECE8ECE8ECE8ECE8ECE8ECE8ECE8ECE8ECEA6CEA6CEA7CEA8CE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCE8FCF92CF92CF9ACF9CCF9ECFA0CFA2CFA4CFA6CFA8CFAACFACCFAED082D084D085D086D086D086D088D089D08AD08BD08FD090D090D090D090D090D091D092D083D083D083D094D080D080D080D080D080D080D080D096D096D096D096D096D097D097D097D08DD08DD08DD08DD08DD08DD08DD099D08CD08CD08CD09BD09CD09DD09ED09ED09ED09FD0A0D0A1D0A2D08ED08ED08ED08ED08ED08ED08ED08ED08ED0A4D0A5D0A6D0A7D0A7D0A7D0A8D0A9D0AAD0ABD0ABD0ABD0ACD0ADD0ADD0ADD0AED0AFD1A0D1A2D1A4D1A6D1A8D1AAD1ACD1AED1B0D1B2D1B4D1B4D1B4D1B8D1BAD1BCD1BED280D28CD28ED290D292D294D296D298D29AD29CD29ED2A0D2A2D2A4D2A6D2A8D2AAD2ACD2AED2B0D2B2D2B4D2B6D2B8D2BAD2BCD2BED383D387D38BD394D398D398D398D3A0D3A8D3A8D3A8D4B1D4B2D4B3D4B4D4B5D4B6D4B7D4B8D4B9D4BAD4BBD4BCD4BDD4BED4BFD580D581D582D583D584D585D586D587D588D589D58AD58BD58CD58DD58ED58FD590D591D592D593D594D595D596E1BDB1E1BDB3E1BDB5E1BDB7E1BDBBE1BDB9");
  1212.             $map[1] = pack("H*", "C380C381C382C383C384C385C3A0C3A1C3A2C3A3C3A4C3A5C480C481C482C483C484C485C78DC78EC79EC79FC7A0C7A1C7BAC7BBC880C881C882C883C8A6C8A7E1B880E1B881E1B882E1B883E1B884E1B885E1B886E1B887C387C3A7C486C487C488C489C48AC48BC48CC48DE1B888E1B889C48EC48FE1B88AE1B88BE1B88CE1B88DE1B88EE1B88FE1B890E1B891E1B892E1B893C388C389C38AC38BC3A8C3A9C3AAC3ABC492C493C494C495C496C497C498C499C49AC49BC884C885C886C887C8A8C8A9E1B894E1B895E1B896E1B897E1B898E1B899E1B89AE1B89BE1B89CE1B89DE1B89EE1B89FC49CC49DC49EC49FC4A0C4A1C4A2C4A3C7A6C7A7C7B4C7B5E1B8A0E1B8A1C4A4C4A5C89EC89FE1B8A2E1B8A3E1B8A4E1B8A5E1B8A6E1B8A7E1B8A8E1B8A9E1B8AAE1B8ABE1BA96C38CC38DC38EC38FC3ACC3ADC3AEC3AFC4A8C4A9C4AAC4ABC4ACC4ADC4AEC4AFC4B0C4B1C78FC790C888C889C88AC88BE1B8ACE1B8ADE1B8AEE1B8AFC4B4C4B5C7B0C4B6C4B7C7A8C7A9E1B8B0E1B8B1E1B8B2E1B8B3E1B8B4E1B8B5C4B9C4BAC4BBC4BCC4BDC4BEE1B8B6E1B8B7E1B8B8E1B8B9E1B8BAE1B8BBE1B8BCE1B8BDE1B8BEE1B8BFE1B980E1B981E1B982E1B983C391C3B1C583C584C585C586C587C588C7B8C7B9E1B984E1B985E1B986E1B987E1B988E1B989E1B98AE1B98BC392C393C394C395C396C3B2C3B3C3B4C3B5C3B6C58CC58DC58EC58FC590C591C6A0C6A1C791C792C7AAC7ABC7ACC7ADC88CC88DC88EC88FC8AAC8ABC8ACC8ADC8AEC8AFC8B0C8B1E1B98CE1B98DE1B98EE1B98FE1B990E1B991E1B992E1B993E1B994E1B995E1B996E1B997C594C595C596C597C598C599C890C891C892C893E1B998E1B999E1B99AE1B99BE1B99CE1B99DE1B99EE1B99FC59AC59BC59CC59DC59EC59FC5A0C5A1C5BFC898C899E1B9A0E1B9A1E1B9A2E1B9A3E1B9A4E1B9A5E1B9A6E1B9A7E1B9A8E1B9A9C5A2C5A3C5A4C5A5C89AC89BE1B9AAE1B9ABE1B9ACE1B9ADE1B9AEE1B9AFE1B9B0E1B9B1E1BA97C399C39AC39BC39CC3B9C3BAC3BBC3BCC5A8C5A9C5AAC5ABC5ACC5ADC5AEC5AFC5B0C5B1C5B2C5B3C6AFC6B0C793C794C795C796C797C798C799C79AC79BC79CC894C895C896C897E1B9B2E1B9B3E1B9B4E1B9B5E1B9B6E1B9B7E1B9B8E1B9B9E1B9BAE1B9BBE1B9BCE1B9BDE1B9BEE1B9BFC5B4C5B5E1BA80E1BA81E1BA82E1BA83E1BA84E1BA85E1BA86E1BA87E1BA88E1BA89E1BA98E1BA8AE1BA8BE1BA8CE1BA8DC39DC3BDC3BFC5B6C5B7C5B8C8B2C8B3E1BA8EE1BA8FE1BA99C5B9C5BAC5BBC5BCC5BDC5BEE1BA90E1BA91E1BA92E1BA93E1BA94E1BA95C3A6C7A2C7A3C7BCC7BDC3B0C3B8C7BEC7BFC3BEC491C4A7C4B3C580C582C58BC593C5A7C683C685C688C68CC79DC692C699C6A3C6A5C6A8C6ADC6B4C6B6C7AEC7AFC6B9C6BDC785C786C788C789C78BC78CC7A5C7B2C7B3C7B6C7B7C89DC8A3C8A5CE91CEACCEB1E1BC80E1BC81E1BC82E1BC83E1BC84E1BC85E1BC86E1BC87E1BC88E1BC89E1BC8AE1BC8BE1BC8CE1BC8DE1BC8EE1BC8FE1BDB0E1BE80E1BE81E1BE82E1BE83E1BE84E1BE85E1BE86E1BE87E1BE88E1BE89E1BE8AE1BE8BE1BE8CE1BE8DE1BE8EE1BE8FE1BEB0E1BEB1E1BEB2E1BEB3E1BEB4E1BEB6E1BEB7E1BEB8E1BEB9E1BEBAE1BEBCCEB2CF90CEB3CEB4CE95CEADCEB5E1BC90E1BC91E1BC92E1BC93E1BC94E1BC95E1BC98E1BC99E1BC9AE1BC9BE1BC9CE1BC9DE1BDB2E1BF88CEB6CE97CEAECEB7E1BCA0E1BCA1E1BCA2E1BCA3E1BCA4E1BCA5E1BCA6E1BCA7E1BCA8E1BCA9E1BCAAE1BCABE1BCACE1BCADE1BCAEE1BCAFE1BDB4E1BE90E1BE91E1BE92E1BE93E1BE94E1BE95E1BE96E1BE97E1BE98E1BE99E1BE9AE1BE9BE1BE9CE1BE9DE1BE9EE1BE9FE1BF82E1BF83E1BF84E1BF86E1BF87E1BF8AE1BF8CCEB8CF91CE90CE99CEAACEAFCEB9CF8AE1BCB0E1BCB1E1BCB2E1BCB3E1BCB4E1BCB5E1BCB6E1BCB7E1BCB8E1BCB9E1BCBAE1BCBBE1BCBCE1BCBDE1BCBEE1BCBFE1BDB6E1BEBEE1BF90E1BF91E1BF92E1BF96E1BF97E1BF98E1BF99E1BF9ACEBACFB0CEBBCEBCCEBDCEBECE9FCEBFCF8CE1BD80E1BD81E1BD82E1BD83E1BD84E1BD85E1BD88E1BD89E1BD8AE1BD8BE1BD8CE1BD8DE1BDB8E1BFB8CF80CF96CF81CFB1E1BFA4E1BFA5E1BFACCF82CF83CFB2CF84CEA5CEABCEB0CF85CF8BCF8DE1BD90E1BD91E1BD92E1BD93E1BD94E1BD95E1BD96E1BD97E1BD99E1BD9BE1BD9DE1BD9FE1BDBAE1BFA0E1BFA1E1BFA2E1BFA6E1BFA7E1BFA8E1BFA9E1BFAACF86CF95CF87CF88CEA9CF89CF8EE1BDA0E1BDA1E1BDA2E1BDA3E1BDA4E1BDA5E1BDA6E1BDA7E1BDA8E1BDA9E1BDAAE1BDABE1BDACE1BDADE1BDAEE1BDAFE1BDBCE1BEA0E1BEA1E1BEA2E1BEA3E1BEA4E1BEA5E1BEA6E1BEA7E1BEA8E1BEA9E1BEAAE1BEABE1BEACE1BEADE1BEAEE1BEAFE1BFB2E1BFB3E1BFB4E1BFB6E1BFB7E1BFBAE1BFBCCF93CF94CF9BCF9DCF9FCFA1CFA3CFA5CFA7CFA9CFABCFADCFAFD192D194D195D087D196D197D198D199D19AD19BD19FD0B0D390D391D392D393D0B1D0B2D093D0B3D193D0B4D081D095D0B5D190D191D396D397D0B6D381D382D39CD39DD0B7D39ED39FD098D0B8D19DD3A2D3A3D3A4D3A5D0B9D09AD0BAD19CD0BBD0BCD0BDD0BED3A6D3A7D0BFD180D181D182D0A3D183D19ED3AED3AFD3B0D3B1D3B2D3B3D184D185D186D187D3B4D3B5D188D189D18AD18BD3B8D3B9D18CD18DD3ACD3ADD18ED18FD1A1D1A3D1A5D1A7D1A9D1ABD1ADD1AFD1B1D1B3D1B5D1B6D1B7D1B9D1BBD1BDD1BFD281D28DD28FD291D293D295D297D299D29BD29DD29FD2A1D2A3D2A5D2A7D2A9D2ABD2ADD2AFD2B1D2B3D2B5D2B7D2B9D2BBD2BDD2BFD384D388D38CD395D399D39AD39BD3A1D3A9D3AAD3ABD5A1D5A2D5A3D5A4D5A5D5A6D5A7D5A8D5A9D5AAD5ABD5ACD5ADD5AED5AFD5B0D5B1D5B2D5B3D5B4D5B5D5B6D5B7D5B8D5B9D5BAD5BBD5BCD5BDD5BED5BFD680D681D682D683D684D685D686E1BEBBE1BF89E1BF8BE1BF9BE1BFABE1BFB9");
  1213.             break;
  1214.         case "Hungarian":
  1215.             $map[0] = pack("H*", "41414141454549494F4FC396C396C3965555C39CC39CC39C");
  1216.             $map[1] = pack("H*", "C380C381C3A0C3A1C389C3A9C38DC3ADC393C3B3C3B6C590C591C39AC3BAC3BCC5B0C5B1");
  1217.             break;
  1218.         case "Icelandic":
  1219.             $map[0] = pack("H*", "C381C3904545C389C38DC3935555C39AC39DC39EC384C384C384C398");
  1220.             $map[1] = pack("H*", "C3A1C3B0C38BC3ABC3A9C3ADC3B3C39CC3BCC3BAC3BDC3BEC386C3A4C3A6C3B8");
  1221.             break;
  1222.         case "Latvian":
  1223.             $map[0] = pack("H*", "4141C48C4545C4A24949C4B6C4BBC5854F4FC596C5A05555");
  1224.             $map[1] = pack("H*", "C480C481C48DC492C493C4A3C4AAC4ABC4B7C4BCC586C58CC58DC597C5A1C5AAC5AB");
  1225.             break;
  1226.         case "Lithuanian":
  1227.             $map[0] = pack("H*", "414143434343C48C4949C5A055555555");
  1228.             $map[1] = pack("H*", "C484C485C486C487C488C489C48DC4AEC4AFC5A1C5AAC5ABC5B2C5B3");
  1229.             break;
  1230.         case "Polish":
  1231.             $map[0] = pack("H*", "C484C486C498C581C583C393C59AC5B9");
  1232.             $map[1] = pack("H*", "C485C487C499C582C584C3B3C59BC5BA");
  1233.             break;
  1234.         case "Romanian":
  1235.             $map[0] = pack("H*", "C482C382C38EC898C89A");
  1236.             $map[1] = pack("H*", "C483C3A2C3AEC899C89B");
  1237.             break;
  1238.         case "Roman":
  1239.             $map[0] = pack("H*", "41414545494949494F4F4F4FC5AAC5AAC5AA");
  1240.             $map[1] = pack("H*", "C480C481C492C493C4AAC4ABC4ACC4ADC58CC58DC58EC58FC5ABC5ACC5AD");
  1241.             break;
  1242.         case "Slovak":
  1243.             $map[0] = pack("H*", "4141C384C48C4444454549494C4C4C4C4E4E4F4F4F4F4F4FC3945252C5A054545555555555555959");
  1244.             $map[1] = pack("H*", "C381C3A1C3A4C48DC48EC48FC389C3A9C38DC3ADC4B9C4BAC4BDC4BEC587C588C393C396C3B3C3B6C590C591C3B4C594C595C5A1C5A4C5A5C39AC39CC3BAC3BCC5B0C5B1C39DC3BD");
  1245.             break;
  1246.         case "Slovenian":
  1247.             $map[0] = pack("H*", "C48CC490C5A0");
  1248.             $map[1] = pack("H*", "C48DC491C5A1");
  1249.             break;
  1250.         case "Spanish":
  1251.             $map[0] = pack("H*", "414145454949C3914F4F55555555");
  1252.             $map[1] = pack("H*", "C381C3A1C389C3A9C38DC3ADC3B1C393C3B3C39AC39CC3BAC3BC");
  1253.             break;
  1254.         case "Swedish":
  1255.             $map[0] = pack("H*", "4141414141414141434343434343C39045454545454545454949494949494949C5814E4E4E4E4F4F4F4F4F4F525253535355555555555559595959C385C384C384C384");
  1256.             $map[1] = pack("H*", "C380C381C382C383C3A0C3A1C3A2C3A3C387C3A7C486C487C48CC48DC3B0C388C389C38AC38BC3A8C3A9C3AAC3ABC38CC38DC38EC38FC3ACC3ADC3AEC3AFC582C391C3B1C583C584C392C393C394C3B2C3B3C3B4C598C599C59AC59BC5A1C399C39AC39BC3B9C3BAC3BBC39CC39DC3BCC3BDC3A5C386C3A4C3A6");
  1257.             break;
  1258.         case "Turkish":
  1259.             $map[0] = pack("H*", "4141C387C49E4969C3965555C39C");
  1260.             $map[1] = pack("H*", "C382C3A2C3A7C49FC4B1C4B0C3B6C39BC3BBC3BC");
  1261.             break;
  1262.     }
  1263.     return $map;
  1264. }
  1265.  
  1266. function az_dump_dirty($dirty, $pad = "") {
  1267.     foreach ($dirty as $key => $item) {
  1268.         if (is_array($item)) {
  1269.             if (is_string($key)) {
  1270.                  az_trace('   '.$key.':');
  1271.                  az_dump_dirty($item, "   ");  
  1272.             } else {
  1273.                  az_trace($pad."   $key = ".implode(',', $item));
  1274.             }
  1275.         } else {
  1276.             az_trace($pad."   $key = $item");
  1277.         }
  1278.     }
  1279. }
  1280. ?>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement