Pagination Helper for CakePHP

In one of my CMS projects, I ran across a case where the user created a post of very long content that scrolled endlessly down the page. In an effort to make the content more easily readable, I created a Pagination helper that breaks that content into discrete blocks of content with "next" and "prev" links.

The visibility of the content is controlled through script.aculo.us Effects.

The benefit of using this helper is that all of the content is still on the page, for SEO.

To use this helper, all you have to do is add "[page]" markers in your content, which indicate places where you want the content to break into a new "page".

Enable the helper in your controller:

<?php
var $helpers = array("Html""Pagination");
?>

In your view, render the paginated content:


<?php echo $pagination->paginateContent($content_for_layout); ?>

Helper

<?php
class PaginationHelper extends Helper
{
    
/**
     * Parses a string of content for [page] blocks, and replaces them with div tags for dynamic control over
     * content section visibility.
     * 
     * <a href="http://twitter.com/access">@access</a> public
     * <a href="http://twitter.com/since">@since</a> 1.0.0
     * <a href="http://twitter.com/param">@param</a> string $strContent The content to parse and paginate
     * <a href="http://twitter.com/return">@return</a> string The paginated content
     * 
     **/
    
function paginateContent($strContent)
    {
        
$strPaginated '';
        
$arrSections explode('[page]'$strContent);
        if(
count($arrSections) > 0)
        {
            for(
$i=0;$i<count($arrSections);$i++)
            {
                
$arrSections[$i] = preg_replace("/<br([^>]+)>/i"""$arrSections[$i], 1);
                
$curIndex $i 1;
                
$prevIndex $i == $curIndex 1;
                
$nextIndex $i >= count($arrSections) ? count($arrSections) : $curIndex 1;
                
                
$pageID 'page'.$curIndex;
                
$nextPage 'page'.$nextIndex;
                
$prevPage 'page'.$prevIndex;
                
                
// Handle first block
                
if($i == 0)
                {
                    
$strPaginated .= '<div id="'.$pageID.'" class="contentblock">'.$arrSections[$i];
                    if(
count($arrSections) > 1)
                    {
                                            
$strPaginated .= '<a class="pagelink" href=javascript:void(0); onclick="switchPage(\''.$nextPage.'\');">More ></a>';
                    }
                                        
$strPaginated .= '</div>';
                }
                else
                {
                    
// Hide the other blocks
                    
$strPaginated .= '<div id="'.$pageID.'" class="contentblock" style="display:none;">'.$arrSections[$i];
                    
                    if(
$curIndex count($arrSections))
                    {
                        
$strPaginated .= '<a class="pagelink" href=javascript:void(0); onclick="switchPage(\''.$nextPage.'\');">More ></a>';
                    }
                    
                    
$strPaginated .= '<a class="pagelink" href=javascript:void(0); onclick="switchPage(\''.$prevPage.'\');">< Prev</a>';
                    
                    
$strPaginated .= '</div>';
                }
            }
            
            return<<<PAGE_CODE
$strPaginated            
<script type="text/javascript">
var currentPage = 'page1';
</script>
PAGE_CODE;
            
        }
        return 
$strContent;
    
    } 
// End Function


// End Class
?>

You'll also need to add some javascript code to handle the switching of visible content blocks:

[js]
function switchPage(thePage)
{
if(window.currentPage)
{
if(thePage != window.currentPage)
{
new Effect.Fade(window.currentPage);
window.currentPage = thePage;
new Effect.Appear(thePage, {delay:0.5});
}
}
}
[/js]

[tags][/tags]

Generation rX Released!

On Saturday March 3, NH hard rock band Mindset X had a release party for their new CD "Generation rX".

I did all the artwork for the CD, and created two versions of the cover art - one in black and white for a limited release that includes a DVD of the Mindset X "Atmosphere" video, and a full color version that you see here. The cover and all artwork was created in Photoshop.

I also released a new version of the band's website to accompany the release. The website is now running the latest version of Wordpress and features plugins for media players, flickr photo galleries, and upcoming events. More features will be added soon, so visit the site often!

Support local music, and support Mindset X by purchasing this CD!


[tags]art, bands, Beyrent, Generation+rX, Mindset+X, music, photoshop, rock, website, wordpress[/tags]

Apple Movie Trailer Wordpress Widget

I found another great widget, which displays a random Apple Movie trailer, complete with movie poster.

Sadly, I ran into exactly the same problem that I did with the APOD widget - use of url file access functions is strictly verboten on my web host. So, here is the modded version of the widget to use cURL:



<?php
/*
Plugin Name: Apple Trailer Sidebar Widget
Description: Adds a sidebar widget that shows the latest movie trailers from apple.com
Author: Patrick Queisler
Version: 1.1
Author URI: <a href="http://www.sosuechtig.de</p>
<p>Version" title="http://www.sosuechtig.de</p>
<p>Version">http://www.sosuechtig.de</p>
<p>Version</p></a> 1.1 - Erich Beyrent [http://www.beyrent.net]: added cURL functions to fetch the data from Apple
*/

function widget_appletrailer_init() {

    if ( !
function_exists('register_sidebar_widget') )
        return;
        
    function 
widget_appletrailer($args) {
        
        
extract($args);
        
$options get_option('widget_appletrailer');
        
$title $options['title'];
        
        echo 
$before_widget $before_title $title $after_title;
        function 
appledotcom_parser(){
            
$myfile '';
            
$lines = <a href="http://twitter.com/file">@file</a>("http://wdirect.apple.com/trailers/home/externalA.js");
            if(
$lines)
            {
                foreach (
$lines as $line_num=> $line
                {
                    
$myfile .= stripslashes($line);
                }
            }
            else
            {
                
$ch curl_init();
                
curl_setopt($chCURLOPT_URL"http://wdirect.apple.com/trailers/home/externalA.js");
                
curl_setopt ($chCURLOPT_RETURNTRANSFER1);
                
$myfile curl_exec($ch);
                
curl_close ($ch);
            }    
            
            
$out eregi_replace("href=\"/trailers/","target=\"_blank\" href=\"http://www.apple.com/trailers/",$myfile);
            return 
$out;
        }
        function 
get_trailer(){
            echo 
"<center><script language=\"JavaScript\" type=\"text/javascript\">";
            echo 
appledotcom_parser();
            echo 
"loadRandomD();
            </script></center>"
;
        }
        
get_trailer();
        echo 
$after_widget;
    }


    function 
widget_appletrailer_control() {

        
$options get_option('widget_appletrailer');
        if ( !
is_array($options) )
            
$options = array('title'=>'');
        if ( 
$_POST['appletrailer-submit'] ) {

            
$options['title'] = strip_tags(stripslashes($_POST['appletrailer-title']));
            
update_option('widget_appletrailer'$options);
        }

        
$title htmlspecialchars($options['title'], ENT_QUOTES);
        
        echo 
'<p style="text-align:right;"><label for="appletrailer-title">Title: <input style="width: 200px;" id="appletrailer-title" name="appletrailer-title" type="text" value="'.$title.'" /></label></p>';
        echo 
'<input type="hidden" id="appletrailer-submit" name="appletrailer-submit" value="1" />';
    }
    
    
register_sidebar_widget('Apple Trailer''widget_appletrailer');
    
register_widget_control('Apple Trailer''widget_appletrailer_control'300100);
}

add_action('plugins_loaded''widget_appletrailer_init');

?>

Apple, beyrent, curl, movie, php, plugin, security, trailers, widget, wordpress, Apple, curl, movie, php, plugin, security, trailers, widget, wordpress

Astronomy Picture of the Day Widget for Wordpress

A friend of mine wanted to start his own blog, and as a major astronomy buff, he picked out the Red Planet theme. I thought a nice touch would be to add the Astronomy Picture of the Day widget to his sidebar.

However, nothing is ever as easy as it seems. The plugin failed, because url file access was disabled in the php.ini configuration on the webhost. I attempted to change this using the ini_set() function, but was unsuccessful.

Not willing to give up, I rewrote a large portion of the widget that retrieves the APOD content to use cURL instead. In a nutshell, the widget uses cURL to read the APOD page for the current day. There are two images in the page - a small version and a large version. The source of both images is parsed from the captured html, and cURL is used again to fetch the image data for the small image.

Because of the security settings, I couldn't do a getimagesize() call against the image URL directly, so I had to write the image data to a temp file first, and then I was able to get the dimensions of the image to resize it to fit within the sidebar.

Here is the plugin, also available here:

<?php

/*
Plugin Name: APOD Widget
Description: Adds a sidebar widget to display the Astronomy Picture of the Day (APOD)
Author: Paul Lamb
Version: 1.3
Author URI: <a href="http://www.digitalmeandering.com/<br />
Notes:" title="http://www.digitalmeandering.com/<br />
Notes:">http://www.digitalmeandering.com/<br />
Notes:</a>  The framework for this widget was politely borrowed, with no intent to give back, from the Google Search widget

Version 1.3 - Modified by Erich Beyrent [http://www.beyrent.net]
uses a CURL method for retrieving the APOD images, when URL file access is disabled
*/
function widget_apodwidget_init() {

// Check for the required plugin functions. This will prevent fatal
// errors occurring when you deactivate the dynamic-sidebar plugin.
if ( !function_exists('register_sidebar_widget') )
return;

function 
widget_apodwidget_returnimageandlink()
{
$apodAvailable true;
// Grab our date-time stamps
$TodayDt date(ymd);
$MonthDay date(ym);
// Set the image width to 150 pixels
$ImageWidthConst 150;
// Set the base URL for the APOD site, thanks NASA!
$URL 'http://antwrp.gsfc.nasa.gov/apod/';
// Create our HTML file name, following this format:
// 'ap060925.html' where today's date is 09/25/2006
$Filename 'ap'.$TodayDt.'.html';
// Append the file to the URL to create the full URL to today's APOD
$FullURL $URL.$Filename;

// Create the regular expressions for both the regular and large image, even though at the moment I'm not even using the large image
$RegExStringSmall 'src="image/'.$MonthDay.'/(.*)" mce_src="image/'.$MonthDay.'/(.*)"      ';
$RegExStringBig 'href="image/'.$MonthDay.'/(.*)" mce_href="image/'.$MonthDay.'/(.*)"      ';

// Regular expression to validate that we have a good image to present
$RegExImageExtension'.*(\.[Jj][Pp][Gg]|\.[Gg][Ii][Ff]|\.[Jj][Pp][Ee][Gg]|\.[Pp][Nn][Gg])';

$ch curl_init();
curl_setopt($chCURLOPT_URL$FullURL);
curl_setopt ($chCURLOPT_RETURNTRANSFER1);
$html curl_exec ($ch);
curl_close ($ch);

if(
$html == '')
{
$apodAvailable false;
}

if (
eregi ($RegExStringBig$html$big))
{
$ImageSrcBig $big[1];
}

// Check for the regular image
if (eregi ($RegExStringSmall$html$small))
{
$ImageSrcSmall $small[1];
}

// If the APOD is available, use CURL to fetch the image and write it to a temp file
if ($apodAvailable)
{
// Create the full image URL based on what the regular expression found
$ImageUrlSmall $URL.'image/'.$MonthDay.'/'.$ImageSrcSmall;
$ImageUrlSmall preg_replace("/\">/"'',$ImageUrlSmall);

// Create the full big image URL based on what the regular expression found
$ImageUrlBig $URL.'image/'.$MonthDay.'/'.$ImageSrcBig;
$ImageUrlBig preg_replace("/\">/"'',$ImageUrlBig);

preg_match("/.*\.jpg/"$ImageUrlSmall$matches);
$ImageUrlSmall $matches[0];

preg_match("/.*\.jpg/"$ImageUrlBig$matches);
$ImageUrlBig $matches[0];

// Check to make sure the image is a compatible format
if(eregi($RegExImageExtension$ImageUrlSmall))
{
$ImageDimensionsBig = array();

// Fetch the image
$ch curl_init();
curl_setopt($chCURLOPT_URL$ImageUrlSmall);
curl_setopt ($chCURLOPT_RETURNTRANSFER1);
$image curl_exec ($ch);
curl_close ($ch);

// We need to get around the security restrictions, so we create a temp file to write the image to
$filename "temp_".mt_rand().sha1(time());
$handle = <a href="http://twitter.com/fopen">@fopen</a>('/tmp/'.$filename,"w+");
if (
$handle)
{
flock($handle,LOCK_EX);
if (
fwrite($handle,$image))
{
fclose($handle);

// Get the dimensions of the image for resizing
$ImageDimensionsBig getimagesize('/tmp/'.$filename);

//Delete the file
unlink('/tmp/'.$filename);
}
}

// We want a proportional image, so create our resize percentage based on the width
$ImageResizePercentage = ($ImageDimensionsBig[0] / $ImageWidthConst);
// Set the image width to our constant
$ImageWidthSmall = ($ImageWidthConst);
// Set the image height using the resize percentage, again porpotions are the key
$ImageHeightSmall = <a href="http://twitter.com/">@</a>($ImageDimensionsBig[1] / $ImageResizePercentage);

// Create the hyperlink to the APOD site, wrapped around the image itself, setting the target to a new window and passing in the image height and width
$ImageAndLink '<a href="'.$FullURL.'" target="_blank"><img src="'.$ImageUrlSmall.'" width="'.$ImageWidthSmall.'" height="'.$ImageHeightSmall.'"/></a>';

return 
$ImageAndLink;
}
else {
$apodAvailable false;
}
}
// The APOD wasn't available, return a message indicating as such
if (!$apodAvailable) {
return 
'APOD not available

'
;
}
}

function 
widget_apodwidget($args) {
extract($args);

// Each widget can store its own options. We keep strings here.
$options get_option('widget_apodwidget');
$title $options['title'];

echo 
$before_widget;
echo 
$before_title $title $after_title;
$ApodImageAndLink widget_apodwidget_returnimageandlink();
echo 
'
<div style="margin-top: 5px; text-align: left">'
.$ApodImageAndLink.'</div>
'
;
echo 
$after_widget;
}

// This function creates the widget control, using the built in widget abilities for controling widgets (they only get the change the title, it's no big deal)
function widget_apodwidget_control() {

// Get our options and see if we're handling a form submission.
$options get_option('widget_apodwidget');
if ( !
is_array($options) )
$options = array('title'=>__('APOD''widgets'));
if ( 
$_POST['apodwidget-submit'] ) {

// Remember to sanitize and format use input appropriately.
$options['title'] = strip_tags(stripslashes($_POST['apodwidget-title']));
update_option('widget_apodwidget'$options);
}

// Be sure you format your options to be valid HTML attributes.
$title htmlspecialchars($options['title'], ENT_QUOTES);

// Here is our little form segment. Notice that we don't need a
// complete form. This will be embedded into the existing form.
echo '
<p style="text-align: right"><label for="apodwidget-title">' 
__('Title:') . ' <input type="text" value="'.$title.'" name="apodwidget-title" id="apodwidget-title" style="width: 200px" /></label></p>
'
;
echo 
'<input type="hidden" value="1" name="apodwidget-submit" id="apodwidget-submit" />';
}

// This registers our optional widget control form. Because of this
// our widget will have a button that reveals a 200x100 pixel form.
register_widget_control(array('APOD''widgets'), 'widget_apodwidget_control'200100);

// This registers our widget so it appears with the other available
// widgets and can be dragged and dropped into any active sidebars.
register_sidebar_widget(array('APOD''widgets'), 'widget_apodwidget');
}

// Run our code later in case this loads prior to any required plugins.
add_action('widgets_init''widget_apodwidget_init');
?>

[tags][/tags]

AJAX Extended - FAQ

Frequently asked questions

If you can't find the information you're looking in this FAQ, please, contact me. I will be glad to help.
HELP! It doesn't work! What should I do?
Well, basically neither the server script, nor the client script does anything special that is not supported by your server or your browser. So don't worry, it should work in 99,9% cases. Here are the possible workarounds:
Make sure you installed AJAXExtended correctly
If you haven't deleted the test files from the server yet, this check is a piece of cake. Fire up you browser and type in the following address: http://www.yourwebserver.com/path-to-the-php-files/tests/ (notice the tests folder). If it shows a green box saying everything is ok, it means just that: AJAXExtended is installed properly. If it shows a red box or something else, please, check the installation instructions.
Make sure you got an error handling function
During a server-to-server request the proxy might encounter different kind of problems and it can successfully cope with some of them. So the server won't just die unexpectedly and forget about the client request. AJAXExtended allows you to handle these errors. Just like that:

var request = new XMLHTTP()
request.onerror = function(description) {
alert('Error: ' + description)
}

For instance, if the requested server is down or it's name cannot be resolved, AJAXExtended will trigger the error handler and pass on the following description: “Error occurred while connecting to the specified host”.
How does the script access other domains?
The trick is that the script doesn't use the native XMLHTTP object. It works just the same, but it employs a different technology: dynamic SCRIPT tags. It is not a security vulnerability in any way. It's an absolutely standard feature, supported by all browsers.
How does the script handle long requests?
Since the script relies on dynamic inclusion of the SCRIPT tags, all the data we upload to the server is generally a part of the SCRIPT url. Although, the specs impose no limit on the length of the GET request, all browsers do. The tests say we can be quite safe if the length of the SCRIPT url doesn't exceed 2000 symbols. If the request is longer than 2000 symbols (the limit is configurable), the script splits the request into parts.

For instance, instead of doing this:
http://ajaxextended.com/api/? ... a very-very long string that might break the browser...
we're doing this:
http://ajaxextended.com/api/? ... 1st part of this request ...
http://ajaxextended.com/api/? ... 2nd part of this request ...
In the meantime, the server caches all the parts of the request. And when all the parts are received and assembled, the server backend finally performs the server-to-server request.
How does the server cache multipart requests?
If the request is too long, the client script splits the data into multiple parts that do not exceed the specified maximum length. When a server receives a part of the multipart request it writes the data into a cache file. The name of the cache file is the same as the unique ID of the request (yes, all requests have a unique ID). When all parts are received, the script removes the cache file and performs the server-to-server request.
What is the syntax of the response?
If you've ever heard of JSON, it'll be really easy to understand how the response is constructed. Here is a real example of the reponse (getting the homempage of google.ru):

v35422275185316._parse({
'success': '1',
'responseHeaders': {
'Cache-Control': 'private',
'Content-Type': 'text/html',
'Content-Encoding': 'gzip',
'Server': 'GWS/2.1',
'Content-Length': '1852',
'Date': 'Mon, 06 Feb 2006 18:55:53 GMT',
'Connection': 'Keep-Alive'
},
'status': '200',
'statusText': 'OK',
'responseText': ' ...the code of the page...'
})

As you can see, all the data is encapsulated in curly brackets {}, that represent objects in JavaScript. Note, that both keys and values are quoted. v35422275185316 is the unique ID of the request. Here's a brief description of the properties:

success
Possible values are 0, 1. Normally, it is 1. If an error occurs during the request, it is 0.
responseHeaders
HTTP headers received from the third-party server
status
HTTP status code (200, 404, etc.)
statusText
The text that describes the HTTP status code ('OK', 'Not found', etc.)
responseText
The body of the reponse (quotes escaped)

If a fatal error occurs (the server script cannot connect to the specified website or it cannot create a cache file, or anything of the kind), the response will have only two properties:

v35422275185316._parse({
'success': '0',
'description': 'Cannot connect to www.unreachableserver.com'
})

AJAX Extended - Installation

Installation instructions
The following document outlines the process of installation of AJAXExtended to you web server. Please note, however, that in order to use these instructions, you should have some basic server administration skills. Be sure, you're comfortable with terms like file permissions, scripts, cgi-bin, sockets etc.
1. Downloading the latest copy
AJAXExtended is stored in a Subversion repository at http://www.professionalconsulting.ru/svn/ajaxextended/trunk. If you have subversion installed on your local machine, you can check out the current version by typing svn co http://www.professionalconsulting.ru/svn/ajaxextended/trunk. If you have no subversion, you can either install it or click on the link above and download all the files one-by-one with your browser. In this case, be sure to keep the same directory structure.
Package description

  • MIT-LICENSE
  • INSTALLATION.html
  • README.html
  • xmlhttp.js
  • php.server/
    • data/
      • empty
    • tests/
      • index.php
      • base.php
      • session.php
      • simpletest/
        • compatibility.php
        • dumper.php
        • errors.php
        • expectation.php
        • invoker.php
        • reflection_php4.php
        • reflection_php5.php
        • reporter.php
        • scorer.php
        • simpletest.php
        • test_case.php
        • unit_tester.php
    • xmlhttp/
      • base.php
      • session.php
      • pear/
        • json.php
        • pear.php
        • request.php
        • socket.php
        • url.php
    • index.php
  • python.server/
    • httplib.py

/xmlhttp.js — that's the javascript part of AJAXExtended. It contains all the JS classes required for the XMLHTTP magic to work.

/python.server/ — that's just a placeholder for the coming-soon Python version.

/php.server/ — this directory contains the server part of AJAXExtended (PHP version). Make sure you have all these files and directories on your computer (including the empty data directory). Some of these files (e.g. unit-testing files) won't be required for production use, but for installation purposes they're required.
2. Uploading the files to your web server

  • First of all, upload all the files contained in the /php.server/ to any directory on your webserver that is available for public access.
  • Open xmlhttp.js in any text editor and change the line that says apiURL: 'http://api.ajaxextended.com/' to apiURL: 'http://www.yourwebserver.com/path-to-the-php-files/index.php'. The 'index.php' part is optional, you can skip it if you're sure you web server will treat this file as the default index file. In any case 'index.php' should correspond with the '/php.server/index.php' package file. Save xmlhttp.js and upload it to your web server.

3. Checking the server scripts
Once you're done uploading the files, fire up your browser window and type in the following address: http://www.yourwebserver.com/path-to-the-php-files/tests/ Depending on what you see on the screen, there're several options:

  1. You see a green box that says everything's just fine. You're almost done. Skip to to next step.
  2. You see a red box that says something failed (and mentions 'testcreatingsessionfile'). Change the permissions of the /data/ directory so that the PHP script has enough priviliges to write into this directory. A common (but not the best) solution is to do chmod 777, but it might be different with your server configuration.
  3. You see the source code of the script. Your web server does not support PHP or this functionality is disabled. In this case you should refer to your web server manual.

It doesn't work and you don't have a clue?
Send me an e-mail describing the problem and don't forget to include the description of you web server, the results of script testing (probably, even a screenshot) and other relevant information. Without this information, I won't be able to help you. Usually, you can expect a reply within a day.
4. Embedding AJAXExtended in your web pages
Insert a script tag:

in every html page of your site that will use AJAXExtended.
5. Test it
Start your javascript application and make sure AJAXExtended works.
6. Delete unnecessary files
Remove the /tests/ directory from your web server.
You're done. Congratulations!
Just don't forget to get back to AJAXExtended website every once in a while to upgrade you local copy to the latest stable release.

AJAX Extended - Read Me

What is AJAXExtended?
AJAXExtended is a JS/server library that make cross-domain AJAX requests possible. It does not use any proprietary methods or security holes — it relies on a server-side proxy to fetch data from any 3rd party server.

Although a server proxy might seem a bit obvious and a way-too-simple solution, it's a bit more complicated than it seems. To emulate a cross-domain XMLHTTPRequest, a server proxy should support HTTP headers (including cookies), HTTP authentication, data re-encoding and more. AJAXExtended does just it all.

The JavaScript part of AJAXExtended has the same properties, methods and events as the native browser implementation. You don't have to rewrite your code — just plug in the AJAXExtended and you're in business.
Key features

  • Cross-domain AJAX requests
  • HTTP headers support (yes, you can manipulate cookies)
  • Works in 99%+ browsers (no matter what security restrictions apply)
  • Native syntax

How does this work?
AJAX Extended loads the data via dynamic SCRIPT tags. It doesn't use the standard XMLHTTP objects (i.e. ActiveX and XMLHttpRequest) or IFRAMEs, because these have lots of drawbacks. Just to name a few:

  • Microsoft.XMLHTTP relies on ActiveX that makes it virtually impossible to use XMLHTTP in a strict security environment.
  • Both XMLHttpRequest (Mozilla, Firefox, Safari) and Microsoft.XMLHTTP impose strict cross-domain restrictions that cannot be overridden in a cross-browser way.
  • IFRAME used to be the only solution for Opera users (and still is for older versions). It consumes lots of memory (since technically every IFRAME is a new browser window) therefore it is quite slow and it breaks the browser history.

AJAXExtended is free of all these drawbacks since it relies on a very simple technology: creating dynamic SCRIPT tags. No cross-domain or other security restrictions and no usability problems (breaking browser history). It works in all modern browsers: IE5+, Mozilla, Firefox, Opera, Safari (except a buggy 2.0 version, 2.0.2 is ok).
Security
Too many people speculate on the issue of AJAX cross-domain requests. However, there're no security implications whatsoever: no possibility of attacks, no personal data theft. No nothing. Most of those people who consider cross-domain requests (through JS) a vulnerability just don't get the idea.

Below you will find some short key points. For an in-depth overview of the underlying security issues, please, take a look at this article: AJAX greatest security myth busted.

Third-party website ruining your website
The data you get from a third-party website is not executed. It means that any malicious script that might be received from a third-party website won't ever run. Unless you explicitly order so.
Stealing personal information
You can't steal user's personal information that belongs to a third-party website (cookies, that is). The user's browser connects to the server proxy (not the third-party website), so it never sends out any cookies belonging to the third-party website. Cookies are safe. Absolutely.
Stealing secret information (from the intranet)
One thing to remember: the data is received via the server-backend. The server has the same visibility scope as any other computer on the net. So it can't get any data from the intranet or any other closed part of the net that the user has access to.
Stealing not-so-secret information
Some people argue, that using cross-domain requests you may steal content from a third-party website. But AJAX has absolutely nothing to do with it. One can just as easily copy&paste the content from any source to one's own website. It's a matter of ethics, not technology.

Interface
The JavaScript class contains all the same properties, events, and methods as the original XMLHttpRequest objects (except browser-specific methods and properties). You don't have to rewrite your code. All you have to do is to include the supplied javascript file.
Example
Instead of doing this (in Internet Explorer):
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
or this (in Firefox):
xmlhttp = new XMLHttpRequest();
with AJAXExtended you can do it like this (in any browser):
xmlhttp = new XMLHTTP();
Just don't forget to include the supplied javascript file:

Methods, properties and events

  • method abort ()
  • method addEventListener (type, listener, useCapture) not implemented
  • method dispatchEvent (event) not implemented
  • method getAllResponseHeaders ()
  • method getResponseHeader (name)
  • method open (method, url, [async, [username, [password]]])
  • method openRequest (method, url, [async, [username, [password]]])
  • method overrideMimeType (mimetype)
  • method removeEventListener (type, listener, useCapture) not implemented
  • method send ([data])
  • method setRequestHeader (name, value)
  • property status
  • property statusText
  • property responseText
  • property responseBody not implemented
  • property responseStream not implemented
  • property responseXML supported in previous version
  • property readyState
  • event onreadystatechange
  • event onerror
  • event onload
  • event onprogress not implemented

Ajax Extended

I have been using the AJAX Extended library for a little while now to accomplish some cross-domain AJAX functionality, namely in the form of a search widget that can be displayed on any domain using two lines of code. The only problem is that the documentation for this library is fairly sparse, and lately, the developer's website is showing 404 on pages for this library.

So, in the interest of helping others who are seeking access to this information, here are the documents:

READ ME

Installation

FAQ

You can download the library here.

I hope this information helps other developers out there who want to leverage this kind of functionality!

[tags]AJAX, ajaxextended, cross+domain, documentation, scripting[/tags]

MySQL Subselects As Insert Values

I use MySQL as the database backend behind most of my web projects, and I recently came across a situation where I needed to insert a foreign key into a table when I didn't know what the foreign key was. I knew I could do two queries, the first to look up the foreign key and the second to insert the data, but I wanted a more elegant solution. After poking around in the fine manual and not finding what I was looking for, I stumbled over a forum posting that indicated that I could do a nested select within my insert statement.

Here are my tables:
PropertyTypes
+------------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------+-------------+------+-----+---------+----------------+
| PropertyTypeID | int(11) | | PRI | NULL | auto_increment |
| PropertyTypeCode | char(3) | | | | |
| PropertyTypeDesc | varchar(64) | | | | |
+------------------+-------------+------+-----+---------+----------------+
Resorts
+-----------------+---------------+----------------+
| Field | Type | Extra |
+-----------------+---------------+----------------+
| ID | int(11) | auto_increment |
| Name | varchar(200) | |
| Description | text | |
| StreetAddress | varchar(255) | |
| StreetAddress2 | varchar(255) | |
| City | varchar(60) | |
| State | char(2) | |
| PostalCode | varchar(12) | |
| Country | varchar(100) | |
| Region | varchar(60) | |
| PropertyTypeID | int(11) | |
+-----------------+---------------+----------------+
And now I want to insert a new property record, but it's property type is "MOD" and I need to get the PropertyTypeID to insert as a foreign key into the Resorts table. Here's the query:

[sql]

INSERT INTO Resorts VALUES('','Some Hotel','Default description', 'Here','There','Manchester','NH','03102','USA','53',(SELECT PropertyTypeID FROM PropertyTypes WHERE PropertyTypeCode = 'MOD'));

[/sql]

The single value returned by the nested select is what gets inserted as the field value.

[tags]mysql,database, query, SQL,select,insert,nested,subselect,foreign,key,code,beyrent[/tags]

Recursively Deleting Folders

Sometimes, you run into cases where you need to delete a folder recursively throughout a directory. Case in point - CVS, or Code Versioning System, creates a CVS folder within each folder in your parent directory. In a web environment, you might end up with:

li{list-style:none;}

  • \css
    • \CVS
  • \images
    • \CVS
    • \gui
      • \CVS

And so on. Manually removing these folders is about as much fun as a skin graft, so I wrote a quick and dirty VBScript to handle it for me.
[vbs]

Option Explicit
On Error Resume Next
ShowFolderList("C:\path\to\your\folder")

Function ShowFolderList(folderspec)
'{
Dim fso, f, f1, s, sf
Set fso = CreateObject("Scripting.FileSystemObject")
Set f = fso.GetFolder(folderspec)
Set sf = f.SubFolders

For Each f1 in sf
'{
ShowFolderList(f1.path)

if strcomp(f1.name,"CVS") = 0 then
'{
fso.deletefolder f1.path, true
'Exit for
'Exit function
'}
End if
'}
Next
'}
End Function

[/vbs]
You could always modify this script to take several command line arguments representing paths to directories you'd like to clean out, improving the versatility of this script.

[tags]vbscript, cvs, delete, folder, recursive, erich, beyrent, script, windows[/tags]

About Erich

Erich is a web developer and a native New Englander who is passionate about life, the universe, and everything.

He is currently a senior Drupal developer at Harvard University, working on the IQSS OpenScholar project.  Prior to joining the team at Harvard, he was the engineering manager at CommonPlaces e-Solutions, in Hampstead, NH, contributing as the lead engineer on the Greenopolis.com and Twolia.com.

Erich is active in the Drupal community, having contributed modules and patches to the community. He presented at DrupalCon in Szeged Hungary, and co-presented at DrupalCon 2009 in Washington, DC.

Erich lives in New Hampshire with his wife, two sons, and two weimaraners.  When not writing code, Erich enjoys landscaping and woodworking.

Faceted search

Categories

Content type

Project types

Artwork Type

Artwork Tags

Recent comments

Activity Stream

May 26, 2010

  • Twitter ebeyrent tweeted "#selinux prevented #drupal from contacting my #solr server. The fix? setsebool -P httpd_can_network_connect 1" 11:10am #

May 25, 2010

  • Twitter ebeyrent tweeted "RT @Dries: RT @acquia: Hear @bjaspan participate in a @gluster webinar tomorrow - Deploying Open Source Storage Clouds http://bit.ly/cE0j7n" 7:59pm #
  • Twitter ebeyrent tweeted "First swim in the pool tonight, a refreshing 64 degrees! Now to watch the #Lost finale that I recorded! #turningintoagreatnight" 7:58pm #
  • Twitter ebeyrent tweeted "@DamienMcKenna Ever hear Lewis Black talk about MN winters? You'll make up for the heat this winter..." 8:09am #
  • Twitter ebeyrent tweeted "Dear #asshole on the train, your music sucks, turn down your goddamn #ipod. Thank you." 8:07am #
  • Twitter ebeyrent tweeted "1 accident effs up traffic for the whole morning on 93" 6:39am #

May 22, 2010

  • Twitter ebeyrent tweeted "About to start the Carter Mtn brass band concert" 7:21pm #

May 21, 2010

  • Twitter ebeyrent tweeted "beyrent.net relaunched on #drupal, replacing a #wordpress blog" 8:29pm #
  • Twitter ebeyrent tweeted "#android update to 2.1 was smooth with no problems. Sweet!" 8:26pm #

May 20, 2010

  • Twitter ebeyrent tweeted "@cpliakas It sounds like Zend Lucene as a PECL extension or running under #HipHop would be very fast, because the hash tables are cached" 1:20pm #
  • Twitter ebeyrent tweeted "@starshaped I had one for yesterday's comment about the pink hair, but decided to hold myself back..." 9:23am #
  • Twitter ebeyrent tweeted "@cpliakas LMFAO" 9:21am #
  • Twitter ebeyrent tweeted "@starshaped girl dates are hawt" 9:21am #

May 19, 2010

  • Twitter ebeyrent tweeted "@himerus Did you update it manually?" 4:25pm #
  • Twitter ebeyrent tweeted "I cannot seem to recall a spring a windy as this year, #weather" 8:36am #