Fireworks and Slot Machines

One of the themes of this website is to explore interactive communication using innovative technology. Marshall McLuhan in 1964 proposed that “The Medium is the Message”, and computing is certainly a new medium. Innovations with new HTML5 standards for web browsers, wireless communications, and Javascript programming of the document object model are all leading to new ways for making new messages.

So, in the interest of making my website more interactive, I thought to take the previously static images of my resin figure models that were on my front page and make them more dynamic by turning them into a slot machine.

 
Dsc_7573
 

Amaha Miu

 
Dsc_7919
 

Sturgeon King

 
Dsc_7567
 

Sena Airi

Yes, I make these figures


Sorry, but Javascript does not seem to be enabled. You can’t play my slots!

The slot machine implementation has six different images on each wheel, so the probability of seeing the same image on each wheel is 1/6 x 1/6 x 1/6, or one chance in 216. So, winning will not happen too frequently.

But, if you do win, then you should receive a special surprise. I thought fireworks would be appropriate. When researching a solution for this I discovered that that Paul Lewis, a new developer with Google has embraced the new HTML5 and CSS technologies. His work is moving the technology forward. What is interesting about his work is that he shows with his 3D experiments how the web browser can become the new development platform for dynamic new messaging.

Have a look. See the fireworks display.

fireworks1

What is also interesting is that you can see that the fireworks perform differently in different browsers. Animation and rendering of screens is computationally intensive.

For a description of the enhancements I made to the fireworks display, see my Fireworks page under Projects.

Fractal Image Generation

Breaking news! I have just redeveloped some of my old work with fractals.

This website is intended to explore interactive communication, so pop on over to my Fractals page under Projects. Have a go with my Fractal Image Explorer program that I just created. You can have a lot of fun with colorful and dynamic fractal images.  You may even see how some of the math behind all this magic works.

What follows are images of the Mandelbrot set. This is a rendering of the math that make fractal images possible.  Fractals contain self-replicating parts of themselves the further you look into them.  It’s like swimming into a vortex to infinity!  The image on the left is what is usually shown for the Mandelbrot set.  The image on the right is the same set but generated with very crude and limited detail.  The center image is a colored variation of a magnified fractal copy of the set in the upper filament.

Mandelbrot1 Mandelbrot2  Mandelbrot5

The images that now follow are mainly Julia set images that are all derived from the Mandelbrot set.  This type of art can be very colorful and beautiful.  The last image is taken from the Mandelbrot set.  All of these images were created by my fractal program.

 
Julia_-2.1421876_0.0
 

Julia Sets

 
Julia_-0.5583479_-0.64551014
 

Julia Sets

 
Julia_-0.6328125_-0.384375
 

Julia Sets

 
Julia_0.3222_0.02871
 

Julia Sets

 
Julia_-0.5887181_-0.45027298
 

Julia Sets

 
Mandelbrot8
 

Mandelbrot Sets

 

W3TC Cache Preload for Version 0.9.2.5

Back in March 2012 I posted a set of feature enhancements and repairs to cache priming for the WordPress W3TC Total Cache plugin version 0.9.2.4. See my W3TC Cache Preload or Cache Prime posting.

