Created
September 25, 2015 21:19
-
-
Save alexgarel/b4e4a2e3160d3b0e8559 to your computer and use it in GitHub Desktop.
Utility to fixe a not so broken joomla assets table. Provided all assets are there and parent_id is correct, it will recompute level, rgt and lft for all entries.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import argparse | |
import getpass | |
import sys | |
import mysql.connector | |
class AssetsFixer: | |
cols = ["id", "parent_id", "level", "lft", "rgt"] | |
def __init__(self, user, password, db, prefix, host="localhost"): | |
self.db = mysql.connector.connect(user=user, password=password, db=db, host=host) | |
self.prefix = prefix | |
def get_all(self, query, *params): | |
c = self.db.cursor() | |
c.execute(query, params) | |
return c.fetchall() | |
def collect_info(self): | |
by_id = self.by_id = {} | |
by_parent_id = self.by_parent_id = {} | |
query = "select %s from %sassets" % (", ".join(self.cols), self.prefix) | |
for values in self.get_all(query): | |
row = dict(zip(self.cols, values)) | |
by_id[row["id"]] = row | |
siblings = by_parent_id.setdefault(row["parent_id"], []) | |
siblings.append(row["id"]) | |
def fix_data(self, parent_id, level, lft): | |
"""depth first descent | |
""" | |
child_lft = lft | |
children = self.by_parent_id.get(parent_id, []) | |
# sort on actual lft | |
children.sort(key=lambda i: self.by_id[i]['lft']) | |
for child_id in children: | |
child_lft = self.fix_data(child_id, level + 1, child_lft + 1) | |
# fix row | |
data = self.by_id[parent_id] | |
data["level"] = level | |
data["lft"] = lft | |
data["rgt"] = child_lft + 1 | |
return data["rgt"] | |
def update_db(self): | |
c = self.db.cursor() | |
update_cols = list(self.cols) | |
update_cols.remove("id") | |
update_expr = ", ".join("{col}=%({col})s".format(col=col) for col in update_cols) | |
update_query = "UPDATE {prefix}assets SET {update_expr} WHERE id=%(id)s".format( | |
prefix=self.prefix, update_expr=update_expr) | |
c.executemany(update_query, list(self.by_id.values())) | |
self.db.commit() | |
def __call__(self): | |
print("Collecting info") | |
self.collect_info() | |
print("Fixing") | |
roots = self.by_parent_id[0] | |
assert len(roots) == 1, "More than one root !" | |
root = roots[0] | |
self.fix_data(root, level=0, lft=1) | |
print("Updating db") | |
self.update_db() | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser( | |
description=("Utility to fixe a not so broken joomla assets table. " + | |
"Provided parent is correct")) | |
parser.add_argument("-u", "--username", required=True) | |
parser.add_argument("-l", "--hostname", default="localhost") | |
parser.add_argument("database", nargs=1) | |
parser.add_argument("prefix", nargs=1, help="tables prefix") | |
opts = parser.parse_args() | |
answer = input( | |
"Note that this is a potentially dangerous operation, " + | |
"did you backup you database first ? (yes/no):") | |
if answer == "yes": | |
password = getpass.getpass() | |
af = AssetsFixer( | |
db=opts.database[0], prefix=opts.prefix[0], user=opts.username, password=password) | |
af() | |
else: | |
print("So backup first") | |
sys.exit(1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment