diff --git a/crates/engine/src/cmd/review.rs b/crates/engine/src/cmd/review.rs index c55aac8e5..18363ead7 100644 --- a/crates/engine/src/cmd/review.rs +++ b/crates/engine/src/cmd/review.rs @@ -426,6 +426,32 @@ mod tests { s.execute("review.unprotectSheet", json!({"password": "old"})).unwrap(); } + #[test] + fn protected_structure_blocks_rename_move_copy_hide_and_unhide_sheet() { + let mut s = book(); + let names = |s: &Session| s.doc().unwrap().wb.sheets.iter().map(|x| x.name.clone()).collect::>(); + let before = names(&s); + s.execute("review.protectWorkbook", json!({})).unwrap(); + assert!(s.execute("sheet.rename", json!({"sheet": 0, "name": "Renamed"})).is_err()); + assert!(s.execute("sheet.move", json!({"sheet": 0, "to": 1})).is_err()); + assert!(s.execute("sheet.move", json!({"sheet": 0, "to": 1, "copy": true})).is_err()); + assert!(s.execute("sheet.hide", json!({"sheet": 1})).is_err()); + assert_eq!(names(&s), before); + assert!(s.doc().unwrap().wb.sheets.iter().all(|x| x.visibility == Visibility::Visible)); + s.execute("review.protectWorkbook", json!({})).unwrap(); + s.execute("sheet.rename", json!({"sheet": 0, "name": "Renamed"})).unwrap(); + s.execute("sheet.move", json!({"sheet": 0, "to": 1})).unwrap(); + s.execute("sheet.move", json!({"sheet": 0, "to": 1, "copy": true})).unwrap(); + assert_eq!(s.doc().unwrap().wb.sheets.len(), 3); + s.execute("sheet.hide", json!({"sheet": 1})).unwrap(); + s.execute("review.protectWorkbook", json!({})).unwrap(); + assert!(s.execute("sheet.unhide", json!({"sheet": 1})).is_err()); + assert!(s.execute("sheet.unhide", json!({})).is_err()); + assert_eq!(s.doc().unwrap().wb.sheets[1].visibility, Visibility::Hidden); + s.execute("review.protectWorkbook", json!({})).unwrap(); + s.execute("sheet.unhide", json!({"sheet": 1})).unwrap(); + } + #[test] fn protect_workbook_with_a_password_really_verifies() { let mut s = book(); diff --git a/crates/engine/src/cmd/sheet.rs b/crates/engine/src/cmd/sheet.rs index 9a64a0a1b..14bb064a4 100644 --- a/crates/engine/src/cmd/sheet.rs +++ b/crates/engine/src/cmd/sheet.rs @@ -767,6 +767,9 @@ fn delete_sheet(s: &mut Session, p: &Json) -> Result { } fn rename_sheet(s: &mut Session, p: &Json) -> Result { + if s.doc()?.wb.protected_structure { + return Err(EngineError::Other("The workbook is protected and cannot be changed.".into())); + } let i = target_sheet(s, p)?; let Some(name) = str_param(p, "name").map(|n| n.trim().to_string()) else { return Err(bad("sheet.rename", "missing `name`")) }; s.doc()?.wb.check_sheet_name(&name, Some(i)).map_err(EngineError::Other)?; @@ -818,6 +821,9 @@ fn rename_sheet(s: &mut Session, p: &Json) -> Result { } fn move_sheet(s: &mut Session, p: &Json) -> Result { + if s.doc()?.wb.protected_structure { + return Err(EngineError::Other("The workbook is protected and cannot be changed.".into())); + } let i = target_sheet(s, p)?; let copy = bool_param(p, "copy").unwrap_or(false); let to = p.get("to").and_then(Json::as_u64).map(|v| v as usize).ok_or_else(|| bad("sheet.move", "missing `to`"))?; @@ -895,6 +901,9 @@ fn tab_color(s: &mut Session, p: &Json) -> Result { } fn hide_sheet(s: &mut Session, p: &Json) -> Result { + if s.doc()?.wb.protected_structure { + return Err(EngineError::Other("The workbook is protected and cannot be changed.".into())); + } let i = target_sheet(s, p)?; if s.doc()?.wb.sheets.iter().filter(|s| s.visibility == Visibility::Visible).count() <= 1 { return Err(EngineError::Other("A workbook must contain at least one visible worksheet.".into())); @@ -909,6 +918,9 @@ fn hide_sheet(s: &mut Session, p: &Json) -> Result { } fn unhide_sheet(s: &mut Session, p: &Json) -> Result { + if s.doc()?.wb.protected_structure { + return Err(EngineError::Other("The workbook is protected and cannot be changed.".into())); + } if p.get("sheet").is_none() { let hidden: Vec = s.doc()?.wb.sheets.iter().filter(|s| s.visibility != Visibility::Visible).map(|s| s.name.clone()).collect(); if hidden.is_empty() {