A W3TC security fix was recently released. Unfortunately, the fix introduced a fault with basic mode disk caching. A cached page is now being prefixed with a nine byte ‘<?php /*’ text string. This text is inserted before the page expiry time and compressed page data is written. What this means is that the page expiry time is never picked up successfully when the cache page is referenced. The page is always thought to be expired and cached disk pages are never returned. There is also a problem with decoding of the returned data.

w3-total-cache/lib/W3/Cache/File.php

/**
  * Sets data
  *
  * @param string $key
  * @param mixed $var
  * @param integer $expire
  * @return boolean
  */
 function set($key, &$var, $expire = 0) {
.
.
.
    if ($fp) {
      if ($this->_locking) {
         @flock($fp, LOCK_EX);
      }
//    @fputs($fp, '<?php /* ');   // wrong place ...
      @fputs($fp, pack('L', $expire));
      @fputs($fp, '<?php /* ');   // it needs to go here ...
      @fputs($fp, @serialize($var));
      @fclose($fp);
.
.
.
 /**
  * Returns data
  *
  * @param string $key
  * @return mixed
  */
 function get($key) {
.
.
.
    if ($ftime > time() - $expire) {
      $data = '';
.
      while (!@feof($fp)) {
        $data .= @fread($fp, 4096);
      }
.                            
//    $var = substr($data, 9);   // and this is wrong ...
      $data = substr($data, 9);  // it needs to be this ...
      $var = @unserialize($data);
   }
.
.
.

 

A corrected version of W3TC 0.9.2.5 that also integrates my previous cache prime feature enhancements can be downloaded here.

Updated Elements

  • w3-total-cache/lib/W3/Config.php
  • w3-total-cache/lib/W3/PgCache.php
  • w3-total-cache/lib/W3/Plugin/PgCacheAdmin.php
  • w3-total-cache/lib/W3/Plugin/TotalCacheAdmin.php
  • w3-total-cache/lib/W3/Plugin/PgCache.php
  • w3-total-cache/lib/W3/Cache/Base.php
  • w3-total-cache/lib/W3/Cache/File.php
  • w3-total-cache/lib/W3/Cache/File/Generic.php
  • w3-total-cache/inc/functions/http.php
  • w3-total-cache/inc/options/pgcache.php

 

Update, January 6, 2013

Apparently, folks are actually using this feature?  This has prompted me to complete some outstanding items from my cache prime feature enhancement last March, 2012.

  1. The cache prime trace name is now configurable.  The trace resource is publicly accessible from your site URL.  You need a way to turn this on or off.
  2. Cache priming will now generate a new cached page before a previously cached page expires.  Previously, pages were not refreshed until after they had expired.  This change can reduce the probability that an uncached page reference occurs. Pages may now be generated within two update intervals before expected expiry.

The download file referenced above now contains these changes.  The recommended steps to install this feature enhancement on a working W3TC 0.9.2.4 or W3TC 0.9.2.5 implementation are as follows:

  1. Turn off cache priming.  In the Page Cache configuration page clear the checkbox to automatically prime the page cache.  Save all settings.
  2. Empty all cache.
  3. Deactivate the W3TC 0.9.2.4 plugin in WordPress.
  4. Update the plugin code.  Extract the zip file.  Replace the w3-total-cache plugin folder in WordPress with the w3-total-cache folder from the zip file.
  5. Activate the W3TC 0.9.2.5 plugin in WordPress.
  6. Empty all cache again.
  7. Review the Page Cache configuration.  Activate cache priming if desired.

w3tc_prime2

The default TTL (Time to Live) of page cache files is set via the “Expires header lifetime” field in the “HTML” section on Browser Cache Settings tab.  This is 3600 seconds, or one hour.

Specific page expiry times can be set in the Advanced section of the Page Cache configuration. W3TC limits maximum disk cache expiry time to 30 days, or 2592000 seconds.

The example below specifies that any page URL ending with “html” is to be cached for 30 days; any page URL that contain any two digit number between slashes is to be cached for 7 days; and any page URL that begins with “/v/” or “/tag/” or “/category/” is to be cached for 24 hours.  The first identified match in the listed order is used.  If no match is found then the default TTL is set.

w3tc_prime3

W3TC Cache Preload or Cache Prime

W3TC Total Cache is a WordPress plugin for page caching of WordPress sites.  Release version 0.9.2.4 has four faults in the cache preload function that prevent successful operation of this feature.  This post provides code corrections for these faults. One additional fault that can result in failure to process URL redirection for page requests is also fixed.

This post also introduces new features to ensure that pages are primed only when required; it provides an enhancement for visible monitoring and tracing cache preload activities; and it enables setting different cache retention times depending on the page URL.

This post applies to both basic mode and enhanced mode disk caching. Cache priming has been primarily tested for basic mode disk caching.

 

Index

This post has 4 pages.  Page navigation is at the bottom of each page.

Page 1.  Describes the known faults or bugs in W3TC Total Cache version 0.9.2.4 preload function.

Page 2.  Describes a feature enhancement to enable setting of specific page cache expiry times.

Page 3.  Describes a feature enhancement to allow administrator control and monitoring of prime activities.

Page 4.  Describes a feature enhancement to prime only uncached and valid pages.

 

 Download

A full download of W3TC Total Cache version 0.9.2.4 with corrected source code as shown is available here.

 

Introduction

Cache preload, also known as cache priming, is a caching feature to ensure that a page is always ready and stored in cache.  Cached pages improve site performance.  Preloading of cached pages ensures that any initial access to a page will respond as quickly as repeat access to a page.

W3TC cache preload operates as a background activity scheduled by the WordPress cron function.  The priming activity is invoked only after the cron time expires and the WordPress site is accessed.  This means that once the cron time expires the next prime activity runs only after the site is next accessed.

When the prime activity runs it will preload a specified number of pages.  Before preloading any pages it schedules a new instance of the preload activity on the WordPress cron timer.  Therefore, as the prime activity is executing while the next activity is waiting, it is necessary to ensure that the cron timer wait time is longer than the execution time of the prime activity.  Typically, the W3TC update interval or cron wait time should be in the order of 5 or 10 minutes, and the pages processed per interval should be around 10 or 20 so that the total page access time for all pages should be less than a minute or two.

 

Fault 1

In PgCacheAdmin.php the function ‘w3_url_format’ is called prior to initiating the HTTP request to load the page. The existing code does not include a reference to the location of this function for dynamic linking to the function definition. This means that the execution silently fails after the next periodic scheduling of the prime function. Page priming will appear to be operating, events will be queued, but no cache files will ever be loaded. The missing statement is shown in a different color.

 

w3-total-cache/lib/W3/Plugin/PgCacheAdmin.php

.
.
.
    /**
     * Prime cache
     *
     * @param integer $start
     * @return void
     */
    function prime($start = 0) {
.
.
.
        /**
         * Make HTTP requests and prime cache
         */
        require_once W3TC_INC_DIR . '/functions/http.php';
       	require_once W3TC_INC_DIR . '/functions/url.php';

        foreach ($queue as $url) {
            $url = w3_url_format($url, array('w3tc_preload' => 1));

            w3_http_get($url);
        }
.
.
.

 

Fault 2

The second problem with the released implementation is that the ‘w3_url_format’ function adds the query string ‘w3tc_preload = 1’ to the page URL. In many cases the W3TC implementation may be configured to not cache URLs with query strings, thus the requested page will never be cached.

One solution to this fault is to remove the query string from URL wherever it may cause incorrect behaviour. Page caching is determined in function PgCache.php, in two places. One is in function ‘process()’ where the page request is examined for caching, and the other is in function ‘_can_cache()’ where the URL is examined for a query string. The requred statements to eliminate the query string are shown in a different color.

w3-total-cache/lib/W3/PgCache.php

.
.
.
    /**
     * Do cache logic
     */
    function process() {
.
.
.
        if ($this->_caching && !$this->_enhanced_mode) {
            $cache = & $this->_get_cache();

            /**
             * Remove preload query string on URL to cache
             */
            $this->_request_uri = preg_replace('~[?&]w3tc_preload.*~i', '', $this->_request_uri);

            $mobile_group = $this->_get_mobile_group();
            $referrer_group = $this->_get_referrer_group();
            $encryption = $this->_get_encryption();
            $compression = $this->_get_compression();
            $raw = !$compression;
            $this->_page_key = $this->_get_page_key($this->_request_uri, $mobile_group, $referrer_group, $encryption, $compression);
.
.
.
    /**
     * Checks if can we do cache logic
     *
     * @return boolean
     */
    function _can_cache() {
.
.
.
        /**
         * Skip if there is query in the request uri
         */
        $uri = preg_replace('~[?&]w3tc_preload.*~i', '', $this->_request_uri);
        if (!$this->_config->get_boolean('pgcache.cache.query') && strstr($uri, '?') !== false) {
            $this->cache_reject_reason = 'Requested URI contains query';

            return false;
        }
.
.
.

 

Fault 3

The third fault with the released code relates to the ‘w3_http_get($url)’ function call shown above. This function is defined in w3-total-cache/inc/functions/http.php and it calls a more general ‘w3_http_request’ function that calls a WordPress function to get the requested page. However, the ‘w3_http_request’ function sets a ‘W3TC_POWERED_BY’ user agent which is subsequently recognized in ‘PgCache.php’ as a rejected user agent for caching.

What this means is that any request to cache a page will return an unprocessed or non-minimized page. This can reduce the effect of page caching and results in unprocessed text being stored for the cached page. This unprocessed text is not minimized and does not include W3TC information and is different than what is cached under normal operating conditions.

A solution to this problem is to ensure that the call to ‘w3_http_request’ overrides the ‘W3TC_POWERED_BY’ user agent. The corrected code is shown below.

 

w3-total-cache/lib/W3/Plugin/PgCacheAdmin.php

.
.
.
    /**
     * Prime cache
     *
     * @param integer $start
     * @return void
     */
    function prime($start = 0) {
.
.
.
        /**
         * Make HTTP requests and prime cache
         */
        require_once W3TC_INC_DIR . '/functions/http.php';
       	require_once W3TC_INC_DIR . '/functions/url.php';

        foreach ($queue as $url) {
            $url = w3_url_format($url, array('w3tc_preload' => 1));

            $result = w3_http_get($url, array('user-agent' => ''));

        }
.
.
.

 

Fault 4

The fourth fault relates to the ‘function prime($start = 0)’ parameter in the prime function shown above. The W3TC cache preload feature is designed to load sets of pages from URLs in a prioritized Google sitemap. The start parameter is a starting index into the sitemap list of URLs to prime. It is intended to identify the start of the next group of pages to be preloaded.

Due to the parameter omission in ‘w3-total-cache/lib/W3/Plugin/PgCache.php’ the start parameter is never being passed to the prime() function. Thus, by default, the start value is always being initialized to zero. This means that the prime function can never process all the required pages in the sitemap and if it actually ran as intended it would instead always reprocess only the first selected set of pages. The missing parameter is shown in a different color.

 

w3-total-cache/lib/W3/Plugin/PgCache.php

.
.
.
    /**
     * Prime cache
     *
     * @param integer $start
     * @return void
     */
    function prime($start = 0) {
        $this->get_admin()->prime($start);
    }
.
.
.

 

CURL Redirection Fault

Sites that are hosted on servers that use CURL when safe mode or open_basedir is enabled can experience URL redirection failure when trying to prime a page. Fortunately, a workaround for the CURL problem is known. This workaround must be included in the W3TC ‘w3_http_get()’ function to capture any WordPress redirection failure to recover from the problem. If this is not done then primed URLs in the sitemap that redirect may not successfully load. A WordPress ‘Too many redirects’ error may occur.

 

w3-total-cache/inc/functions/http.php

.
.
.
    /**
     * Sends HTTP GET request
     *
     * @param string $url
     * @param array $args
     * @return array|WP_Error
     */
    function w3_http_get($url, $args = array()) {
        $args = array_merge($args, array(
            'method' => 'GET'
        ));

        $result = w3_http_request($url, $args);

        // If server uses cURL and has open_basedir set then redirection may not work 
        if ( is_wp_error($result) && stripos($result->get_error_message(),'Too many redirects') !== false) { 
            if (!ini_get('safe_mode') && !ini_get('open_basedir')) { 
                return $result; 
            } 
            if ( function_exists( 'curl_init' ) && function_exists( 'curl_exec' ) ) { 
                $result = w3_curl($url, $args); 
            } 
        } 
        return $result; 
    } 

    /** 
     * Sends HTTP GET request with cURL 
     * 
     * @param string $url 
     * @param $args array 
     * @return array|WP_Error 
     */ 
    function w3_curl($url, $args = array()) { 
        $args = array_merge(array( 'redirection' => 5 ), $args); 
        $ch = curl_init($url); 
        curl_setopt ($ch, CURLOPT_URL, $url); 

        //follow on location problems 
        $syn = w3_curl_redir_exec($ch, $args); 
        curl_close($ch); 
        return $syn; 
    } 

    /** 
     * Redirects HTTP GET request with cURL when safe_mode or open_basedir is enabled 
     * 
     * @param string $url 
     * @param $args array 
     * @return array|WP_Error 
     * 
     * courtesy of http://au.php.net/manual/ro/function.curl-setopt.php#71313 
     */ 
    function w3_curl_redir_exec($ch, $args) {
	$defaults = array(
		'method' => 'GET', 'timeout' => 5,
		'redirection' => 5, 'httpversion' => '1.0'
	);

	$r = wp_parse_args( $args, $defaults );
	if ( $r['redirection']-- <= 0 ) {
		return new WP_Error('http_request_failed', __('W3 Too many redirects.'));
	}
	
	$timeout = (int) ceil( $r['timeout'] );
	curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout );
	curl_setopt($ch, CURLOPT_TIMEOUT, $timeout );
	curl_setopt($ch, CURLOPT_HEADER, true);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
	$data = curl_exec($ch);
	
	list($header, $body) = explode("nr", $data, 2);
	$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
	
	if ($http_code == 301 || $http_code == 302) {
		$matches = array();
		preg_match('/Location:(.*?)n/', $header, $matches);
		$url = @parse_url(trim(array_pop($matches)));
		
		if (!$url) {
			//couldn't process the url to redirect to
			return new WP_Error('http_request_failed', __('W3 Redirect malformed URL'));
		}
		
		$last_url = parse_url(curl_getinfo($ch, CURLINFO_EFFECTIVE_URL));
		if (!$url['scheme'])
			$url['scheme'] = $last_url['scheme'];
		if (!$url['host'])
			$url['host'] = $last_url['host'];
		if (!$url['path'])
			$url['path'] = $last_url['path'];
		$new_url = $url['scheme'] . '://' . $url['host'] . $url['path'] . ($url['query']?'?'.$url['query']:'');
		
		curl_setopt($ch, CURLOPT_URL, $new_url);
		return w3_curl_redir_exec($ch,$r);
	}
	
	$response = array();
	$response['code'] = $http_code;
	$response['message'] = get_status_header_desc($response['code']);
	return array( 'headers' => array(), 'body' => $body, 'response' => $response, 'cookies' => array() );
    }
.
.
.

Hackers or Gremlins

Happy New Year! Well, 2012 has begun with a bang. Shortly after noon on January 2 all my Gallery 2 data was trashed unexpectedly. Now, this is either hackers, or someone doing something on my hosted web service site, or some unseen glitch in the Gallery 2 software.

This must be a sign that I need to spend some time to upgrade to Gallery 3. I has been planning to rebuild my Gallery implementation anyway, so this is not necessarily all bad. I want to centralize on a standard maximum image size and reload the albumns.

It’s time to do this. Please be patient as this work begins. Page caching is off for the moment so performance may suffer.

 

January 3, 2012: Gallery 3 is not installing. It fails with a 500 server error code attempting to run the install script. The Apache version on my hosting service is version 2.0.52 and Gallery 3 requires Apache 2.2. This may be the issue.

Gallery 2 is being reinstalled. Let’s just think of this as a major engine overhall.

 

January 4, 2012: I found this in the Gallery2 security section while doing some research yesterday.

A shared webserver that runs all PHP scripts under the same generic user and not under the specific account’s user can only be secured in a limited way.

Reality check: Most shared webhosting plans fall into the above category and we hear from incidents like a Gallery 2 that was somehow deleted over night only very rarely, maybe once a year. So it isn’t as bad as it sounds. The chance being on a webhost with a malicious customer should be really small.

 

January 6, 2012: All the China photos, Utah photos, and PKking resin model construction photos are restored. One benefit of this work is that all images now contain EXIF information and will have consistent display sizes. One liability of this restoration is that all descriptions and keyword tags for the images have been lost. I will try to recover descriptive inforormation for the gallery folders.

 

January 8, 2012: Resin model photographs are being restored. Please be patient.

 

Gallery2 MiniSlideShow Performance Cache

MiniSlideShow is an interesting Flash plugin to display Gallery2 images on your website. It uses an XML RSS feed to access Gallery2 albums. It also uses Gallery2 to return the correct size image resolution for embedded slideshows. MiniSlideShow is being used in the sidebars on Kisekae World.

One problem with this solution is that generating an RSS feed takes time. The generation delay impacts the start of the slideshow, particularly if the slideshow is included as a sidebar block on every Gallery page or embedded application page. This can be particularly noticable if the feed contains many images. Each display of a new page results is a new invokation of the slideshow and this results in a new initialization of the RSS feed.

One way to mitigate the feed generation performance loss is to cache the generated RSS feed XML text. For example, if the album feed has been previously generated and saved then it can be referenced on a new page load without the overhead of regenerating the RSS feed. This can provide some performance relief.

Gallery2 provides disk caching services as part of its core module. I have modified the xml() function in mediaRSS.php distributed with MiniSlideShow to use the Gallery2 cache functions. The code assumes that a new ‘minislideshow’ module has been installed as a Gallery2 module as described further below. For caching, the Gallery2 itemId is used as the cache key, and each cached RSS text entity will expire after 3600 seconds (1 hour). All new lines inserted into the function are shown in a different color.

 

mediaRss.php

<?php
// +---------------------------------------------------------------------------+
// |  E2  XML Audio/Video Player/Minislideshow for Gallery2                    |
// +---------------------------------------------------------------------------+
// | mediaRss.php     [v.3.0.0]                                                |
// +---------------------------------------------------------------------------+
// | Copyright (C) 2009 Wayne Patterson [suprsidr@flashyourweb.com]            |
// +---------------------------------------------------------------------------+
// |                                                                           |
// | This program is free software; you can redistribute it and/or             |
// | modify it under the terms of the GNU General Public License               |
// | as published by the Free Software Foundation; either version 2            |
// | of the License, or (at your option) any later version.                    |
// |                                                                           |
// | This program is distributed in the hope that it will be useful,           |
// | but WITHOUT ANY WARRANTY; without even the implied warranty of            |
// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             |
// | GNU General Public License for more details.                              |
// |                                                                           |
// | You should have received a copy of the GNU General Public License         |
// | along with this program; if not, write to the Free Software Foundation,   |
// | Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.           |
// |                                                                           |
// +---------------------------------------------------------------------------+
//

.
. [snipped code]
.

    function xml() {
	init();
	global $gallery, $userId, $count;


	// Determine cache item	
	if (isset ($_REQUEST['g2_itemId'])) {
	    $cacheId = $_REQUEST['g2_itemId'];
	} else if (isset ($_REQUEST['g2_albumId'])) {
	    $cacheId = $_REQUEST['g2_albumId'];
	} else {
		$cacheId = getRoot() ;
	}
	
	// Retrieve cache item if not expired	
	if (isset($cacheId)) {
		$expire = 3600;		   
		$cacheMsg = "n";
		$cachePathInfo = array('type' => 'module-data',
			   'module' => 'minislideshow',
			   'itemId' => $cacheId);
		list ($xmltime, $xmldata) =
			GalleryDataCache::getFromDisk($cachePathInfo);
		if (isset($xmltime) && isset($xmldata)) {
			$vm = $gallery->getPhpVm();
			$cacbeTime = date('U', $vm->time());
			$diff = (int)$cacbeTime - (int)$xmltime;
			$expiretime = (int)$xmltime + $expire;
			if ($diff < $expire) {
				$xmldata .= "nRetrieved itemId " . $cacheId . " at " . $cacbeTime . " that was cached at " . $xmltime 
					. " and will expire at " . $expiretime;
				echo $xmldata;
				return;
			} else {
				$cacheMsg .= "Cache expired, item cached at " . $xmltime . ", current time " . $cacbeTime 
					. ", item expired at " . $expiretime . "n" ;
			}
		} else {
			$cacheMsg .= "Cache not found, itemId " . $cacheId . "n";
		}
	}

	
	$title = '';
	$recursive = '';
	if (!$userId) {
	    $userId = $gallery->getActiveUserId();
	}
	if (!$userId) {
	    list($ret,$userId) = GalleryCoreApi::getAnonymousUserId();
	}
	if (isset ($_REQUEST['mode'])) {
	    $mode = $_REQUEST['mode'];
	}else{
		$mode = '';
	}
	if (isset ($_REQUEST['g2_itemId'])) {
	    $g2_itemId = $_REQUEST['g2_itemId'];
	    list ($ret, $item) = GalleryCoreApi::loadEntitiesById($g2_itemId, 'GalleryAlbumItem');
	    if ($ret) {
	        print "Error loading initial item:" . $ret->getAsHtml();
	    }
	    $title = getTitle($item);
	}else{
	    $title = "Gallery2 MediaRss";
	}
	if (isset ($_REQUEST['g2_view'])) {
	    $g2_view = $_REQUEST['g2_view'];
	}
	if (isset ($_REQUEST['mime'])) {
	    $mime = $_REQUEST['mime'];
	}
	if (isset ($_REQUEST['recursive'])) {
	    $recursive = $_REQUEST['recursive'];
	}
	if(isset ($_REQUEST['limit'])){
		global $limit;
		$limit = $_REQUEST['limit'];
	}
	$xml = '';
	$count = 0;
	$urlGenerator =& $gallery->getUrlGenerator();
	$link = $urlGenerator->generateUrl(array(), array('forceFullUrl' => true));
	$vm = $gallery->getPhpVm();
	list ($ret, $language) = GalleryTranslator::getDefaultLanguageCode( );
	if ($ret) {
	    $language = "en-us";
	}
	if (!$vm->headers_sent()) {
	    $vm->header('Content-Type: application/rss+xml; charset=UTF-8');
	}
	echo "<?xml version="1.0" encoding="UTF-8" ?>n";	
	$xml .= "<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/">n";
	$xml .= "    <channel>n";
	$xml .= "        <title>" . cdata($title) . "</title>n";
	$xml .= "        <link>" . $link . "</link>n";
	$xml .= "        <description>" . cdata($title) . "</description>n";
	$xml .= "        <language>" .$language. "</language>n";
	$xml .= "        <generator>FlashYourWeb MediaRSS Generator v3.0.0</generator>n";
	$xml .= "        <lastBuildDate>" . date('r', $vm->time()) . "</lastBuildDate>n";
	$xml .= "        <ttl>120</ttl>n";
	if(isset($g2_itemId)){
		$xml .= getAlbumList ($g2_itemId);
	}else{
		$xml .= getAlbumList (getRoot());
	}
	switch ($mode) {
	    case 'dynamic':
	        switch ($g2_view) {
	            case 'dynamicalbum.UpdatesAlbum':
	                $xml .= getDynamicChildIds($userId);
	            break;
	            case 'dynamicalbum.PopularAlbum':
	                $xml .= getDynamicChildIds($userId, 'views', 'viewCount', ORDER_DESCENDING, 'GalleryItemAttributesMap', 'itemId');
	            break;
	            case 'dynamicalbum.RandomAlbum':
	                $xml .= getDynamicChildIds($userId, 'random', 'random', ORDER_ASCENDING, null, 'id');
	            break;
	            case 'keyalbum.KeywordAlbum':
	            	$xml .= getKeywordChildIds($userId, $g2_keyword=null);
	            break;
	            case 'tags.VirtualAlbum':
	                $xml .= getTagChildIds($userId, $g2_tagName=null);
	            break;
	            default:
	            $xml .= getDynamicChildIds($userId);
	        }
	    break;
	    case 'search':
	        $xml .= getSearchItems($g2_itemId, $mime);
	    break;
	    case 'simple':
	        $xml .= itemListDisplay();
	    break;
	    default:
	        if(isset($g2_itemId) && $recursive){
	            $xml .= getItemsRecursive ($g2_itemId);
	        }else if(isset($g2_itemId)){
	            $xml .= getItems($g2_itemId);
	        }else{
	            $xml .= getItems(getRoot());
	        }
	}
	
	$xml .= "    </channel>n";
	$xml .= "</rss>";


	// Cache item for future access	
	if (isset($cacheId)) {
		$cachePath = GalleryDataCache::getCachePath($cachePathInfo);
		if (isset($cachePath)) {
			$xmltime = date('U', $vm->time());
			GalleryDataCache::putToDisk($cachePathInfo, $dataToCache = array($xmltime, $xml));
			$xml .= $cacheMsg;	
			$xml .= "Cache itemId " . $cacheId . " at " . $xmltime . " to " . $cachePath;
		}
	}


	echo $xml;
    }
xml();
?>

 

For the above solution to work there must be a ‘/g2data/cache/module/minislideshow’ directory on your web server. This is where Gallery2 stores the cached files. If this directory does not exist or disk caching is not possible then the cache modifications will fail silently. To determine if caching is functioning you need to examine the source code for the XML feed. Cache message text is inserted in the document following the RSS directives. The following three feeds below will show cache information if you use your browser to display the source text.

Example:

Cache expired, item cached at 1325184690, current time 1325189988, item expired at 1325188290
Cache itemId 3576 at 1325189988 to /home/g2data/cache/module/minislideshow/3/5/3576.dat

 

Installation

The MiniSlideShow has been packaged as a rudimentary Gallery2 module so that it can be included in the Gallery2 sidebar block. Download the MiniSlideShow Gallery2 Module and copy the ‘minislideshow’ directory to your Gallery2 modules folder.

The packaged module doesn’t install the MiniSlideShow function for you. It does include the modified ‘mediaRSS.php’ file shown above and the required ‘minislideshow.swf’ and ‘swfobject.js’ files released with MiniSlideShow. Copy these files to your Gallery2 home directory on your web server.

You need to edit the ‘MiniSlideShow.tpl’ block template file found in the ‘minislideshow/templates/blocks’ directory. See below. Change the reference URLs to refer to your Gallery2 URL of your ‘mediaRSS.php’ and the ‘minislideshow.swf’ files. You should also ensure that the ‘swfobject.js’ file is included in the <head> section of your pages with <script type=”text/javascript” src=”http://www.yoursite.com/swfobject.js”></script>

 

MiniSlideShowBlock.tpl

{*
 * $Revision: 1.5 $
 * If you want to customize this file, do not edit it directly since future upgrades
 * may overwrite it.  Instead, copy it into a new directory called "local" and edit that
 * version.  Gallery will look for that file first and use it if it exists.
 *}

<div id="slideshow" style="margin: 10px 10px 15px 0px; padding: 5px; border-style:solid; border-width:1px; background: #ffffff; text-align: center;">

{if $theme.pageType == 'album'}
	{if !empty($theme.item.title)} <span style="color: #800000; font-weight: bold;">{$theme.item.title|markup}</span><br /> {/if} Slide Show <br />
{/if}
{if $theme.pageType == 'photo'}
	{if !empty($theme.parent.title)} <span style="color: #800000; font-weight: bold;">{$theme.parent.title|markup}</span><br /> {/if} Slide Show <br />
{/if}

<div id="g2slidecontainer" style="height: 160px;">
 <div id="g2slideplayer">
    You need Flash Player and JavaScript enabled to view this item.
 </div>
</div>

<script type="text/javascript">
    var attributes = {ldelim}
      id: 'mini',
      name: 'movie'
    {rdelim};
    var params = {ldelim}
      menu: 'false',
      wmode: 'transparent',
      allowscriptaccess: 'always',
      allowfullscreen: 'true'
    {rdelim};
    var flashvars = {ldelim}
{if $theme.pageType == 'album'}
      xmlUrl: 'http://www.yoursite.com/gallery2/mediaRss.php?mode=dynamic%26g2_view=dynamicalbum.RandomAlbum%26g2_albumId={$theme.item.id}',
{/if}
{if $theme.pageType == 'photo'}
      xmlUrl: 'http://www.yoursite.com/gallery2/mediaRss.php?mode=dynamic%26g2_view=dynamicalbum.RandomAlbum%26g2_albumId={$theme.parent.id}',
{/if}
      shuffle: 'true',
      showDropShadow: 'true',
      delay: 3,
      useFull: 'true',
      showControls: 'always',
      roundedMask: 'true'
    {rdelim};
    swfobject.embedSWF("http://www.yoursite.com/gallery2/minislideshow.swf", "g2slideplayer", "160", "160", "9.0.115.0", null, flashvars, params, attributes);
</script>
</div>

 

As a Gallery2 administrator you can install the ‘minislideshow’ plugin. The installation process should create the Gallery2 module cache directory for you. If you want to include the slideshow in your Gallery2 theme add the ‘minislideshow’ as a sidebar block. If you want to view the slideshow from an external application such as WordPress, simply create a text widget with code similar to the template file above.

 

WordPress Text Widget

<div id="slideshow" style="margin: 10px 10px 15px 0px; padding: 5px; border-style:none; background: #ffffff; text-align: center;">
<span style="color: #800000; font-weight: bold;">Resin Figure Models</span><br /><span style="font-size: 0.8em; font-family: Verdana,Arial,Helvetica,sans-serif;">Slide Show </span><br />

<div id="g2slidecontainer" style="height: 160px;">
 <div id="g2slideplayer">
    You need Flash Player and JavaScript enabled to view this item.
 </div>
</div>

<script type="text/javascript">
    var attributes = {
      id: 'mini',
      name: 'movie'
    };
    var params = {
      menu: 'false',
      wmode: 'transparent',
      allowscriptaccess: 'always',
      allowfullscreen: 'true'
    };
    var flashvars = {
      xmlUrl: 'http://www.yoursite.com/gallery2/mediaRss.php?mode=dynamic%26g2_view=dynamicalbum.RandomAlbum%26g2_albumId=21930',
      shuffle: 'true',
      showDropShadow: 'true',
      delay: 3,
      useFull: 'true',
      showControls: 'always',
      roundedMask: 'true'
   };
    swfobject.embedSWF("http://www.yoursite.com/gallery2/minislideshow.swf", "g2slideplayer", "160", "160", "9.0.115.0", null, flashvars, params, attributes);
</script>
</div>

Slimbox 2 Inline Slideshow

Slimbox 2 is a lightweight jQuery implementation of the famous Lightbox 2 script that is often used to overlay images on a web page. Slimbox 2 is being used on Kisekae World.

It is possible to extend and modify the Slimbox script to automatically show all images in a set much like a slideshow. Slimbox 2 uses the jQuery Library, a CSS file, an associated Javascript file, and a few images. This new version uses the same jQuery library with the jQuery Timer extension, a slightly modified CSS file, a modified Javascript file, and two additional images.

Try the example below. Click on any image to invoke Slimbox. Start the slide show.

Dsc_0100 Dsc_0101 Dsc_0102

The modified CSS file is shown below. All modified and new lines are shown in a different color.

slimbox2.css

/* SLIMBOX */

#lbOverlay {
	position: fixed;
	z-index: 9999;
	left: 0;
	top: 0;
	width: 100%;
	height: 100%;
	background-color: #000;
	cursor: pointer;
}

#lbCenter, #lbBottomContainer {
	position: absolute;
	z-index: 9999;
	overflow: hidden;
	background-color: #fff;
}

.lbLoading {
	background: #fff url(loading.gif) no-repeat center;
}

#lbImage {
	position: absolute;
	left: 0;
	top: 0;
	border: 10px solid #fff;
	background-repeat: no-repeat;
}

#lbPrevLink, #lbNextLink {
	display: block;
	position: absolute;
	top: 0;
	width: 50%;
	outline: none;
}

#lbPrevLink {
	left: 0;
}

#lbPrevLink:hover {
	background: transparent url(prevlabel.gif) no-repeat 0 15%;
}

#lbNextLink {
	right: 0;
}

