One thing I forget earlier, part of iteration 2 work on the KnowledgeTree contract was bulk import.

KnowledgeTree 2 had a semi-functioning bulk upload using ZIP files. Semi-functioning, as the ZIP files could not have folders within them. And it's a lot more work to archive up a massive hierarchy than to just copy it to somewhere on your KnowledgeTree server.

So, being an abstraction astronaut again, I abstracted the idea of import storage using only three functions - listDocuments, listFolders, and getDocumentInfo. listDocuments and listFolders act on folder paths (empty being the root), and getDocumentInfo acts upon whatever string listDocuments returned.

This trivially allows a filesystem import storage, as it maps to "ls" and "cat". With those two, you can find and view any document on a system. And so KTFSImportStorage was born, which is given a base path to start at, and which implements these functions.

But thinking ahead a bit, getDocumentInfo handles the ability of multiple versions of documents in an import storage location. So, we could support importing from various revision control systems pretty easily in future. Note that listDocuments isn't _required_ to only list documents in the logical root of the site - it could potentially list every single document in a repository with the paths they should be imported into, and listFolders could return nothing. This is quite useful for cases where implementing listDocuments and listFolders on a hierarchy would be hard - say when importing from another document management system. (Thinking even further ahead, returning an iterator from listDocuments and listFolders would be a brilliant idea...)

KTZipImportStorage could have potentially implemented ZIP file imports using PECL zip, but I opted to just execute the unzip command with the right arguments, make KTZipImportStorage inherit from KTFSImportStorage, and not get too tied down at this stage.