#!/usr/bin/env php . // repair user.project_prefs field (does not validate project_prefs of all users!) // * squash duplicate app_id entries (and optionally delete deprecated appids) // * delete empty venue tags // * migrate preset from attribute to tag (Drupal specific) // // produces lots of output in dry_run mode so always pipe it through less require_once("../inc/boinc_db.inc"); // check your scheduler code if is supported before turning this on define('ADD_APPS_SELECTED_BLOCK', false); // removes all currently deprecated apps from project_prefs define('DELETE_DEPRECATED_APPS', false); // test on all users first then change to false! $dry_run = true; // delete duplicate app_id entries and delete old ones // returns an array of unique app_id entries // function clean_appids($appids) { $apps = BoincApp::enum("deprecated=0"); $current_appids = array(); foreach ($apps as $app) { $current_appids[] = $app->id; } $uniq = array_unique($appids); sort($uniq); if (DELETE_DEPRECATED_APPS) { $uniq = array_intersect($uniq, $current_appids); } return $uniq; } // extracts all app_id elements below $node, removes them // and adds only the elements that remain after cleaning // may add a surrounding apps_selected block if needed // function handle_project_specific_block($node) { $asnode = $node->getElementsByTagName("apps_selected"); $length = $asnode->length; if ($length == 1) { $node = $asnode->item(0); } else if ($length == 0) { if (ADD_APPS_SELECTED_BLOCK) { // create a new apps_selected block and move app_id tags in there $asnode = $node->ownerDocument->createElement('apps_selected'); $asnode = $node->appendChild($asnode); $appids = $node->getElementsByTagName("app_id"); // changing the tree in a loop has strange results so do it separately $to_remove = array(); $ids = array(); foreach($appids as $elem) { $ids[] = $elem->nodeValue; $to_remove[] = $elem; } foreach ($to_remove as $elem) { $oldnode = $node->removeChild($elem); } foreach ($ids as $id) { $elem = $node->ownerDocument->createElement('app_id', $id); $asnode->appendChild($elem); } $node = $asnode; } } else { echo "more than one apps_selected block found; Exiting\n"; exit(1); } $appids = $node->getElementsByTagName("app_id"); if ($appids->length == 0 ) return; // changing the tree in a loop has strange results so do it separately $to_remove = array(); $ids = array(); foreach ($appids as $elem) { $ids[] = $elem->nodeValue; $to_remove[] = $elem; } foreach ($to_remove as $elem) { $oldnode = $node->removeChild($elem); } $cleaned = clean_appids($ids); foreach ($cleaned as $id) { $elem = $node->ownerDocument->createElement('app_id', $id); $node->appendChild($elem); } } // read project specific prefs into a DOMDocument tree and find nodes to fix // returns fixed XML or false if something failed function repair_prefs($prefs) { $prefs_dom = new DOMDocument(); $prefs_dom->preserveWhiteSpace = false; $prefs_dom->formatOutput = true; $prefs_dom->validateOnParse = true; if (!$prefs_dom->loadXML($prefs)) { return false; } $root = $prefs_dom->firstChild; if ($root->hasChildNodes()) { $subNodes = $root->childNodes; $to_remove = array(); foreach ($subNodes as $subNode) { // ignore empty text nodes which are basically whitespace from indentation (only relevant if preserveWhiteSpace is set to true above) if (($subNode->nodeType != XML_TEXT_NODE) || (($subNode->nodeType == XML_TEXT_NODE) &&(strlen(trim($subNode->wholeText))>=1))) { if ($subNode->nodeName == "venue") { if (!$subNode->hasChildNodes()) { // empty venue tag, clean this up too $to_remove[] = $subNode; echo "warning: empty venue tag removed\n"; continue; } if ($subNode->hasAttributes()) { // transform old style preset attributes to tag $preset = $subNode->getAttribute("preset"); if ($preset != "") { $subNode->removeAttribute("preset"); $elem = $subNode->ownerDocument->createElement('preset', $preset); $subNode->appendChild($elem); echo "warning: transformed preset attribute to tag\n"; } } // check if venue has a project_specific subnode (its optional) $psnode = $subNode->getElementsByTagName("project_specific"); if ($psnode->length == 1) { handle_project_specific_block($psnode->item(0)); } } if ($subNode->nodeName == "project_specific") { handle_project_specific_block($subNode); } } } foreach ($to_remove as $elem) { $oldnode = $root->removeChild($elem); } } return $prefs_dom->saveXML($prefs_dom->documentElement); } function process_set($users) { global $dry_run; foreach ($users as $user) { if (!$user->project_prefs) { //echo "repair not needed for user $user->id\n"; continue; } // only parse XML if it contains something we want to fix if (strstr($user->project_prefs, "app_id") || strstr($user->project_prefs, "preset=")) { echo "repair started for user $user->id\n"; $p = repair_prefs($user->project_prefs); if ($p) { if (!$dry_run) { if ($user->update("project_prefs='$p'")) { echo "project_prefs repaired and updated for user $user->id\n"; } else { echo "failed to update project_prefs for user $user->id; Exiting\n"; exit(1); } } else { echo "repair succeeded for user $user->id\n"; echo "repaired prefs:\n$p\n"; } } else { echo "repair failed for user $user->id\n"; if ($dry_run) { echo "original prefs:\n$user->project_prefs\n"; } } } } } $n = 0; $maxid = BoincUser::max("id"); if ($dry_run) { echo "Dry run only! No preferences will be updated\n"; } while ($n <= $maxid) { $m = $n + 1000; $users = BoincUser::enum("id >= $n and id < $m"); //echo "processing from $n\n"; if (!$users) break; process_set($users); $n = $m; } ?>