#lbNextLink:hover {
	background: transparent url(nextlabel.gif) no-repeat 100% 15%;
}

#lbBottom {
	font-family: Verdana, Arial, Geneva, Helvetica, sans-serif;
	font-size: 10px;
	color: #666;
	line-height: 1.4em;
	text-align: left;
	border: 10px solid #fff;
	border-top-style: none;
}

#lbCloseLink {
	display: block;
	float: right;
	width: 66px;
	height: 22px;
	background: transparent url(closelabel.gif) no-repeat center;
	margin: 5px 0;
	outline: none;
}

#lbShowLink { 
        display: block; 
        float: right; 
        width: 66px; 
        height: 33px; 
        background: transparent url(showlabel.gif) no-repeat center; 
        margin: 5px 0; 
        outline: none; 
} 

#lbShowLink:hover { 
        background: transparent url(showlink.gif) no-repeat center; 
} 

#lbShow { 
        display: block; 
        float: right; 
        width: 22px; 
        height: 22px; 
        background: transparent url(showactive.gif) no-repeat center; 
        margin: 5px 0; outline: none; 
} 

#lbCaption, #lbNumber {
	margin-right: 71px;
}

#lbCaption {
	font-weight: bold;
}

 

The extended Slimbox 2 Javascript file is shown below. All modified and new lines are shown in a different color.

