Showing posts with label scripting. Show all posts
Showing posts with label scripting. Show all posts

Sunday, September 27, 2009

PHP MySQLi bind_result Empty LONGTEXT

The problem is that when using the PHP MySQLi extension with prepared statements, I was getting an empty value in one of my bound variables after using $mysqli->execute(), $stmt->bind_param(), and $stmt->fetch(). If I executed the exact same query using $mysqli->query() and subsequently using $result->fetch_object() I would get the values I was looking for.

I went as far as copying and pasting the variable names associated with the column from every place they were used in the script and comparing them to make sure there were no typos.

Now, if I leave everything exactly how it is, I can toggle this behavior by switching the data type of the problematic column between TEXT and LONGTEXT. When the column is LONGTEXT then the prepared statement returns an empty string to the bound variable. If the column is TEXT then everything works as expected.

Strange behavior to say the least. I've caught a few unresolved PHP bug reports that appear to be related to this when looking for PHP MySQLi LONGTEXT, but nothing too detailed.

In any event, we'll be able to work with a TEXT column instead of using a LONGTEXT column in this application. You should probably research the impart if could have on you before switching though. Some of the older bug reports appear to mention Wordpress users struggling with this when reading from the Wordpress database for plugins.

Wednesday, September 16, 2009

Wordpress xargs: xgettext: No such file

I was getting the following error when trying to use the Wordpress i18n-tools to generate POT files for a Wordpress plugin.

xargs: xgettext: No such file or directory
Couldn't generate POT file!


At first it didn't make sense. I couldn't figure out why it wasn't finding my plugin directory or file. It hadn't donned on me that the file which couldn't be found was actually xgettext.
I'd assumed I already had gettext installed. I know it's installed on at least one of the machines around here.

In any event, took less than 5 minutes to fix by installing gettext.

sudo apt-get install gettext

Wednesday, September 2, 2009

PHP Get Oldest File

I was looking around to make sure there wasn't some sort of built-in function in PHP now for determining the oldest file in a directory. I didn't find a built-in function that returns the oldest file, but I found plenty of user functions that were not what I was looking for.

So, I wrote my own old file getter function. The function will accept either an array of file paths or a string which will be passed to glob in order to get an array of file paths. The function returns the oldest file found.

function oldest_file($dir)
{
if(is_string($dir))
{
$dir = glob($dir);
}
$oldest = array_shift($dir);
foreach($dir as $file)
{
if(filemtime($file) < filemtime($oldest))
{
$oldest = $file;
}
}
return $oldest;
}

Monday, April 6, 2009

Digitalpoint Style "days old" phpBB3 MOD

One of the things I like about the Digitalpoint forum listings is that little gray indicator to the right of thread titles that lets you know how old a topic is in days. I don't think I've seen this handy little notice anywhere other than Digitalpoint (at least before now) and it kinda boggles my mind as to why.

Especially for phpBB3 forums, where adding it is as simple as adding a single line in three files.
Here's a screenshot of the prosilver theme with the MOD applied.

phpBB3 days old mod

And here's what needs to be done, and the order to do it so you don't get any errors during the change.

First open /language/en/viewforum.php and find the following line,
 'VIEW_FORUM_TOPICS'  => '%d topics',


After that line add this line and then save/upload the file.
 'OLD'     => 'old',


Next open /viewforum.php and find the following line,
   'TOPIC_TITLE'  => censor_text($row['topic_title']),


After that line add the following line then save/upload the file.
   'TOPIC_AGE_DAYS' => round((time() - $row['topic_time']) / 86400),


Next open /styles/prosilver/template/viewforum_body.html and find the following piece of code which will be at the end of the line it is on.
<a href="{topicrow.U_VIEW_TOPIC}" class="topictitle">{topicrow.TOPIC_TITLE}</a>


After that line add the following line then save/upload the file.
<!-- IF topicrow.TOPIC_AGE_DAYS > 0 --><small style="color:#999;"> - {topicrow.TOPIC_AGE_DAYS} <!-- IF topicrow.TOPIC_AGE_DAYS > 1 -->{L_DAYS}<!-- ELSE -->{L_DAY}<!-- ENDIF --> {L_OLD}</small><!-- ENDIF -->


Once those changes are made, clear the cache from the ACP and you should be up and running.

Sunday, November 16, 2008

PHP Robot Check

I've seen quite a few methods for determining if a request was made by a robot or search engine spider using PHP over the years, and they always have something to do with checking the User Agent. That works, but you have to keep up with User Agent strings which can get to be annoying.

I had an idea how to check for robots using PHP and htaccess today that doesn't involve a User Agent string at all. Instead it uses sessions and exploits the fact that all well behaved robots are going to request robots.txt before they request anything else.

I start by making sure my usual robots.txt file is in place. Then I upload a robots.php file to the same location as robots.txt with a tiny bit of code in it.

<?php
session_start();
$_SESSION['robot'] = 1;
echo file_get_contents('robots.txt');
exit;
?>


All that does is start a session, set a robot $_SESSION variable that I can check in subsequent scripts, then return the contents of robots.txt.

In htaccess I have the following line which transparently redirects requests to robots.txt made by visitors/spiders to robots.php, which in turn returns the contents of robots.txt

RewriteEngine on
RewriteRule robots\.txt robots.php


Now in my applications I can easily check for robots and drop things like advertisement banners to speed up page loads since spiders don't look at advertisements anyway.

<?php
session_start();
echo isset($_SESSION['robot']) ? 'ROBOT !!' : 'Not a robot';
?>


Here are some of the benefits of doing it this way.

  1. I can continue to modify robots.txt as I normally would
  2. I don't need to keep up with changing User Agent strings
  3. Checking for the existance of a session variable is quicker than performing pattern matching on a variable

Saturday, October 11, 2008

Disable Firefox Image Drag

Firefox 3 has a behavior where clicking an image and moving your mouse will start to drag a preview of that image around. The behavior even occurs with the background images for elements.

While this drag and drop behavior for images in Firefox can be really usefull for the visitor, this behavior can be a real problem if you're writing an application that depends on JS to drag around elements that contain images or have background images.

Placing the image or element inside a container and attaching event listeners to that container instead of the image or element with a background seems like an obvious answer, but it will only work for the first click. If the visitor drops the element and tries to drag it again Firefox will start up with the drag and drop behavior.

What does work though is having your mousedown event listener prevent the default action, which seems to be that drag and drop behavior.

Within the event handler you're assigning to your mousedown event, assuming you're argument list includes an event object, as is the case with most jQuery event handlers, your function declaration might look like this.

var callback = function(event){
if(event.preventDefault)
{
event.preventDefault();
}
// Other code...
}


Nice and easy.

Friday, October 10, 2008

PHP: JPEG/JPG DPI function

While working with a lot of JPEG images lately I needed to be able to set the DPI with PHP and I was having trouble finding a way to do that with the native PHP or GD functions. The stuff I did find seemed to be overly complicated or just plain wrong.

So I wrote my own DPI function for PHP that rather than doing pointless resampling simply adjusts the file header.
My needs didn't require me to do it "right", but it will handle from 1 to 255 DPI.

/*
@$jpg -- Path to a jpg file
@$dpi -- DPI to use (1-255)
*/
function set_dpi($jpg, $dpi = 163)
{
$fr = fopen($jpg, 'rb');
$fw = fopen("$jpg.temp", 'wb');

stream_set_write_buffer($fw, 0);

fwrite($fw, fread($fr, 13) . chr(1) . chr(0) . chr($dpi) . chr(0) . chr($dpi));

fseek($fr, 18);
stream_copy_to_stream($fr, $fw);

fclose($fr);
fclose($fw);

unlink($jpg);
rename("$jpg.temp", $jpg);
}

Friday, September 26, 2008

Broken Pipe: ef_unified_filter() failed

I was getting this error message in my Apache error logs while trying to setup an Apache based output filter using ExtFilterDefine and AddOutputFilter.

The error that accompanied it was
Broken pipe: apr_file_write(child input), len 0.

Not sure what either of those meant, but guessing the ef_unified_filter() failed was just a notice that the filter was broken, I tried a bunch of things and only found maybe 50 results while searching around, none of which helped.

It turns out that the problem was in the way I was defining the cmd="" argument for ExtFilterDefine. I, and the people who were having the same problem but with different commands, weren't using the full path to our command applications.

My original ExtFilterDefine directive looked like this.

ExtFilterDefine AutoWrap \
cmd="cat -"


However I needed to specify the full path to cat, which in my case looked like this.

ExtFilterDefine AutoWrap \
cmd="/bin/cat -"

Monday, August 11, 2008

Javascript Lorem Ipsum Generator

About 2 years ago I wanted a Lorem Ipsum Javascript to make adding Lorem Ipsum to webpages in progress a little less work & bandwidth consuming.
It's worked well for quite awhile now, but today seemed like a good day to revamp the script.

This new version of the script works the same way as the last version.
HTML comments are placed where Lorem Ipsum should go & the script replaces those comment nodes with text nodes at the DOM level.
Version 2 is also about 3 times faster, the comment scheme is easier to use, & there's some convience methods added.

Starting with the easier comment scheme.
Comments are placed like so depending on whether words, sentences, or paragraphs should replace the comment.
It starts with the number of units to use, followed by what type of units to use. The script is pretty forgiving about the usage of whitespace when defining these. The script also doesn't care if the unit name is pluralized or not.
<-- 2 words -->
<-- 4 sentences -->
<-- 1 paragraph -->


Ranges can also be used for the amount of units.
For instance, if somewhere between 2-5 words should be generated you could use the following comment.
The script is just as forgiving with whitespace between the range numbers as it is with the rest of the format.
<-- 2- 5 word -->


There's a main method included that will start at the <body> element & process all comment nodes in the document. This method can be called at the end of the document, or added as an event listener.
LoremIpsum.LoremIpsum();
window.addEventListener('load', LoremIpsum.LoremIpsum, false);
window.attachEvent('onload', LoremIpsum.LoremIpsum);


The processing can also be rooted at any other node by using the scanElm method.
var trunk = document.getElementById('example');
LoremIpsum.scanElm(trunk);


The script doesn't have to be used to replace comments in the document, or in a certain node.
The word, sentence, & paragraph generation methods can be called directly & will return however many units they're asked for.
The only restriction is that ranges can't be used when calling the methods directly.
If you want random numbers of units, you'll need to generate those numbers yourself.
var elm = document.getElementById('elm');
var word_count = Math.ceil(Math.random() * 10);
var ipsum = LoremIpsum.words(word_count);
elm.appendChild(document.createTextNode(ipsum));


Quite a few aliases are included for each method.
Camel notation, Underscore notation, Lowercase notation.
main & init are included for starting with the root element.
get_* schemes are included for the generation methods.
/* Convenience / aliases / etc */
LoremIpsum.main = LoremIpsum.init = LoremIpsum.LoremIpsum;
LoremIpsum.getWords = LoremIpsum.get_words = LoremIpsum.words;
LoremIpsum.getSentences = LoremIpsum.get_sentences = LoremIpsum.sentences;
LoremIpsum.getParagraphs = LoremIpsum.get_paragraphs = LoremIpsum.paragraphs;
LoremIpsum.scan_elm = LoremIpsum.scanelm = LoremIpsum.scanElm;
lorem_ipsum = loremipsum = loremIpsum = LoremIpsum;


Here's the source, it saves to about 4.5KB fully commented & uncompressed.
The methods include further documentation in the form of comments.

LoremIpsum = {
fullstop: '.', /* Character(s) to add to the end of sentences */
min_wps: 10, /* Minimum words per sentence */
max_wps: 25, /* Maximum words per sentence */
min_spp: 2, /* Minimum sentences per paragraph */
max_spp: 8, /* Maximum sentences per paragraph */

/* Word bank
Words are selected from this array randomly
to generate complete text nodes.

NOTE: This array __can__ be altered provided it remains a single-dimension array.
*/
src: new Array('a','adipiscing','amet','arcu','at','auctor','bibendum','commodo','congue','curabitur','cursus','diam','donec','duis','eget','elementum','enim','eros','et','eu','fusce','gravida','in','integer','ipsum','justo','lectus','leo','ligula','lorem','maecenas','magna','malesuada','massa','mattis','mauris','metus','molestie','morbi','nam','nec','nibh','non','nulla','odio','orci','ornare','pellentesque','pharetra','porta','porttitor','proin','quam','quisque','risus','rutrum','sagittis','sapien','sed','sem','sit','sodales','tellus','tempus','ultricies','urna','ut','vitae','vivamus','vulputate'),

/* words, getWords, get_words
@param words: Number of words to return
@param fullstop: String to append to the words, such as sentence ending punctuation.
@returns String: String of whitespace delimited words.
*/
words: function(words, fullstop){
var lastword, nextword, str, wc = (LoremIpsum.src.length - 1);
lastword = nextword = Math.round(Math.random() * wc);
str = LoremIpsum.src[lastword].charAt(0).toUpperCase() + LoremIpsum.src[lastword].substr(1);
words--;
while(words > 0){
while(lastword == nextword){
nextword = Math.round(Math.random() * wc);
}
str += (' ' + LoremIpsum.src[nextword]);
lastword = nextword;
words--;
}
return str + (fullstop ? fullstop : '');
},

/* sentences, getSentences, get_sentences
@param sentences: Number of sentences to return.
@returns String: String of sentences delimited by current value of LoremIpsum.fullstop
*/
sentences: function(sentences){
var wpsos = LoremIpsum.max_wps - LoremIpsum.min_wps;
var str = LoremIpsum.words((LoremIpsum.min_wps + Math.round(Math.random() * wpsos)), LoremIpsum.fullstop);
sentences--;
while(sentences > 0){
str += (' ' + LoremIpsum.words((LoremIpsum.min_wps + Math.round(Math.random() * wpsos)), LoremIpsum.fullstop));
sentences--;
}
return str;
},

/* paragraphs, getParagraphs, get_paragraphs
@param paragraphs: Number of paragraphs to return.
@param format: How to format each paragraph, where %s is the text.
For instance: <p class="myclass">%s</p>
@returns String: String of paragraphs formated using @format.
*/
paragraphs: function(paragraphs, format){
if(!format){
format = "%s\n\n";
}
var sppos = LoremIpsum.max_spp - LoremIpsum.min_spp;
var str = format.replace(/%s/i, LoremIpsum.sentences(LoremIpsum.min_spp + Math.round(Math.random() * sppos)));
paragraphs--;
while(paragraphs > 0){
str += format.replace(/%s/i, LoremIpsum.sentences(LoremIpsum.min_spp + Math.round(Math.random() * sppos)));
paragraphs--;
}
return str;
},

/* scanElm, scanelm, scan_elm
@param elm: Entry point, DOM Node to begin searching for comment nodes.
@returns Number: The number of comments replaced with text nodes.
*/
scanElm: function(elm){
var rc = 0;
for(var c = 0; c < elm.childNodes.length; c++) {
var child = elm.childNodes[c];
if(child.nodeType == 1){
LoremIpsum.scanElm(child);
}else if (child.nodeType == 8){
var matches = child.data.match(/^\s*(\d+(?:\s*-\s*\d+)?)\s+(words?|sentences?|paragraphs?)\s*$/i);
if(matches){
if(matches[1].indexOf('-') > -1){
var ms = matches[1].split(/\s*-\s*/);
matches[1] = Math.max(ms[0], Math.ceil(Math.random() * ms[1]));
}
child.parentNode.replaceChild(document.createTextNode(LoremIpsum[matches[2]](matches[1])), child);
rc++;
}
}
};
return rc;
},

/* LoremIpsum, main, init
Starts with the root node & recursively processes all comment nodes.
*/
LoremIpsum: function(){
var body = document.documentElement ? document.documentElement : document.body;
LoremIpsum.scanElm(body);
}
};
/* Convenience - aliases - etc */
LoremIpsum.main = LoremIpsum.init = LoremIpsum.LoremIpsum;
LoremIpsum.getWords = LoremIpsum.get_words = LoremIpsum.words;
LoremIpsum.getSentences = LoremIpsum.get_sentences = LoremIpsum.sentences;
LoremIpsum.getParagraphs = LoremIpsum.get_paragraphs = LoremIpsum.paragraphs;
LoremIpsum.scan_elm = LoremIpsum.scanelm = LoremIpsum.scanElm;
lorem_ipsum = loremipsum = loremIpsum = LoremIpsum;

