tekmosis blogged

With MediaWiki you're able to set the baseurl (thus being able to use a CDN). However when images are overwritten there's no clear way to use your CDN's API to flush it. You'll have to use a hook to go about doing this. I used on UploadVerifyFile hook rather than the UploadComplete hook.

In your hook method you'll first need to check if the file already exists, meaning that it'd be an overwrite; this is why I'm running this in UploadVerifyFile rather than UploadComplete. I've left the param names the same in the hook as what's in the documention, in this case it's $upload, $mime, and &$error but we only need $upload.

$image_exists = UploadBase::getExistsWarning($upload->getLocalFile());


If the $image_exists then we want to run our CDN flush but we have to get the images path first. This depends on how your paths on your CDN is setup but you can use two methods.

Because we're using a wiki farm images are put into folders per-wiki /pokemon /zelda etc so I preg_replace out the path using

$upload->getLocalFile()->getFullUrl(); //returns https://cdn.neoseeker.com/w/pokemon/1/17/Image.png


Alternatively, if you only need the path that MediaWiki would normally use then you may use

$upload->getLocalFile()->getUrlRel(); //returns 1/17/Image.png


So your final hook would look something like this

function onUploadVerifyFile($upload, $mime, &$error) {
	$image_exists = UploadBase::getExistsWarning($upload->getLocalFile());
	if ($image_exists) {
		CDN::Flush('/'.$upload->getLocalFile()->getUrlRel());
	}
	return true;
}
tekmosis blogged

If you have two different projects, one being MediaWiki and you want to integrate the MediaWiki engine into your site, you can use their API. However, doing a web based call to their API adds more overhead than what you normally want, so you can use their internal API calls using the ApiMain class.

For the setup we have a folder called "test". To get this to work you'll need to symlink the includes, and languages directories from our MediaWiki install. Then, we need to either symlink LocalSettings.php, or create a new one which can point to a different database other than our original MediaWiki install.

An example of your /test/index.php would be,

include '/projects/wikiguides/wiki/includes/WebStart.php';

$params = new DerivativeRequest(
		$wgRequest,
		array('action' => 'query', 'page' => 'Main Page')
	);

	$api = new ApiMain($params);
	$api->execute();
	$data = $api->getResultData();


The above code is generally what you'd find from doing a bit of reading on MediaWiki's own documentation wiki but there's a caveat as of 1.20, and 1.21 if you're using InnoDB. Starting in 1.20 they now use transactions, doing the above code however will not call the doCommit() function so you'll never get any writes done (such as logging in, editing a page, etc). It's a bit of a hack in my opinion but you'll need to do the following

$lb = wfGetLBFactory(); //Run these lines after your API calls
$lb->shutdown();


If your tables are MyISAM then you don't need to worry about doing this extra step. You can note this behaviour in InnoDB for example when trying to login no data will be inserted but the mId column will still auto increment.

MediaWiki MediaWiki API
tekmosis blogged

With the launch of the Wii U, you may need to do some validation against the new Nintendo Network ID. The rules for the ID are as follows:
  • Use between 6 and 16 characters. You can use numbers, letters, periods, dashes, and underscores.
  • You cannot use punctuation as the first or last character of your ID. You also cannot use two or more punctuation in a row.
Here is the regex;
^([a-z]([a-z0-9]|(?<![\-\_\.])[\-\_\.]){4,14}[a-z])$

also ensure that you're doing a case insensitive check

web development nintendo network id regex regular expression
tekmosis blogged

In PHP 5.3 you can do Late Static Binding

Through some testing I found that you can overwrite the value of the parents values for both variables and constants by using them statically in the parent class. Here's an example.

__phpcode0__

Our class is very generic, we have a static class variable $foo and a constant bar. We also have two supporting functions that will echo out each value. Note that we are using static:: on both and not $this-> or self::

__phpcode1__

This is our child class where we again define $foo and bar but we set the values differently. Again we have two functions but they're calling the parent functions to echo both values.

The script is set up and executed as such,
__phpcode2__