slimbox2.js

/*!
	Slimbox v2.04 - The ultimate lightweight Lightbox clone for jQuery
	(c) 2007-2010 Christophe Beyls 
	MIT-style license.
*/

(function($) {

	// Global variables, accessible to Slimbox only
	var win = $(window), options, images, activeImage = -1, activeURL, prevImage, nextImage, compatibleOverlay, middle, centerWidth, centerHeight,
		ie6 = !window.XMLHttpRequest, hiddenElements = [], documentElement = document.documentElement,

	// Preload images
	preload = {}, preloadPrev = new Image(), preloadNext = new Image(),

	// DOM elements
	overlay, center, image, sizer, prevLink, nextLink, bottomContainer, bottom, caption, number, slideshow, show;

	/*
		Initialization
	*/

	$(function() {
		// Append the Slimbox HTML code at the bottom of the document
		$("body").append(
			$([
				overlay = $('<div id="lbOverlay" />')[0],
				center = $('<div id="lbCenter" />')[0],
				bottomContainer = $('<div id="lbBottomContainer" />')[0]
			]).css("display", "none")
		);

		image = $('<div id="lbImage" />').appendTo(center).append(
			sizer = $('<div style="position: relative;" />').append([
				prevLink = $('<a id="lbPrevLink" href="#" />').click(previous)[0],
				nextLink = $('<a id="lbNextLink" href="#" />').click(next)[0]
			])[0]
		)[0];

		bottom = $('<div id="lbBottom" />').appendTo(bottomContainer).append([
			$('<a id="lbCloseLink" href="#" />').add(overlay).click(close)[0],
			slideshow = $('<a id="lbShowLink" href="#" />').click(show)[0],
			show = $('<div id="lbShow" />').hide()[0],
			caption = $('<div id="lbCaption" />')[0],
			number = $('<div id="lbNumber" />')[0],
			$('<div style="clear: both;" />')[0]
		])[0];
	});

	/*
		API
	*/

	// Open Slimbox with the specified parameters
	$.slimbox = function(_images, startImage, _options) {
		options = $.extend({
			loop: false,				// Allows to navigate between first and last images
			overlayOpacity: 0.8,			// 1 is opaque, 0 is completely transparent (change the color in the CSS file)
			overlayFadeDuration: 400,		// Duration of the overlay fade-in and fade-out animations (in milliseconds)
			resizeDuration: 400,			// Duration of each of the box resize animations (in milliseconds)
			resizeEasing: "swing",			// "swing" is jQuery's default easing
			initialWidth: 250,			// Initial width of the box (in pixels)
			initialHeight: 250,			// Initial height of the box (in pixels)
			imageFadeDuration: 400,			// Duration of the image fade-in animation (in milliseconds)
			captionAnimationDuration: 400,		// Duration of the caption animation (in milliseconds)
			slideShowDuration: 5000, // Duration of the slide show display (in milliseconds)
			counterText: "Image {x} of {y}",	// Translate or change as you wish, or set it to false to disable counter text for image groups
			closeKeys: [27, 88, 67],		// Array of keycodes to close Slimbox, default: Esc (27), 'x' (88), 'c' (67)
			previousKeys: [37, 80],			// Array of keycodes to navigate to the previous image, default: Left arrow (37), 'p' (80)
			nextKeys: [39, 78]			// Array of keycodes to navigate to the next image, default: Right arrow (39), 'n' (78)
		}, _options);

		// The function is called for a single image, with URL and Title as first two arguments
		if (typeof _images == "string") {
			_images = [[_images, startImage]];
			startImage = 0;
		}

		middle = win.scrollTop() + (win.height() / 2);
		centerWidth = options.initialWidth;
		centerHeight = options.initialHeight;
		$(center).css({top: Math.max(0, middle - (centerHeight / 2)), width: centerWidth, height: centerHeight, marginLeft: -centerWidth/2}).show();
		compatibleOverlay = ie6 || (overlay.currentStyle && (overlay.currentStyle.position != "fixed"));
		if (compatibleOverlay) overlay.style.position = "absolute";
		$(overlay).css("opacity", options.overlayOpacity).fadeIn(options.overlayFadeDuration);
		position();
		setup(1);

		images = _images;
		options.loop = options.loop && (images.length > 1);
		return changeImage(startImage);
	};

	/*
		options:	Optional options object, see jQuery.slimbox()
		linkMapper:	Optional function taking a link DOM element and an index as arguments and returning an array containing 2 elements:
				the image URL and the image caption (may contain HTML)
		linksFilter:	Optional function taking a link DOM element and an index as arguments and returning true if the element is part of
				the image collection that will be shown on click, false if not. "this" refers to the element that was clicked.
				This function must always return true when the DOM element argument is "this".
	*/
	$.fn.slimbox = function(_options, linkMapper, linksFilter) {
		linkMapper = linkMapper || function(el) {
			return [el.href, el.title];
		};

		linksFilter = linksFilter || function() {
			return true;
		};

		var links = this;

		return links.unbind("click").click(function() {
			// Build the list of images that will be displayed
			var link = this, startIndex = 0, filteredLinks, i = 0, length;
			filteredLinks = $.grep(links, function(el, i) {
				return linksFilter.call(link, el, i);
			});

			// We cannot use jQuery.map() because it flattens the returned array
			for (length = filteredLinks.length; i < length; ++i) { 				if (filteredLinks[i] == link) startIndex = i; 				filteredLinks[i] = linkMapper(filteredLinks[i], i); 			} 			return $.slimbox(filteredLinks, startIndex, _options); 		}); 	}; 	/* 		Internal functions 	*/ 	function position() { 		var l = win.scrollLeft(), w = win.width(); 		$([center, bottomContainer]).css("left", l + (w / 2)); 		if (compatibleOverlay) $(overlay).css({left: l, top: win.scrollTop(), width: w, height: win.height()}); 	} 	function setup(open) { 		if (open) { 			$("object").add(ie6 ? "select" : "embed").each(function(index, el) { 				hiddenElements[index] = [el, el.style.visibility]; 				el.style.visibility = "hidden"; 			}); 		} else { 			$.each(hiddenElements, function(index, el) { 				el[0].style.visibility = el[1]; 			}); 			hiddenElements = []; 		} 		var fn = open ? "bind" : "unbind"; 		win[fn]("scroll resize", position); 		$(document)[fn]("keydown", keyDown); 	} 	function keyDown(event) { 		var code = event.keyCode, fn = $.inArray; 		// Prevent default keyboard action (like navigating inside the page) 		return (fn(code, options.closeKeys) >= 0) ? close()
			: (fn(code, options.nextKeys) >= 0) ? next()
			: (fn(code, options.previousKeys) >= 0) ? previous()
			: false;
	}

	function previous() {
		return changeImage(prevImage);
	}

	function next() {
		return changeImage(nextImage);
	}

	function show() { 
                $(show).toggle(); 
                $(slideshow).stopTime(); 
                if ( $(show).is(':visible') ) { 
                     next(); 
                     $(slideshow).everyTime(options.slideShowDuration, "slideshow", function() { next(); }); 
                } 
                return false; 
         }

	function changeImage(imageIndex) {
		if (imageIndex >= 0) {
			activeImage = imageIndex;
			activeURL = images[activeImage][0];
			prevImage = (activeImage || (options.loop ? images.length : 0)) - 1;
			nextImage = ((activeImage + 1) % images.length) || (options.loop ? 0 : -1);

			stop();
			center.className = "lbLoading";

			preload = new Image();
			preload.onload = animateBox;
			preload.src = activeURL;
		}

		return false;
	}

	function animateBox() {
		center.className = "";
		$(image).css({backgroundImage: "url(" + activeURL + ")", visibility: "hidden", display: ""});
		$(sizer).width(preload.width);
		$([sizer, prevLink, nextLink]).height(preload.height);

		$(caption).html(images[activeImage][1] || "");
		$(number).html((((images.length > 1) && options.counterText) || "").replace(/{x}/, activeImage + 1).replace(/{y}/, images.length));

		if (prevImage >= 0) preloadPrev.src = images[prevImage][0];
		if (nextImage >= 0) preloadNext.src = images[nextImage][0];

		centerWidth = image.offsetWidth;
		centerHeight = image.offsetHeight;
		var top = Math.max(0, middle - (centerHeight / 2));
		if (center.offsetHeight != centerHeight) {
			$(center).animate({height: centerHeight, top: top}, options.resizeDuration, options.resizeEasing);
		}
		if (center.offsetWidth != centerWidth) {
			$(center).animate({width: centerWidth, marginLeft: -centerWidth/2}, options.resizeDuration, options.resizeEasing);
		}
		$(center).queue(function() {
			$(bottomContainer).css({width: centerWidth, top: top + centerHeight, marginLeft: -centerWidth/2, visibility: "hidden", display: ""});
			$(image).css({display: "none", visibility: "", opacity: ""}).fadeIn(options.imageFadeDuration, animateCaption);
		});
	}

	function animateCaption() {
		if (prevImage >= 0) $(prevLink).show();
		if (nextImage >= 0) $(nextLink).show();
		if (images.length > 1) $(slideshow).show();
		$(bottom).css("marginTop", -bottom.offsetHeight).animate({marginTop: 0}, options.captionAnimationDuration);
		bottomContainer.style.visibility = "";
	}

	function stop() {
		preload.onload = null;
		preload.src = preloadPrev.src = preloadNext.src = activeURL;
		$([center, image, bottom]).stop(true);
		$([prevLink, nextLink, image, bottomContainer, slideshow]).hide();
	}

	function close() {
		if (activeImage >= 0) {
			stop();
			activeImage = prevImage = nextImage = -1;
			$(center).hide();
			$(overlay).stop().fadeOut(options.overlayFadeDuration, setup);
			$(slideshow).stopTime();
			$(show).hide();
		}

		return false;
	}

})(jQuery);

 

