Using rrdtool with PHP

The PHP interface to rrdtool hasn’t been updated in 5 years and appears to have been deprecated by the developer, who doesn’t provide any documentation for it. Fortunately, there’s no functionality in the extension, so it won’t go out of date as long as the rrdtool library on your system is up to date. I’ve managed to figure out the functions by looking at the source code and thought it might be helpful for someone.
Continue reading “Using rrdtool with PHP”

Easy SVG grid

I needed a grid in the background while I was debugging an SVG image I was creating, something like Photoshop’s transparency grid. Here’s what I did.

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="200" height="400">
  <defs> 
    <pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse">
      <rect fill="black" x="0" y="0" width="10" height="10" opacity="0.1"/>
      <rect fill="white" x="10" y="0" width="10" height="10"/>
      <rect fill="black" x="10" y="10" width="10" height="10" opacity="0.1"/>
      <rect fill="white" x="0" y="10" width="10" height="10"/>
    </pattern>
  </defs>
  <rect fill="url(#grid)" x="0" y="0" width="100%" height="100%"/>
</svg>

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="200" height="400"> <defs> <pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse"> <rect fill="black" x="0" y="0" width="10" height="10" opacity="0.1"/> <rect fill="white" x="10" y="0" width="10" height="10"/> <rect fill="black" x="10" y="10" width="10" height="10" opacity="0.1"/> <rect fill="white" x="0" y="10" width="10" height="10"/> </pattern> </defs> <rect fill="url(#grid)" x="0" y="0" width="100%" height="100%"/> </svg>

The New CBC Radio 3

I often listen to CBC Radio 3 at work. Recently they updated their website; while it’s mostly a change for the better (yay, the player doesn’t stop updating!) there were a couple of things bugging me about it. With the old design, you always had access to the player and the main navigation, but now they stay at the top of the page. Not helpful when you’re scrolling through comments and whatnot.

So I wrote a Greasemonkey script that keeps the player and the left navigation bar in place. It also clears out the CBC header at the top, as well as the CBC Radio header that sits below that, for a cleaner page.

Continue reading “The New CBC Radio 3”

Delete MediaWiki pages from the database

Deleting a page from the wiki doesn’t actually remove it, just hides it away. Here’s a procedure to permanently remove things from the database, and never ever see them again.

DROP PROCEDURE IF EXISTS delete_page;
DELIMITER //
 
CREATE PROCEDURE delete_page(IN page_id_var INT)
	LANGUAGE SQL
	NOT DETERMINISTIC
	MODIFIES SQL DATA
	SQL SECURITY INVOKER
	COMMENT 'permanently deletes pages from the database'
BEGIN
	DECLARE page_title_var VARCHAR(255);
	DECLARE page_namespace_var INT;
	SELECT page_title, page_namespace INTO page_title_var, page_namespace_var FROM page WHERE page_id = page_id_var;
	DELETE FROM redirect WHERE rd_from = page_id_var;
	DELETE FROM externallinks WHERE el_from = page_id_var;
	DELETE FROM langlinks WHERE ll_from = page_id_var;
	DELETE FROM searchindex WHERE si_page = page_id_var;
	DELETE FROM page_restrictions WHERE pr_page = page_id_var;
	DELETE FROM pagelinks WHERE pl_from = page_id_var;
	DELETE FROM categorylinks WHERE cl_from = page_id_var;
	DELETE FROM templatelinks WHERE tl_from = page_id_var;
	DELETE text.* FROM text LEFT JOIN revision ON (rev_text_id = old_id) WHERE rev_page = page_id_var;
	DELETE FROM revision WHERE rev_page = page_id_var;
	DELETE FROM imagelinks WHERE il_from = page_id_var;
	DELETE FROM recentchanges WHERE rc_namespace = page_namespace_var AND rc_title = page_title_var;
	DELETE text.* FROM text LEFT JOIN archive ON (ar_text_id = old_id) WHERE ar_namespace = page_namespace_var AND ar_title = page_title_var;
	DELETE FROM archive WHERE ar_namespace = page_namespace_var AND ar_title = page_title_var;
	DELETE FROM logging WHERE log_namespace = page_namespace_var AND log_title = page_title_var;
	DELETE FROM watchlist WHERE wl_namespace = page_namespace_var AND wl_title = page_title_var;
	DELETE FROM page WHERE page_id = page_id_var LIMIT 1;
END//
 
DELIMITER ;

DROP PROCEDURE IF EXISTS delete_page; DELIMITER // CREATE PROCEDURE delete_page(IN page_id_var INT) LANGUAGE SQL NOT DETERMINISTIC MODIFIES SQL DATA SQL SECURITY INVOKER COMMENT 'permanently deletes pages from the database' BEGIN DECLARE page_title_var VARCHAR(255); DECLARE page_namespace_var INT; SELECT page_title, page_namespace INTO page_title_var, page_namespace_var FROM page WHERE page_id = page_id_var; DELETE FROM redirect WHERE rd_from = page_id_var; DELETE FROM externallinks WHERE el_from = page_id_var; DELETE FROM langlinks WHERE ll_from = page_id_var; DELETE FROM searchindex WHERE si_page = page_id_var; DELETE FROM page_restrictions WHERE pr_page = page_id_var; DELETE FROM pagelinks WHERE pl_from = page_id_var; DELETE FROM categorylinks WHERE cl_from = page_id_var; DELETE FROM templatelinks WHERE tl_from = page_id_var; DELETE text.* FROM text LEFT JOIN revision ON (rev_text_id = old_id) WHERE rev_page = page_id_var; DELETE FROM revision WHERE rev_page = page_id_var; DELETE FROM imagelinks WHERE il_from = page_id_var; DELETE FROM recentchanges WHERE rc_namespace = page_namespace_var AND rc_title = page_title_var; DELETE text.* FROM text LEFT JOIN archive ON (ar_text_id = old_id) WHERE ar_namespace = page_namespace_var AND ar_title = page_title_var; DELETE FROM archive WHERE ar_namespace = page_namespace_var AND ar_title = page_title_var; DELETE FROM logging WHERE log_namespace = page_namespace_var AND log_title = page_title_var; DELETE FROM watchlist WHERE wl_namespace = page_namespace_var AND wl_title = page_title_var; DELETE FROM page WHERE page_id = page_id_var LIMIT 1; END// DELIMITER ;

Now you can look up your article ID, and then call the procedure with CALL delete_page(999);.

No route matches “…” with {:method=>:get}

I configured Ruby on Rails to run with Apache, because I’m not too worried about speed and didn’t want to mess with proxies. I also configured the app to run in a subdirectory, using Apache’s Alias directive to point to the app’s public directory. I’ll point out this is the first time I’ve ever looked at Ruby in my life, and my first time working with any MVC framework, although I’ve looked into them a bit.

I was getting the dreaded No route matches "/subdirectory/" with {:method=>:get} error and it seemed pretty clear what the problem was. The app didn’t know it was in a subdirectory; I’d probably need to edit the routes to tell it so. It seems this is the last thing the people in Google-land were needing to do, but I eventually figured it out. I’d need to do something like this with the routes:

map.connect 'subdirectory/:controller/:action/:id'

So I took a look at routes.rb and it was using resources, not traditional routes. So what do I do with that?

It took hours of searching before I found the answer to my problem — a testament to the quality of Rails’ documentation I suppose. The answer is path_prefix

map.resources :groups, :path_prefix => 'subdirectory/'

You can also use it for the root as well.

map.root :controller => 'start', :path_prefix => 'subdirectory/'

Now I just have to fix the fact that the author of the app hard coded all sorts of stuff with the assumption that the app wouldn’t be in a directory. Grrr.

Update: Turns out it’s even easier than that. I didn’t have to change routes.rb at all.

config.action_controller.relative_url_root = '/subdirectory'

This has the added advantage of fixing things like linked stylesheets and stuff as well.

Configuring Unison on Windows

Unison is a free file sync utility that runs on Windows, OS X, or Linux. As with most of these things, Windows support is a bit of an afterthought, and can be tricky to work with. Here’s a step-by-step list of what I did to get it working. Note that I’m only setting things up for mirroring (ie one-way sync) so I only installed the SSH server and Unison text client on the “client” and the remainder of the stuff on the “server.” If you’re doing full 2-way sync, perform all the installations on both machines.

  1. Install GTK for Windows. I tried a couple of installers that didn’t work, but found an installer as part of this project that worked great.
  2. Install an SSH server. freeSSHd is a great little program, and it’s a tiny download. Make sure to install as a Windows service when it asks you.
  3. Configure the SSH server. Starting up the freeSSHd GUI actually tries to open another SSH server, even though you configured one to run as a service. This means that changes you make in the GUI won’t take effect until you restart the service. The only thing you’ll need to do is create a user, with password stored as SHA1 hash, that is allowed to use the shell.
  4. Configure environment variables. Unison expects to find an environment variable called HOME and will complain if it doesn’t find it. I set mine to C: and it’s happy with that.
  5. Get a fake SSH client. Unison expects that it can say ssh and something will answer. So first download PuTTY and Plink and make sure they’re in your path. Then download the Unison-ssh wrapper and make sure it’s in your path. It will redirect any calls for ssh.exe to plink.exe
  6. Put the Unison client in place. Windows binaries are available here. Rename the binaries something simple like unison-gui and unison. I suggest naming the text version unison as this is the name that Unison expects the client to have. Make sure the binaries are also in your path.
  7. Test your SSH. At this point you should be able to go to a command prompt and do this:
    ssh -l username -pw password server "unison -version"
    and get something like this:
    unison version 2.27.57
  8. Test your Unison. Assuming that worked, try testing Unison with the -testserver parameter:
    unison -testserver -sshargs "-l username -pw password" c: ssh://server//
    which should give you something like this:
    Connected [//server1/c:/ -> //server2/c:/]
  9. Set up your files. Congratulations, the servers are talking to each other! I suggest running the next step in the GUI, as there may be a lot of initial file syncing to be done:
    unison-gui -sshargs "-l username -pw password" c:directory ssh://server//directory
    It’s pretty self-explanatory, arrows point in the direction of updates, click “Go” when you’ve got it set up how you want it.

After the initial setup, you can continue using the GUI, or start using the text mode. There are a number of preferences that make things easier, which you can learn about in the documentation. As I mentioned, I’m using this for mirroring, so I use the -force parameter to make sure the “server” always takes priority over the “client.”

Creating a FreePBX module

The process of creating a module for FreePBX is, in theory, documented in a number of wiki pages, but these are worse than nothing. Full of outdated information — some of it labeled as such, some of it not — as well as broken links and vague promises of information “to be determined,” this collection of pages does more harm than good. Here, then, are some pointers to get a module working, presented as a list of files you need to have in your module’s directory.

module.xml
One of the few well documented pages in the wiki, this determines how the module is displayed within the FreePBX menu structure, as well as information on version, change logs, etc. that will be displayed in the module administration page.
install.php
Any code that needs to be executed when the module is installed should be placed here. This includes database setup; the wiki will tell you that install.sql fulfills this purpose, but it doesn’t. Within this file you have access to the $db object, which is a PEAR::DB object. Note that this file gets executed during upgrades as well. So for example if you add a column to an existing FreePBX database table, make sure to catch any errors that might happen if the column already exists from a previous install.
uninstall.php
Similarly, this file is used to clean up after the module is uninstalled. Again, uninstall.sql is not processed properly so all database cleanup should be in this file.
functions.inc.php
This file is necessary if you want to interact with other FreePBX modules; for example, to add a drop-down box while editing an extension. How is this done? During the initialisation stage of the page, before anything is output, config.php runs through the list of installed modules and includes all their functions.inc.php files. It then checks for a function called modulename_configpageinit and executes it if found.
Within this function you can call addguifunc to specify a function to be run when the page displays, and addprocessfunc for one that is run after the page has been submitted for changes. The simple act of adding a drop-down box to a page requires you to prepare a list of items at the initialisation stage (using addoptlist and addoptlistitem) and then refer to that list during page output in your GUI function to actually output the drop-down box (using the gui_selectbox object.) To save the value, your process function is called, and you are given the chance to do what you like with the POST request.
page.modulename.php
This is one of the many things in the wiki marked as “deprecated” with no mention of what replaces it. So I’ve continued to use it for the module’s own page. Unlike other modules’ pages, when you’re creating your own module’s page it is largely included as-is, meaning you can write it like most any PHP page. If you want to do anything “AJAX” in your pages, you’ll need to exit from the page when you return an XML or JSON value; this will prevent FreePBX from tacking it’s own stuff onto the output.

WMI error 80041010 on performance counters

I recently was having problems with my WMI queries. Following some (bad) advice I rebuilt the repository. It didn’t solve my problem, and afterwards all the performance counter classes had disappeared. Win32_PerfRawData_* and Win32_PerfFormattedData_* were gone, reporting error 0x80041010 [“Invalid class”] (Instead of an error 0x80041010, MS says you might get error 0x80041002 [“Object could not be found”] or error 0x80041006 [“Insufficient memory”] when trying to connect to a nonexistent class.) All the rebuilding and troubleshooting and searching MOF files gave me nothing.

The answer? winmgmt /resyncperf rebuilds the performance counter classes in the repository. To be extra safe, winmgmt /clearadap clears the old data first.

Continue reading “WMI error 80041010 on performance counters”

ATT00000.txt files in Outlook

This information is for people who create email messages in programming languages, not Outlook users.

The separator (defined in the Content-Type header) is used to start a new part of the multipart MIME message. Standard practice (not sure if it’s RFC behaviour or not) is to place an instance of the separator at the end of the message. Outlook sees this as the start of a new attachment. Because it has no Content-Disposition information it names it automatically, and of course there’s no content so it’s an empty file. So by not placing the separator at the end of the message, you avoid the empty attachment.

Searching for the answer today I have seen loads of people asking about this online, nobody came up with an answer. Part of the problem is the frequency with which Outlook generates these ATT*.txt files; some people were seeing their attachments replaced with these empty text files, some were getting blank email bodies, but with attachments and the empty text files, etc.

This just came to me after spending the day trying to figure it out, and it works. The logic of the first paragraph is entirely guesswork on my part.

PHP fatal errors in imagepng()

I upgraded to PHP 5.2 from 4.3 recently and came across a couple of error messages: php[7028], PHP Warning: imagepng(): gd-png: fatal libpng error: zlib error in … followed by: php[7028], PHP Warning: imagepng(): gd-png error: setjmp returns error condition in …

Turns out the paramters for imagepng changed in PHP 5.1.3, and I’m not sure what the third argument used to be, but where I had imagepng($image, null, 100) it died, because the third argument (quality) is supposed to be 0 to 9 now.

I came across postings saying to replace DLL files and all this nonsense, but all I needed to do was change the 100 to a 9.

Driver for PC card from a Linksys WET-11 bridge

The Linksys WET-11 bridge, if taken apart, contains a PCMCIA (PC Card) wireless adapter with an external antenna connection, which could be very useful.

Unfortunately it has no helpful identifying marks. On one side there is an FCC ID (PKW-WM11); the other side has a model number (?) of WM302-IJR10, a serial number, and a date code. Do a search for that FCC ID and you’ll get tons of people asking where they can find drivers, and if anyone knows what kind of card it is, etc.

Apparently nobody thought to actually put it into a PC, which I did. Windows can’t find a driver for it, but it does identify itself as a PCMCIA-11M_WLAN_CARD_V3.0. So I did a search on that string, and found that the drivers for a ZyAIR B-100 made by Zyxel will work just fine for everything from Windows 95 to Windows XP.

The software is available here: http://www.zyxeltech.de/zyairb100.htm and I’ve tested the older version in Windows 95, and the newer version in Windows XP. Both work great.

Hopefully Google will lead some weary travellers here. If this has made your day, visit my website and click on an ad! http://mike.eire.ca/

Using PHP to interface with WMI

Windows Management Instrumentation (WMI) is a Windows derivative of the WBEM standard allowing centralized management of a wide number of Windows functions. There is almost no mention of how to use it from PHP, although combined together they provide a powerful method of web-based management. This example shows how to connect to a remote server, update a single DNS record, then flush the DNS cache.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
< ?php
$host = 'www';
$ip = '192.168.1.1';
$domain = 'example.com';
$query = "SELECT * FROM MicrosoftDNS_AType WHERE DomainName='$domain' AND OwnerName='$host.$domain'"; 
try {
//create the object
	$rpc = new COM('WbemScripting.SWbemLocator');
//update DNS
	$wmi = $rpc->ConnectServer($rpchost, 'Root/MicrosoftDNS', $user, $pass);
	$hosts = $wmi->ExecQuery($query);
	foreach($hosts as $host) {
		echo "Updating $host->OwnerName from $host->IPAddress to $ip.";
		flush(); ob_flush();
		$result = new Variant(null);
		$host->Modify(null, $ip, $result);
	}
//flush the DNS cache by restarting the dnscache service
	$query = "SELECT * FROM Win32_Service WHERE Name='Dnscache'";
	$wmi = $rpc->ConnectServer($rpchost, 'Root/cimv2', $user, $pass);
	$services = $wmi->ExecQuery($query);
	foreach ($services as $service) {
		$service->StopService();
		sleep(2);
		$service->StartService();
	}
}
catch(Exception $e) {
	echo $e;
	exit;
}?>

< ?php $host = 'www'; $ip = '192.168.1.1'; $domain = 'example.com'; $query = "SELECT * FROM MicrosoftDNS_AType WHERE DomainName='$domain' AND OwnerName='$host.$domain'"; try { //create the object $rpc = new COM('WbemScripting.SWbemLocator'); //update DNS $wmi = $rpc->ConnectServer($rpchost, 'Root/MicrosoftDNS', $user, $pass); $hosts = $wmi->ExecQuery($query); foreach($hosts as $host) { echo "Updating $host->OwnerName from $host->IPAddress to $ip."; flush(); ob_flush(); $result = new Variant(null); $host->Modify(null, $ip, $result); } //flush the DNS cache by restarting the dnscache service $query = "SELECT * FROM Win32_Service WHERE Name='Dnscache'"; $wmi = $rpc->ConnectServer($rpchost, 'Root/cimv2', $user, $pass); $services = $wmi->ExecQuery($query); foreach ($services as $service) { $service->StopService(); sleep(2); $service->StartService(); } } catch(Exception $e) { echo $e; exit; }?>

A couple of points to note:

  1. this is PHP 5 code, it will not work in version 4.
  2. This code uses the COM functions, only available in Windows-based PHP installs.
  3. notice that even though I only pulled one record from the WMI server, I still have to use foreach to iterate through the result set. Like the query itself, the result set is treated the same as one from a database.
  4. I needed to reconnect after the DNS update to use a new namespace; where the DNS server management classes are in the Root/MicrosoftDNS namespace, the service management classes are in the default Root/cimv2 namespace.
  5. Microsoft’s WMI documentation is here. All the code samples are VBScript, but using the example above you should be able to figure things out.

Upgrading Blackberry device software

Your cellular carrier likely isn’t making the most recent version of your Blackberry’s software available to you.  Usually they don’t want to support a newer version, or don’t want subscribers to have access to new features until they find a way to make money from them.  However, if any carrier has a newer version of software, you can have it too.  Here’s how it works:

  1. Google for this: “software download for” welcome site:blackberry.com to find the most recent version of software for your phone.  This may take some searching, as there’s about 50 different carriers.  I have a 7290, and Cincinnati Bell had version 4.1.0.379, much better than Rogers’ 4.0.2
  2. Download and install the software
  3. Open the file C:Program FilesCommon FilesResearch In MotionAppLoaderVendor.xml in Notepad
  4. Search for the name of the provider whose software you downloaded.  In my case, I searched for “Cincinnati” to find the right section: <vendor id=”0x87″ Name=”Cincinnati Bell”>
  5. Inside that provider’s section of the file (the section ends at the </vendor> line,) find the subsection corresponding to the version you downloaded.  Mine was: <bundle id=”System” version=”4.1.0.379″>
  6. Copy that “bundle” subsection in its entirety, which should be 3 lines, like my example:
    <bundle id="System" version="4.1.0.379">
    <devicehwid> 0x94000903 0x9c000503</devicehwid>
    </bundle>

    If you want to double check that this is really for your device, the file C:Program FilesCommon FilesResearch In MotionAppLoaderdevice.xml has a list of all the hardware IDs with model numbers. My device ID is 0x9c000503, which I can see is included in the line above.

  7. Search for your actual provider.  I searched for Rogers and found this: <vendor id=”0x6B” Name=”Rogers”>
  8. Paste the “bundle” subsection into your provider’s section (above the </vendor> line)
  9. Start the desktop software and plug in.  Your handheld should be upgraded shortly

That XML file keeps track of which carrier is allowed to have a given version of the Blackberry OS.  All I did was say “A handheld from Rogers is allowed to have version 4.1.0.379 if it connects.”