The result
quote
child var
child const
as you can see we have successfully modified the variables used in the parent class through the child via Late Static Binding.

technology
tekmosis blogged

If you have a group of wikis running off of a single code base (aka wiki farm), this tip is for you.

The way mediawiki sets its cookies is by using $wgDBprefix to append to all the cookies. Chances are you either have different databases or use a single db with a prefix for each table.

This is fine for a single install but on a wiki farm this means you're creating three cookies per wiki; this is bad as every request sent to the server will also be sending all of those cookies as well. To avoid this add the following to your LocalSettings.php

__phpcode0__

you may change the value to suit your needs.

web development cookies mediawiki farm wiki tip
tekmosis blogged

If you've ever wondered how sites are able to prompt a user to download a file from their servers here's how using php headers:

php code


header('Content-Type: video/quicktime');
header('Content-Disposition: attachment; filename=videoname.mov');
header('Expires: ' . gmdate('D, d M Y H:i:s', gmmktime() - 3600) . ' GMT');
header('Content-Length: ' . filesize('/usr/home/tekmosis/testmovie.mov'));

$fp = fopen('/usr/home/tekmosis/testmovie.mov', 'r');
fpassthru($fp);
 



Note that in the Content-Disposition header the filename can be whatever you want it to be. The only thing that matters pertaining to the correct filename is the fopen() and the Content-Length.

Your script actually needs to output the binary data of the file rather than directly pointing it to the location on the webserver.

Your MIME type should coincide with whatever type of media you're dishing up. For a list of MIME types see: http://www.utoronto.ca/web/HTMLdocs/Book/Book-3ed/appb/mimetype.html

php php tutorial tutorial web development
tekmosis blogged

Personally I haven't been able to test this but from what I've heard using GD to resize an animated gif is slower and may not result in the best of quality. An alternative to GD is to use ImageMagick. The ImaegMagick software itself is normally used via command line, executing those commands through PHP. Things can be handled much more easily with a PHP based API, however. We actually have a couple of options for an API but I'll be using IMagick for this.

ImageMagick PHP API list: I wont go over installing the package as you can find information on that on each API's own site.

The unfortunate thing with this method is that you need to process each frame of the gif individually, the API doesn't handle this with their own functions. Now, ImageMagick needs to process each frame anyways but having a function to do it for you would have been more convenient than creating a loop but I digress.

For this example we'll just be using a simple form that has a single 'file' type input field that submits to itself, resizes the image and then displays it. This script is meant to be as simple and straight forward as possible but it could be used as an image resizing script if tweaked a bit.

php code

if (!empty($_FILES)) { //only run if we've submitted an image 
    $imagick = new Imagick($_FILES['test_img']['tmp_name']); //instantiate a new Imagick object from our submitted image

    foreach ($imagick as $frame) { //loop through each frame
        $frame->resizeImage(70,70,Imagick::FILTER_LANCZOS,0); //resize the image to 70(width)x70(height) using the Lanczos filter
    }

    header( "Content-Type: image/{$imagick->getImageFormat()}" ); //set content type to whatever the images format is (gif, png, jpeg)
    echo $imagick->getImagesBlob(); //display the image
    die; //kill the script so it wont error out about header data (the html form)
}

?> 


<form action="" method="post" enctype="multipart/form-data">
    img: <input type="file" name="test_img" />
    <input type="submit" name="submit" value="submit" />
</form>
 


With the inline comments it should be pretty straight forward on what's happening. What you should take note of is that there are two functions for displaying an image.

getImageBlob();
getImagesBlob();

Note the pluralization! I found this rather silly but if that's how they chose to do it, whatever. What you want to use for animated gif's is the pluralized function, getImagesBlob(); otherwise it will only display one frame and not all of them.

The second thing you need to take note of if the enctype on the form. You will need this for the $_FILES array to be populated.

As mentioned earlier with some tweaking you can make this a simple yet more flexible image resizing script. All you need to do is add in some $_POST or $_GET variables for the desired width/height and replace the hard coded 70's

Resources:

web development imagemagick imagick
tekmosis blogged