The image files referenced in the CSS are shown below. These images should be stored in the same directory as the CSS file.

The jQuery Library and the jQuery Timer extension can be downloaded from the jQuery distribution site. If you are running WordPress then jQuery is included in the distribution. The jQuery Timer extension is a small javascript file that you need to load.

 

jquery.timers-1.2.js

/**
 * jQuery.timers - Timer abstractions for jQuery
 * Written by Blair Mitchelmore (blair DOT mitchelmore AT gmail DOT com)
 * Licensed under the WTFPL (http://sam.zoy.org/wtfpl/).
 * Date: 2009/10/16
 *
 * @author Blair Mitchelmore
 * @version 1.2
 *
 **/

jQuery.fn.extend({
	everyTime: function(interval, label, fn, times) {
		return this.each(function() {
			jQuery.timer.add(this, interval, label, fn, times);
		});
	},
	oneTime: function(interval, label, fn) {
		return this.each(function() {
			jQuery.timer.add(this, interval, label, fn, 1);
		});
	},
	stopTime: function(label, fn) {
		return this.each(function() {
			jQuery.timer.remove(this, label, fn);
		});
	}
});

jQuery.extend({
	timer: {
		global: [],
		guid: 1,
		dataKey: "jQuery.timer",
		regex: /^([0-9]+(?:.[0-9]*)?)s*(.*s)?$/,
		powers: {
			// Yeah this is major overkill...
			'ms': 1,
			'cs': 10,
			'ds': 100,
			's': 1000,
			'das': 10000,
			'hs': 100000,
			'ks': 1000000
		},
		timeParse: function(value) {
			if (value == undefined || value == null)
				return null;
			var result = this.regex.exec(jQuery.trim(value.toString()));
			if (result[2]) {
				var num = parseFloat(result[1]);
				var mult = this.powers[result[2]] || 1;
				return num * mult;
			} else {
				return value;
			}
		},
		add: function(element, interval, label, fn, times) {
			var counter = 0;

			if (jQuery.isFunction(label)) {
				if (!times) 
					times = fn;
				fn = label;
				label = interval;
			}

			interval = jQuery.timer.timeParse(interval);

			if (typeof interval != 'number' || isNaN(interval) || interval < 0)
				return;

			if (typeof times != 'number' || isNaN(times) || times < 0)  				times = 0; 			 			times = times || 0; 			 			var timers = jQuery.data(element, this.dataKey) || jQuery.data(element, this.dataKey, {}); 			 			if (!timers[label]) 				timers[label] = {}; 			 			fn.timerID = fn.timerID || this.guid++; 			 			var handler = function() { 				if ((++counter > times && times !== 0) || fn.call(element, counter) === false)
					jQuery.timer.remove(element, label, fn);
			};

			handler.timerID = fn.timerID;

			if (!timers[label][fn.timerID])
				timers[label][fn.timerID] = window.setInterval(handler,interval);

			this.global.push( element );

		},
		remove: function(element, label, fn) {
			var timers = jQuery.data(element, this.dataKey), ret;

			if ( timers ) {

				if (!label) {
					for ( label in timers )
						this.remove(element, label, fn);
				} else if ( timers[label] ) {
					if ( fn ) {
						if ( fn.timerID ) {
							window.clearInterval(timers[label][fn.timerID]);
							delete timers[label][fn.timerID];
						}
					} else {
						for ( var fn in timers[label] ) {
							window.clearInterval(timers[label][fn]);
							delete timers[label][fn];
						}
					}

					for ( ret in timers[label] ) break;
					if ( !ret ) {
						ret = null;
						delete timers[label];
					}
				}

				for ( ret in timers ) break;
				if ( !ret ) 
					jQuery.removeData(element, this.dataKey);
			}
		}
	}
});

jQuery(window).bind("unload", function() {
	jQuery.each(jQuery.timer.global, function(index, item) {
		jQuery.timer.remove(item);
	});
});

 

Download the Slimbox 2 distribution. Modify it as shown or replace the distribution files with the code shown above. Put all javascript files in the ‘js’ directory and the CSS file in the ‘css’ directory. Store the new image files in the ‘css’ directory. Upload the directories to your web server. Include the following HTML in the <head> section of your web page.

 

<link rel="stylesheet" href="css/slimbox2.css" type="text/css" media="screen" />
<script type="text/javascript" src="js/jquery.js"></script>
<script type="text/javascript" src="js/jquery.timers-1.2.js"></script>
<script type="text/javascript" src="js/slimbox2.js"></script>

 

To include the Lightbox or Slimbox effect in your images you must include a ‘rel’ tag in a hyperlink as shown below. See the Slimbox 2 documentation for more information.

 

<a href="images/image-1.jpg" rel="lightbox-cats">image #1</a>
<a href="images/image-2.jpg" rel="lightbox-cats">image #2</a>
<a href="images/image-3.jpg" rel="lightbox-cats">image #3</a>

 

If you have extensions or improvements to this work please post a comment. It would be more efficient to use text blocks for the ‘Slide Show’ images. This would eliminate the need for server image downloads.

 

Errata – December 21, 2011.

Ooops, I forgot to include the fact that you need to include another little javascript file (autoload.js) to start the Slimbox2 function on the page. It is part of the standard Slimbox2 distribution, but I didn’t include it in the code above. So, you need this as well. Make sure you add a <script type=”text/javascript” src=”js/autoload.js”></script> in the <head> section of your web page along with the others shown above.

Or, if it makes it easier, just download this slimbox2ss.zip file where everything is packaged and there is an example HTML file that you can open in a browser.

autoload.js

// AUTOLOAD CODE BLOCK (MAY BE CHANGED OR REMOVED)
if (!/android|iphone|ipod|series60|symbian|windows ce|blackberry/i.test(navigator.userAgent)) {
	jQuery(function($) {
		$("a[rel^='lightbox']").slimbox({loop: true}, 
				function(el) { return [el.rev || el.href, el.title]; }, 
				function(el) { return (this == el) || ((this.rel.length > 8) && (this.rel == el.rel)); });
	});
}

Hide Feedjit Header and Footer

Feedjit is a useful add-on to a website for tracking visitors to your blog. Feedjit shows a little snapshot of when and where each visitor to your site came from. One can easily see visitors from around the world.

However, one difficulty with Feedjit is that it contains a ‘real-time view’ hyperlink to the Feedjit site for traffic statistics. I don’t particularly want this accessible from the front page of my site. I also don’t want the Feedjit advertising to show if I can eliminate it.

So, with a little Javascript, I managed to intercept the Feedjit HTML and modify the resulting display so that it would appear as follows:

How is it done?

The first thing was to figure out how Feedjit works. Feedjit requires that you include in your page a request to load a Feedjit javascript file. What comes back from this is script to create the necessary HTML in your page. This HTML creates an IFRAME window for the Feedjit results so that it can load the visitor information independently from your page load. The Feedjit output is put in the iframe in an HTML table element where each table row represents one visitor to your site. If we can somehow identify the iframe object and the table object in the frame then we can use some javascript to fiddle with the table and remove the top and bottom rows.

Use the Firebug console in Firefox to see the CSS and HTML that was loaded by Feedjit into the browser. This will show that the iframe name was ‘FJIframe’ and the table ID was ‘FJ_Tlist’. This is good, but please understand that this solution is just a hack that is dependent on these coded names. Once Feedjit updates their script or rebuilds their process and changes these names this hack will fail to work.

The first step is to figure out how to edit the table so it appears as we want. Assume for the moment that the Feedjit table has been rendered fully and we are coming in after the fact to fix it up. The function ‘hideFeedjit()’ shown in the code below performs the table row deletions. We must remove the top two rows of advertising and the bottom two rows to get rid of the Feedjit links to the real-time view. We will hide the rows in the table by setting their display style to none so that they take no space.

One difficulty with this solution is that the container div block has already been rendered and sized to fit the full table. When we hide the rows we end up with unused white space at the bottom of the container. To fix this we need to resize our container. This is why we have the named ‘visitor’ div block set up around the Feedjit code. We can resize this object to remove the white space from the missing rows.

The next problem to solve is a timing problem. We can only run the ‘hideFeedjit()’ function after the Feedjit server has responded to the request to load its javascript and its HTML has been created. We don’t know when this will happen. So, we run a little timing loop to periodically check if the iframe has been loaded. This is the purpose of the ‘checkIframeLoading()’ function. If we can see that the iframe is fully populated and the table in the iframe exists then we can perform our edits.

The operative test to see if the iframe is loaded is to check if readyState == ‘complete’. This works in Internet Explorer and Firefox version 3.6 or later. But, for some reason Internet Explorer versions prior to version 8 would not identify the iframe object if it was searched for by name.

The last step is to start this whole process once my web page is loaded. We activate the function as soon as it is declared but we know that the page load may not be complete. This is why we check for existance of the iframe and table objects. If they are undefined we simply wait a bit and try again.

This solution is fault tolerant. If it fails to find the required objects or delays in load times occur then the worst thing that will happen is that you will see the uncorrected Feedjit display. We also shut the timing loop down after 100 iterations or 10 seconds to be nice to our web browser. So, as web transfer delays are always possible there is no guarantee that it will hide the Feedjit links all the time, every time.

Good luck! You can see this Feedjit hack operating on the home page of Kisekae World

.

 

WordPress Text Widget Contents

Validated in Firefox, Internet Explore 8.0 and later, Chrome, and Safari

Copy this code into a WordPress text widget. Or, if you are not using WordPress or some other content management system then simply insert this code into your web page where you want the Feedjit output to appear. Note, you will have to replace the identified Feedjit script link with your own code. See the Feedjit site.

<div id="visitors">

<script type="text/javascript" src="http://feedjit.com/serve/?... ">
</script>


<script type="text/javascript">
var loop = 100 ;
function checkIframeLoading() {
   // Get a handle to the iframe element

   var iframe = window.frames['FJIframe'];
   // Check if loading is complete
    if (iframe) {
      if ( iframe.document.readyState == 'complete' ) {
        var table = iframe.document.getElementById('FJ_TList') ;
        if (table) {
           // The loading is complete, call the function we want executed once the iframe is loaded
           hideFeedjit();
           return;
        }
      }
   }

   // If we are here, it is not loaded. Set things up so we check the status again in 100 milliseconds.
   loop = loop - 1 ;
   if (loop > 0) {
      window.setTimeout('checkIframeLoading();', 100);  
   }
}
checkIframeLoading();
</script>

<script type="text/javascript">
function hideFeedjit() {
   var iframe = window.frames['FJIframe'];
   var table = iframe.document.getElementById('FJ_TList') ;
   var div = document.getElementById('visitors') ;
   var th1 = table.offsetHeight ;
   var dh1 = div.offsetHeight ;
   table.rows[0].style.display = 'none' ;
   table.rows[1].style.display = 'none' ;
   table.rows[table.rows.length-1].style.display = 'none' ;
   table.rows[table.rows.length-2].style.display = 'none' ;
   var th2 = table.offsetHeight ;
   var dh2 = dh1 - (th1-th2) ;
   div.style.height = dh2+'px' ;
}
</script>
</div>

Gallery2 Website Option Sidebar Block

My website is Lightbox enabled for image viewing and slideshow viewing of Gallery2 contents. Although the default case is to enable these Lightbox services, many website viewers may not want this. Website visitors should have the option to configure their viewing experience to suit their preference. This introduces the idea of user specific customization or configuration of their profile on my website.

To enable customization I implemented a user options section as a new Gallery2 sidebar block. The first challenge was to create a new Gallery2 block. Fortunately there is a very simple HTML page custom plugin for Gallery2 available on the Internet, from Bharat Mediratta, that can be used to do this. I didn’t care about the intended capability to link to HTML pages but I did want the ability to establish a sidebar block. I wanted to display checkbox options in the sidebar that the user could modify. This sidebar block would be shown on all Gallery2 pages so I also needed some way to retain the settings of these checkboxes as the user traversed the gallery pages.

To do this, I had to maintain the user option state in session cookies. The cookies provide persistance to the option values across individual page changes within one session and also across different sessions. I had two Lightbox options custom configured in my Matrix theme that could be used for initial settings. One was a global Lightbox option that identified if Lightbox services were available, and the second identified if Lightbox slideshow capabilities were available. If these options were set in the theme and if cookie values did not exist then I could assume that the checkbox options should be set, otherwise the checkbox option value would be the cookie value. For persistance the cookies would be retained within the browser for five days.