Thursday, June 26, 2008

preg_replace_callback VS preg_replace

Some interesting results of a test between the preg_replace_callback function VS the preg_replace function using the e pattern modifier.

Here's the test code.
<?php
function uppers($match)
{
return strtoupper($match[0]);
}
$out = '12345678901234567890123456789012';
$strs = array();
$str_count = 1000;
while($str_count)
{
$strs[] = md5($str_count . microtime());
$str_count--;
}

foreach($strs as &$str)
{
$out = preg_replace('#[ace]+#e', 'strtoupper($0)', $str);
}

foreach($strs as &$str)
{
$out = preg_replace_callback('#[ace]+#', 'uppers', $str);
}

?>


The test was benchmarked using the profiler included in the Xdebug Debugger and Profiler Tool for PHP.
OS is Ubuntu 8.04, Apache version 2.2.8, PHP version 5.2.4
Profiler output is being viewed in KCachegrind.

Here's the callee map generated from the output.



And here's the slightly easier to understand source map.



The two numbers on the left are the percentage of the total runtime dedicated to the actions next to them.
It appears to me that the preg_replace_callback way is faster, but I would love to see how it performs on other OS's if anyone cares to do their own tests & comment.

Sunday, June 22, 2008

Unexpected '}' In Runtime-Created Function

When working the the create_function function in PHP, it's possible to get an error message similar to the following.

Parse error: syntax error, unexpected '}' in /var/www/test.php(36) : runtime-created function on line 1


Now if you're using the return from create_function in an anonymous context you might also get a message like the following.

Warning: preg_replace_callback() [function.preg-replace-callback]: Requires argument 2, '', to be a valid callback in /var/www/test.php on line 36


After getting a Parse Error in the beginning, a subsequent warning can be downright baffling since parse errors generally halt script execution. Apparently parse errors within anonymous, create_function context only halt execution within that context though.

Now when you look into that initial error and the create_function code portion thinking maybe you used a curly brace to end a function call instead of a closing parenthesis and don't see a curly brace anywhere "baffled" can't begin to explain the feeling you might have.

The reason for the initial error likely has to do with a missing semi-colon in the code argument of your create_function call. For instance if your code simply runs the arguments through a function like the following.

$str = preg_replace_callback('#[ACEGIKMOQSUWY]+#', create_function('$a', 'return strtolower($a[0])'), $str);


That seems perfectly fine, however it needs to have the semi-colon to end the function call as in the following.

$str = preg_replace_callback('#[ACEGIKMOQSUWY]+#', create_function('$a', 'return strtolower($a[0]);'), $str);


This fact can be extremely confusing if you've used preg_replace with the e modifier where this ending semi-colon isn't required.

Tip:

