In taxonomy_access_taxonomy():
function taxonomy_access_taxonomy($op, $type, $array = NULL) {
if ($type == 'term') {
switch ($op) {
case 'delete': // delete everything from term_access and node_access
// issue #167977 - klance
$affected_nodes = _taxonomy_access_get_nodes_for_term($array['tid']);
db_query('DELETE FROM {term_access} WHERE tid = %d', $array['tid']);
// issue #167977 - klance
_taxonomy_access_node_access_update($affected_nodes);
//node_access_rebuild();
break;
}
}
_taxonomy_access_get_nodes_for_term() appears to return an empty array, because the term's records have already been deleted from {term_node} so no results are found. This results in no update taking place, which in turn results in corrupted {node_access} records.
Comments
Comment #1
xjmFor reference, in taxonomy_del_term():
So, the entries have already been deleted from
{term_node}by the timehook_taxonomy()is called.The best way I can think of to fix this is to override the submit handler for the term deletion form, so that we can get our terms before the records are deleted. The applicable form is
taxonomy-form-term. So, we would need to implementtaxonomy_access_taxonomy_form_term_alter()or perhapstaxonomy_access_taxonomy_taxonomy_term_confirm_delete_alter()if that's possible.References:
http://api.drupal.org/api/function/taxonomy_form_term/6
http://api.drupal.org/api/function/taxonomy_form_term_submit/6
http://api.drupal.org/api/function/taxonomy_term_confirm_delete/6
http://api.drupal.org/api/function/taxonomy_term_confirm_delete_submit/6
Comment #2
xjmAlso, a similar issue may exist upon vocabulary deletion.
Comment #3
xjmWorking on a patch for this.
Comment #4
xjmThe attached patch resolves this issue for both term deletions and vocabulary deletions.
Comment #5
xjmWhoops, patch has test content still.
Comment #6
xjmFixed patch. Tested with both MySQL and PostgreSQL; the following cases work:
Should test:
Comment #7
xjmTested the cases above; when a term's parent is deleted, nodes tagged with the children are not properly rebuilt.
I think
_taxonomy_access_get_nodes_for_term()probably needs to return nodes for term children:#177502: Should unconfigured terms inherit access control settings of parent terms?
#112307: Cannot create node in taxonomy term without create permission for parent term
Comment #8
xjmIt occurs to me that, while this patch covers the admin forms in core, it doesn't help if any contrib module calls
taxonomy_del_term()ortaxonomy_del_vocabulary()directly.The forum module does this, in fact:
http://api.drupal.org/api/function/forum_confirm_delete_submit/6
http://api.drupal.org/api/function/forum_uninstall/6
(See also: #261041: Denying access to forum names through direct url's, #366270: Forum access control does not protect forum topics).
I really don't see a better way to use the data in
{term_node}before it is deleted, though. (Maybe some sort of caching in_taxonomy_access_get_nodes_for_term()?) At the least, TAC's responses to term and vocab deletion should be refactored out so they are available as API for intergration with other modules.Comment #9
keve commented#6
For the patch, at the vocabulary deletion, i think you should also insert:
db_query('DELETE FROM {term_node} WHERE tid = %d', $tid);before updating
_taxonomy_access_node_access_update($affected_nodes);#8
I do not have any idea either, for direct deletion function e.g
taxonomy_del_term()Maybe, notification flag should be set:
node_access_needs_rebuild($rebuild = NULL), when the form_alter functionstaxonomy_access_form_taxonomy_form_term_alter()ortaxonomy_access_form_taxonomy_form_vocabulary_alterfor specific vid or tid was not executed (e.g. flagged with setting a static variable).Do you think it would work?
Comment #10
keve commented#6 As a second thought, for a huge vocabulary:
During
_taxonomy_access_node_access_update($affected_nodes);php might run out of memory or time limit, which will break vocab deletion, because it is executed beforetaxonomy_del_vocabulary.(This can be true for other cases, where we use:
_taxonomy_access_node_access_update($affected_nodes);)Maybe, we should save vid or tid into a new variable e.g 'taxonomy_access_vid_needs_deletion' and use flag
node_access_needs_rebuild($rebuild = NULL), before executing_taxonomy_access_node_access_update($affected_nodes);, if it runs correctly, we can unflag and delete value in that new variable in hook_taxonomy.Comment #11
xjmI was thinking about this as well; if I tried to delete my site's main content vocabulary as the patch is now, I am pretty sure it would time out. Maybe we should have a threshold in
_taxonomy_access_node_access_update(), so that if$affected_nodesis small, it proceeds with with the update, but if it is large, it sets the rebuild flag instead so that the user does not get a "white screen of death." In either case we can move that part intohook_taxonomy()so that we don't interfere with other modules.I think it is a good idea to set a variable in
hook_form_alter()and check it inhook_taxonomy(); that way, we can avoid a node access rebuild most of the time but still protect the integrity of{node_access}if other modules call the function. I will come up with a revised patch using this method.Comment #12
xjmI should note that
hook_taxonomy()gets invoked once for each term that is deleted, including both all terms in vocabulary deletions and child terms in term deletions. In the latter case, the hook runs for the vocabulary only after it has been run for all the terms.So, we will want to make sure that
_taxonomy_access_node_access_update()only gets called once, and that we also don't mistake unsetting the list of affected nodes for the need to setnode_access_needs_rebuild().Comment #13
xjmThis patch:
_taxonomy_access_node_access_update()ornode_access_needs_rebuild()as a fallback._taxonomy_access_get_nodes_for_term()to return nodes tagged with child terms as well._taxonomy_access_node_access_update()so thatnode_access_needs_rebuild()is set if there are too many nodes.Comment #14
keve commentedThanks for the patch. Needs testing.
Do you know if there a size limit to
variable_set('taxonomy_access_affected_nodes', $affected_nodes);? Whatif there are thousands of nodes.Comment #15
xjmI checked just now. In the
{variable}table, thevaluefield is a longtext. From the MySQL docs:That's a huge number. If the query tried to insert a larger value, it would simply be truncated in MySQL. I'll double-check Postgres this afternoon, but I think we are safe there.
TAXONOMY_ACCESS_MAX_UPDATEwill switch it over tonode_access_needs_rebuildlong before it has an effect on data integrity.Still, I suppose storing it in the database is kind of ugly, and there is the performance cost of serializing and unserializing the array for storage, as well as the queries to insert and delete it.
Maybe it would be better to make a function something like:
Then the variable is cached for the duration of the request and then automatically expires. We could do this for all four of those variables if needed, which would save 6-7 queries per request.
Comment #16
xjmI tested the patch with the modifications described in #15 in the following cases on both MySQL and PostgreSQL:
All worked fine. (I did not have a vocabulary default set in any of the above cases because of #727696: Global defaults do not work properly on some sites, but the devel module seemed happy with the integrity of
{node_access}.) The first vocabulary I deleted had 5000 terms but no nodes; the second had 1000 terms and controlled 499 nodes.I will do a little code cleanup and then upload the new patch shortly.
Comment #17
xjmNote also, deleting a vocabulary with 500 terms that affected 499 nodes took 3300 ms and about 30 MB of memory, so I think 500 is a safe number for TAXONOMY_ACCESS_MAX_UPDATE. Deleting a vocabulary with 10,000 terms and 499 nodes also was successful (although that request later timed out rendering the query log for devel, so I didn't get the statistics for it).
I also confirmed that
node_access_needs_rebuildis properly flagged when there are more nodes than TAXONOMY_ACCESS_MAX_UPDATE (both for deletions and updates to term access).Patch attached.
Comment #18
keve commentedGood job. :)
I like the implementation
_taxonomy_access_cache_affected_nodes($affected_nodes). Storing $affected_nodes in a static variable seems a fair solution.Comment #19
xjmCommitted to 6.x-1.x-dev.