The Gallery2 sidebar block contents, found in the HtmlBlock.tpl template file in the plugin blocks directory, were replaced with the following Javascript:

templates/blocks/HtmlBlock.tpl

{*
 * $Revision: 1.5 $
 * If you want to customize this file, do not edit it directly since future upgrades
 * may overwrite it.  Instead, copy it into a new directory called "local" and edit that
 * version.  Gallery will look for that file first and use it if it exists.
 *}
{if ($theme.params.lightbox == 1)} 
    <script type="text/javascript">
      // <![CDATA[
      function set(s) {ldelim}
        var x = readCookie(s) ;
        if (!x) return ;
        eraseCookie(s) ;
        (x.charAt(0)=='1') ? x = "0" : x = "1" ;
        createCookie(s,x,5) ;
      {rdelim}

	function createCookie(name,value,days) {ldelim}
	  if (days) {ldelim}
		var date = new Date();
		date.setTime(date.getTime()+(days*24*60*60*1000));
		var expires = "; expires="+date.toGMTString();
	  {rdelim}
	  else var expires = "";
	  document.cookie = name+"="+value+expires+"; path=/";
	{rdelim}

	function readCookie(name) {ldelim}
	  var nameEQ = name + "=";
	  var ca = document.cookie.split(';');
	  for(var i=0;i < ca.length;i++) {ldelim}
		var c = ca[i];
		while (c.charAt(0)==' ') c = c.substring(1,c.length);
		if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
	  {rdelim}
        if (name == "cb1") return {if ($theme.params.lightbox == 1)} "1" ; {else} "0" ; {/if}
        if (name == "cb2") return {if ($theme.params.autoLightbox == 1)} "1" ; {else} "0" ; {/if}
	  return null;
	{rdelim}

	function eraseCookie(name) {ldelim}
	  createCookie(name,"",-1);
	{rdelim}


    document.write('<div class="{$class}">') ;
    if (readCookie('cb1') == "1")
      document.write('<input type="checkbox" id="cb1" checked="checked" onClick="set('+"'cb1'"+');"/><b>Photo Lightbox on</b><br />') ;
    else 
      document.write('<input type="checkbox" id="cb1" onClick="set('+"'cb1'"+');"/><b>Photo Lightbox on</b><br />') ;
  {if ($theme.params.autoLightbox == 1)} 
    if (readCookie('cb2') == "1")
      document.write('<input type="checkbox" id="cb2" checked="checked" onClick="set('+"'cb2'"+');"/><b>Gallery Slideshow on</b>') ;
    else 
      document.write('<input type="checkbox" id="cb2" onClick="set('+"'cb2'"+');"/><b>Gallery Slideshow on</b>') ;
  {/if}
    document.write('</div>') ;
      // ]]>
    </script>
{/if} 

Meta Tags for Gallery2 and WordPress

When Gallery2 is embedded in WordPress there is no standard capability to generate page meta descriptions and keyword tags for individual Gallery2 albums or photos.  The WPG2 plugin creates one WordPress page, the WPG2 page, in which all Gallery2 content is rendered.  Each time a Gallery2 page is rendered by WordPress we need to be able to generate unique meta tags so that search engines can index and identify our site Gallery2 pages. Without the capability to uniquely tag individual Gallery2 pages our photo galleries may not be indexed.

To generate meta tags for each WordPress Gallery2 page, the first step is to generate meta tags in the Gallery2 header.  In many cases the meta description tag is based upon the Gallery2 page description or title and the meta keyword tag is based upon the Gallery2 page keywords.  In my case I follow similar technique for the page description, but for keywords I want to automatically generate these values from the album contents if keywords were not specified.

Some postings on the Internet show how to do this. Meta tags are inserted into your Gallery2 theme.tpl file near the beginning, after the generation of the <title> directive. I have modified the code to default the keyword values from the Gallery2 summary text and album title. I do not assign Gallery2 keywords to album and photo pages. Instead, my pages use the summary text as a keyword to identify the album contents.

theme.tpl

    
    {* If Gallery doesn't provide a header, we use the album/photo title (or filename) *}
    {if empty($head.title)}
      <title>{$theme.item.title|markup:strip|default:$theme.item.pathComponent}</title>
    {/if}

    {* For meta tags, if no keywords for the page we default to use the title and summary text from all children in the page*}
    {assign var="keywordlist" value=$theme.item.title|markup:strip|default:$theme.item.pathComponent}
    {if count($theme.children) > 0}
       {foreach from=$theme.children item=child}
          {if !empty($child.summary)}
            {if !empty($child.title)}
 	       {assign var="keywordlist" value=$keywordlist|cat:", "}
 	       {assign var="keywordlist" value=$keywordlist|cat:$child.title|markup:strip|entitytruncate:1000}
            {/if}
            {assign var="keywordlist" value=$keywordlist|cat:", "}
	    {assign var="keywordlist" value=$keywordlist|cat:$child.summary|markup:strip|entitytruncate:1000}
         {/if}
       {/foreach}
    {/if}

    <meta name="description" content="{$theme.item.description|markup:strip|truncate:180:'...'|default:$theme.parent.title|markup:strip|default:$theme.item.pathComponent}" />
    <meta name="keywords" content="{$theme.item.keywords|markup:strip|default:$keywordlist}" />


The next step, once the <meta> tags are being generated in the Gallery2 HTML <head> section, is to write a new function in the WPG2 plugin to extract these tags from the Gallery2 page and insert them into the WordPress header. We will use this function in the WordPress header template when we want to write the Gallery2 meta tags. Note, this code isolates only the meta tags which have the ‘name=’ directive. This selects only the meta tags identified above and does not bring forward any other Gallery2 meta tags of different types that may duplicate existing WordPress tags. Edit the wpg2functions.inc file and add the following code:

wpg2functions.inc

/**
* Function Hook on WordPress Header
* WP action to add the Gallery2 meta tags to the WP header
*
* @param NULL
* @return NULL
*/
function g2_addmeta() {

	if (!defined('G2INIT')) {
		$ret = g2_login();
		if ($ret) {
			echo '<h2>' . __('Fatal G2 error', 'wpg2') . '</h2> ' . __("Here's the error from G2: ", 'wpg2') . $ret->getAsHtml();
			exit;
		}
	}

	global $g2data;

	if (defined('WPG2PAGE')) {
		// Merge Gallery2 HEAD Outputs Into WP Header 
		if (isset($g2data['headHtml'])) {
			list($g2_title, $g2_css, $g2_javascript, $g2_meta) = GalleryEmbed::parseHead($g2data['headHtml']);
			foreach ($g2_meta as $wpg2_meta) { 
				if (strpos("$wpg2_meta","name=")) { echo "$wpg2_meta rn"; }
			}
		}
	}
		
	// Add BTEV Event Message
	if (function_exists('btev_trigger_error')) {
		btev_trigger_error('WPG2 ADDED G2 HEADER TO WP HEADER', E_USER_NOTICE, __FILE__);
	}
}


Lastly, you need to find the section in your WordPress header template where meta tags are written. In my case I am using the Atahualpa theme and this theme has put some hooks into the header generation to call special functions. I updated bfa_meta_tags.php and inserted the call to our new function g2_addmeta() in the section where meta tags were generated for static and single post pages. Note, for this solution to work correctly you do not want to define meta tags or descriptions within your WordPress theme for the WPG2 page, otherwise you will generate both the theme meta tags and the Gallery2 meta tags.

functions/bfa_meta_tags.php


// META DESCRIPTION & KEYWORDS Tag for single post pages and static pages:
if ( is_single() OR is_page() ) {
	$bfa_meta_description = get_post_meta($post->ID, 'bfa_ata_meta_description', true);
	$bfa_meta_keywords = get_post_meta($post->ID, 'bfa_ata_meta_keywords', true);
	if ( $bfa_meta_description != '' ) {
		echo "<meta name="description" content="" .
    	htmlentities($bfa_meta_description,ENT_QUOTES,'UTF-8') . "" />n";
	}
	if ( $bfa_meta_keywords != '' ) {
		echo "<meta name="keywords" content="" .
    	htmlentities($bfa_meta_keywords,ENT_QUOTES,'UTF-8') . "" />n";
	}  
	// META DESCRIPTION Tag for WPG2 pages:
	g2_addmeta() ;
}