Just an update -- I found this to be an easy fix for my situation, since the only Versioned objects that I wish to apply this too are pages, I did the following. I added a new class called "CustomSiteTree" (for example) and just setup my "Page" object to extend that. For any Googlers, here's the first implementation of my code that I used, in case anyone else finds this useful.
class CustomSiteTree extends SiteTree {
// This is a workaround to prevent this from being a page type option!
private static $hide_ancestor = "CustomSiteTree";
/**
* Updates the CMS and user interface to prevent users from seeing indications that they can delete something when
* actually they cannot since it's still published. This doesn't get in the way of unpublishing, only deleting from
* draft.
*
* IMPORTANT: This can only be done via override since setting it up in an extension will not work for users who are
* administrators, since unfortunately SilverStripe doesn't bother to even call extensions if it sees that you're an
* admin.
*
* @param null $member
* @return bool
*/
public function canDelete($member = null) {
if ($this->isPublished()) return false;
return parent::canDelete($member);
}
/**
* Extra fail-safe (just in case it is needed) to prevent accidental removal of pages which may already be published.
*/
public function delete() {
$stage = Versioned::current_stage();
if ($stage == "Stage") {
// See if this currently has a Live version.
if ($this->isPublished()) {
// Register an error visible to the user and return immediately preventing delete.
user_error("Cannot delete draft when still published.", E_USER_WARNING);
return;
}
}
parent::delete();
}
}
This seems to work reasonably well for me and prevents those pesky issues where the front-end is showing a ghost page which cannot be removed since it can only be administered via some other area than the site tree interface in the CMS (which goes through the effort of enumerating pages that don't exist in draft but still exist in *_Live tables, i.e. published mode). This is because typically the CMS only references the draft table when fetching objects for listing purposes (e.g. has_many relations for populating a GridField)