preg_replace_callback can be twice or more as fast as preg_replace and the e modifier.

Friday, June 20, 2008

Random Color Generation With Javascript

I'm sure there's quite a few ways to get a random color using JS, I can think of a few such as returning a random index from an array of CSS color names, or generating three random numbers between zero and 255 and formatting from there.

The following method is my favorite though because the single randomly generated integer can be easily converted into pretty much any other format or quickly returned as-is.
To illustrate this I've written the following function which will return a hexidecimal, RGB, or integer format random color depending on the argument passed to the function.

// @format (hex|rgb|null) : Format to return, default is integer
function random_color(format)
{
var rint = Math.round(0xffffff * Math.random());
switch(format)
{
case 'hex':
return ('#0' + rint.toString(16)).replace(/^#0([0-9a-f]{6})$/i, '#$1');
break;

case 'rgb':
return 'rgb(' + (rint >> 16) + ',' + (rint >> 8 & 255) + ',' + (rint & 255) + ')';
break;

default:
return rint;
break;
}
}

Tuesday, June 17, 2008

50 Random PHP5 Facts

Here's a list of 50 49 random facts about PHP.

  1. You MUST load PDO before loading any PDO drivers
  2. An iterator cannot be used with foreach by reference
  3. Cannot create references to elements of a temporary array expression
  4. Cannot create references to/from string offsets nor overloaded objects
  5. Cannot assign by reference to overloaded objects
  6. Only variable references should be returned by reference
  7. Only variables can be passed by reference
  8. Cannot return string offsets by reference
  9. Cannot use string offset as an array
  10. Cannot use object as array -- See Dans comment
  11. Cannot use a NULL value as an array
  12. Cannot use a scalar value as an array
  13. foreach() can not iterate over objects without PHP class
  14. Cannot use assign-op operators with overloaded objects nor string offsets
  15. __HALT_COMPILER() can only be used from the outermost scope
  16. Assigning the return value of new by reference is deprecated
  17. Variables passed to each() must be an array or object
  18. Constants may only evaluate to scalar values
  19. is_a() is deprecated. The "instanceof" operator should be used instead This has changed as of PHP 5.3, see Gregs comment
  20. Cannot add internal functions to return value from get_defined_functions()
  21. Cannot add user functions to return value from get_defined_functions()
  22. Static functions cannot be abstract
  23. Exceptions must be derived from the Exception base class
  24. Need to supply an object when throwing an exception
  25. Cannot declare self-referencing constants
  26. Cannot use call_user_function on objects without a class entry
  27. Cannot access self:: when no class scope is active
  28. Cannot access parent:: when no class scope is active
  29. Cannot access parent:: when current class scope has no parent
  30. add_assoc_function() is no longer supported
  31. Usage of {} to access string offsets is deprecated and will be removed in PHP 6
  32. Cannot re-assign $this
  33. __toString() must not throw an exception
  34. Can't use method return value in write context
  35. Can't use function return value in write context
  36. Multiple access type modifiers are not allowed
  37. Cannot use the final modifier on an abstract class member
  38. Default value for parameters with a class type hint can only be NULL
  39. Default value for parameters with array type hint can only be an array or NULL
  40. Cannot call __clone() method on objects - 'clone $obj' should be used instead
  41. Clone method does not require arguments
  42. Interfaces cannot not implement themselves
  43. Classes may not inherit from classes declared final
  44. Classes cannot inherit previously-inherited constants from interfaces
  45. Classes cannot implement previously-implemented interfaces
  46. Class declarations may not be nested
  47. Cannot use reserved words as class or interface names
  48. Interfaces may not include member variables
  49. Properties cannot be declared abstract
  50. Cannot declare properties final, the final modifier is allowed only for methods

Wednesday, April 30, 2008

PHP Error: function return in write context

I was presented with a strange error in PHP today,

Fatal error: Can't use function return value in write context in /var/www/script.php on line 31


This is the code I was using on line 31.

$email = empty(ini_get('sendmail_from')) ? $default : ini_get('sendmail_from');


The problem was with the call to ini_get being within a call to empty.

To get rid of the error, I needed to check for empty values returned from ini_get differently.

$email = ini_get('sendmail_from') == '' ? $default : ini_get('sendmail_from');

Saturday, March 22, 2008

MySQL ORDER BY multiple columns

Given this MySQL query where the single letter column names relate to the format argument of the PHP date function,

SELECT Y,n,j,G
FROM my_table
ORDER BY Y,n,j,G DESC
LIMIT 1


One might assume the most recent row of the following two would be returned.
In case you don't know these off the top of your head the order is,
hour, day, month, year

INSERT INTO my_table (G, j, n, Y) VALUES 
(23, 27, 10, 2007),
(0, 28, 10, 2007)


However that's not the case & the earlier of the two is actually returned.

Reason being that when a MySQL result is being ordered by multiple columns, the ASC or DESC keyword needs to be specified after each of the column names.

So the following query would return the later of the two rows.

SELECT Y,n,j,G
FROM my_table
ORDER BY Y DESC,n DESC,j DESC,G DESC
LIMIT 1

Tuesday, January 8, 2008

Javascript Random Ad Buttons/Banners

I like to think of ad banners on my blog like like the racks in the checkout lines at the grocery store, Impulse Items. I wanted to be as un-obtrusive as possible with ad banners, while still having them stand out.

In order to do this I'm using JS to display them. That way I can defer them from showing untill the content people have come here from search engines to read in the first place has already loaded.
I absolutely hate it when a site makes me stare at a blank section where the article would be while I wait for the advertisements to load. If there's another result I considered clicking on I will immediately close the tab with that blank section in it & go with the alternative instead.
I also don't want to go out of my way to force people to look at advertisements by bypassing blockers & such, doing so appears to be a waste of time.

Now that we know I don't want to intrude, but do want to nag you a little bit, here's how I'm doing it.

First I have an array with objects for all of my banners. Each object includes the alt, href, and src attributes I will need for each banner. Though for some odd reason I decided to use url to denote my href attributes here.
var impulses = new Array(
{alt:'image alt text', url:'click-through-url', src:'image-source'},
{alt:'image alt text', url:'click-through-url', src:'image-source'},
{alt:'image alt text', url:'click-through-url', src:'image-source'}
);


For the sake of simplicity there's only three in that example, but just imagine there's about twenty of those objects in that array.

Now since I don't want the order they're shown in from left to right to be too predictable I'm going cycle a random number of ad banner objects to the end.
Basicly what this code is doing, is geting a random number between zero and the number of ad banner objects there are in the impulses array, and moving the first ad banner object to the end of the array that many times.
for(var i = Math.round(Math.random() * impulses.length); i > 0; i--)
{
impulses.push(impulses.shift());
}


Next I narrow the size of the array down to the number of ad banners I want shown.
Basicly what this code is doing is randomly picking an ad banner object in the array and removing it, untill there's only as many as I want left.
while(impulses.length > 5)
{
impulses.splice(Math.round(Math.random() * (impulses.length - 1)), 1);
}


Once the ad banners that will be displayed are determined I generate the HTML to show them with, I'm using simple <a> and <img> elements for this. I like to generate it using basic data like this so it's easier to modify the actual HTML that's used in a single place later on if I need to.
for(var i = 0; i < impulses.length; i++)
{
impulses[i] = '<a href="'+impulses[i].url+'"><img alt="'+impulses[i].alt+'" src="'+impulses[i].src+'"/></a>';
}


Now with a string of ad banner HTML to use, I make use of the jQuery library to insert it amongst my pages.
var impulse = jQuery("<div id='impulses'>" + impulses.join('') + "</div>").insertBefore('#load-anim');

Wednesday, December 26, 2007

Database Storage of Apache Virtual Hosts

I came up with a workaround for storing Apache Virtual Hosts in a database. I figure it could be adapted to work on Windows using batch files, but I'm only going to go over doing it on a UNIX-like system here. Specificly Ubuntu, or Debian.

At least basic knowledge of the following is assumed.


The first thing that needs to be done is creating a database schema for storing virtual host containers. I'll stick with a simple schema for now so focus stays on Apache to Database connection.

This schema is for MySQL.
CREATE TABLE `vhosts` (
`active` BOOL NOT NULL DEFAULT '0',
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`container` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
INDEX ( `active` )
);


`container` will store the actual vhost container. For instance somthing like this.
NameVirtualHost *
<VirtualHost *>
ServerAdmin webmaster@domain.com
DocumentRoot /home/me/public_html/
</VirtualHost>


`id` is for relation to other tables. Pretty standard stuff.

`active` is an on/off switch for each virtual host. Apache will only include a virtual host from the database if it is active.


With a database table ready to be read from we can move to the part where Apache uses the data from that table.
In httpd.conf, apache#.conf, or whatever your system uses for Apaches' main configuration file there's a directive available called Include. If you edit your configuration file and look at the end of the file you may see an example of Include being used already.
If you're on a Debian system you're likely going to find somthing similar to the following.
# Include the virtual host configurations:
Include /etc/apache2/sites-enabled/


If you're not on a Debian system, you're going to need to create a directory in your Apache directory similar to that. It should be owned by root. You will also need to add an Include directive like the one shown above to your main conf file pointing to the directory you create so Apache knows to load any configuration files found in that directory.

If you are on a Debian system you're eventually going to be emptying that directory so there's no conflicts with the database method. So now would probably be a good time to import the sites from sites-available into the database & mark the `active` field according to which sites are also in `sites-enabled`, as well as backing up that directory.


Now that we have a database to store and retrieve virtual host containers from, & a place where Apache can load configuration files from it's time to write a command to build a configuration file full of virtual host containers from the database for Apache to load.

Here's an example of such a command. Note that you'll have to sort out permissions on your own, the --user & --pass are used here for example sake, though MySQL documentation warns against doing it that way because it leaves the password visible in the pid file for a short time.

Some options are on a new line for ease of reading. The command ends with output redirection to a file in the sites-enabled directory for Apache to load.
I'm using a UNIX domain socket here, but you could use a TCP connection if need be. You might also want to alter the file output redirection is beind aimed at.
mysql --user=root --pass=pass --batch --raw --silent
--database=Websites
--execute="SELECT container FROM vhosts WHERE active = 1"
--skip-column-names
--protocol=socket
--socket=/var/run/mysqld/mysqld.sock
>/etc/apache2/sites-enabled/vhosts.conf


Of course instead of using that directory as an include directory in Apaches' configuration file, you could comment out that particular include & point an include directly at the file you have the above command outputting to.

Now we have a method to connect Apache with a Database for obtaining virtual hosts, but there's still manual labor involved in needing to run that mysql command when Apache is [re]started or reloaded.

To help with this we will have to edit the shell script used to stop/start/restart Apache so that every time Apache is [re]started, or reloaded, this file is refreshed.
On Debian this script is /etc/init.d/apache2, since I'm using Apache2.

Basicly anywhere there is a log_daemon_msg that [re]starts Apache in this script, we add our own logging & our database command right before it. So we end up with some sections looking somthing like this.
log_daemon_msg "Extracting vhosts from database" "apache2"
mysql --user=root --pass=pass --batch --raw --silent --database=Websites --execute="SELECT container FROM vhosts WHERE active = 1" --skip-column-names --protocol=socket --socket=/var/run/mysqld/mysqld.sock >/etc/apache2/vhosts.conf
log_daemon_msg "Starting web server" "apache2"


Now whenever Apache is [re]started, or reloaded, it will be using the vhost containers for active sites stored in the database.
sudo /etc/init.d/apache2 restart