Lets say you have a table containing all users favorite games and you wanted to get totals for the Wii, PS3 and 360; rather than doing a COUNT(*) query for each you can do the following trick:

code
SELECT SUM(IF(platform = 'Wii', 1, 0)) as wii_total,
	SUM(IF(platform = 'PS3', 1, 0)) as ps3_total,
	SUM(IF(platform = 'XBOX360', 1, 0)) as xbox360_total
FROM testdb.favourite_games
WHERE username = 'tekmosis'



web development
tekmosis blogged

Have you ever had an array or list of items that you wanted formatted properly in a nice mutiple column layout rather than just an entire list? This method should help you out a bit with that by taking advantage of PHP's array_chunk() function.

php code

$arr = array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
                'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');

$max_num_cols = 4; //the maximum amount of columns that can be output
$min_items_per_col = 5; //The minimum amount of items per column
$col_limit_calculation = $max_num_cols * $min_items_per_col; //used to determine which value to chunk by
$num_rows = count($arr); //total number of items in the array

//determins how many values to chunk our array by while still keeping with our $max_num_cols
$chunk_count = ($num_rows >= $col_limit_calculation) ? ceil(($num_rows / $max_num_cols)) : $min_items_per_col;

//The chunked array that we get to loop through now
$alphabet_chunk_array = array_chunk($arr, $chunk_count);

echo "<table border="1"><tr>";
foreach ($alphabet_chunk_array as $alphabet_array) {
    echo "<td valign="top">";
    foreach ($alphabet_array as $alphabet) {
        echo $alphabet . "<br />";
    }
    echo "</td>";
}
echo "</tr></table>";
 


web development
tekmosis blogged

If you're a frequent member on the site I'm sure you've noticed blank star images which when clicked switch 'states' to this image and perform an action such as adding a blog to your favorites.

In this post I'll go over a method on achieving a changeable state when you click on the image.

HTML:
code
<a href="#"><img id="preimg" src="https://i.neoseeker.com/f/neo/unsubscribed_star.gif" border="0" /> <span id="word">hello</span></a>


JS:
code
star_on = new Image(15,15);
star_on.src="https://i.neoseeker.com/f/neo/subscribed_star.gif";

star_off = new Image(15,15);
star_off.src="https://i.neoseeker.com/f/neo/unsubscribed_star.gif";

function changestar() {
	var star_is_blank = new RegExp("unsubscribed_star");
	var star_image = document.getElementById('preimg');
	var star_text = document.getElementById('word');

	if (star_is_blank.test(star_image.src)) {
		star_image.src = star_on.src;
		star_text.innerHTML = 'goodbye';
	} else {
		star_image.src = star_off.src;
		star_text.innerHTML = 'hello';
	}
}


Let's go over the JS first. You can place the JS in either an external .js file and include it on the page. However, in this example we'll add it between our header tags.

For optimum performance we'll want to do some preloading. We're doing this because without utilizing preloading you'll get a "flicker" between images while they change states after being clicked. Adding a preloading smooths things out and makes the state change seamless.

code
star_on = new Image(15,15);
star_on.src="https://i.neoseeker.com/f/neo/subscribed_star.gif";

star_off = new Image(15,15);
star_off.src="https://i.neoseeker.com/f/neo/unsubscribed_star.gif";

In this we are preloading images for both states "on" and "off". Both images are 15px by 15px (width / height) so that's what the "15,15" in the Image instantiation represents. Then, we're setting the source for the newly declared images for preloading. Now, lets take a look at our function which will handle the changing.

code
var star_is_blank = new RegExp("unsubscribed_star");
var star_image = document.getElementById('preimg');
var star_text = document.getElementById('word');

We'll set all of our variables to start with so that our logic will look a lot cleaner. Here's a brief run down of our variables and their purpose.
star_is_blank is a regular expression looking for the text "unsubscribed_star" we'll use this to check the current image that is being viewed on the page so we can determine which state to properly display.

star_image this will grab the actual html image tag itself so we can get information we need from it, in our case the source (src="")

star_text this part is optional and only benefits you if you want to change the text on the state change.


code
if (star_is_blank.test(star_image.src)) {
	star_image.src = star_on.src;
	star_text.innerHTML = 'goodbye';
} else {
	star_image.src = star_off.src;
	star_text.innerHTML = 'hello';
}

Now we get to the bulk of the code in the function which handles the actual changing of the image and the text. As noted in the section above with the regex you can now see how we are using it. We'll check if the src of star_img is "unsubscribed_star", if it is then we'll change the src star_image to the src of star_on, take note that we're using star_on and not "https://i.neoseeker.com/f/neo/subscribed_star.gif" itself. If you were to call "https://i.neoseeker.com/f/neo/subscribed_star.gif" rather than star_on users browsers will not be intelligent enough to know to use your preloaded image. You must use the variable in which you set for your preloaded images. The second step in the if block is to change the text and the else block will contain the opposite state changes using star_off and changing the text to 'hello'.

web development
tekmosis blogged

If you're having problems with spam bots on an install of MediaWiki you can do the following in your LocalSettings.php or applicable configuration file; LocalSettings.php is the default.

For example, say a spam bot is spamming User: pages and you want to only allow logged in users to be able to edit these pages.

php code

$wgGroupPermissions['*'          ]['read']            = true;
$wgGroupPermissions['*'          ]['wantedimages']    = true;
$wgGroupPermissions['*'          ]['createtalk']      = false;
$wgGroupPermissions['*'          ]['rollback']        = false;

$wgGroupPermissions['user'       ]['edit']            = true;
$wgGroupPermissions['user'       ]['createpage']      = true;
$wgGroupPermissions['user'       ]['createtalk']      = true;
$wgGroupPermissions['user'       ]['neoedit']         = true;

$wgNamespaceProtection[1] = array('neoedit'); //Main talk pages
$wgNamespaceProtection[2] = array('neoedit'); //User pages
$wgNamespaceProtection[3] = array('neoedit'); //User talk pages
 


Now lets go through this step by step, it's really rather easy. First lets take note of the $wgGroupPermissions array. This array in the example contains two groups:
* - is for everyone including guests
user - is for anyone who is logged in
MediaWiki has a hierarchy in which if you apply something to a lower down group it will apply to all; we'll take a look at that in a bit more depth in a moment.

Currently with the configuration we can disallow guests from creating talk pages however they can still edit them. At the time of MediaWiki version 1.12 there are no flags to set "noedittalk" or "noedituser".
php code

$wgGroupPermissions['*'          ]['createtalk']      = false;
 


Take note of the user permissions. I've set a custom flag called "neoedit"
php code

$wgGroupPermissions['user'       ]['neoedit']         = true;
 


With this set the users, sysop and bureaucrat groups have permissions (value set to boolean true) but guests do not. If you set this for * everyone including guests would have the permission but that defeats the purpose of what we're trying to achieve.

Now that we have our special flag set we can mange the permissions of each namespace.
php code

$wgNamespaceProtection[1] = array('neoedit'); //Main talk pages
$wgNamespaceProtection[2] = array('neoedit'); //User pages
$wgNamespaceProtection[3] = array('neoedit'); //User talk pages
 

This block will disallow guests from being able to do anything but view anything User: Talk: or User_talk:

Alternatively you can also use the constants instead of the Index number for each namespace.

php code

$wgNamespaceProtection[NS_TALK] = array('neoedit'); //Main talk pages
$wgNamespaceProtection[NS_USER] = array('neoedit'); //User pages
$wgNamespaceProtection[NS_USER_TALK] = array('neoedit'); //User talk pages
 

Resources

Namespace Constants List:
http://www.mediawiki.org/wiki/Manual:$wgNamespacesWithSubpages

Namespace Manual:
http://www.mediawiki.org/wiki/Manual:Namespace

Namespace Protection Manual:
http://www.mediawiki.org/wiki/Manual:$wgNamespaceProtection

how to guide help mediawiki web development
(0.0587/d/www1)