How to ignore already protected sheets with script? - google-apps-script

Every day create 2-3 sheets but a minimum of one
these ranges are protecting ["B3:U27", "W3:AP27", "B29:U33", "W29:AP33"]
I reduced the 42 ranges to these 4 ranges to make it faster but still
in 1 minute it can protect about 8 files the problem is that in a few months it can grow more then 100 files
which would take me up to the 6 minute timeout limit and that would interrupt the script.
This is the script I am currently using.
I wonder if it could be modified in some way to ignore the already protected sheets?
function main(){ //Main function to run
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var disregard = ["List", "Data", "Template"]; //ADD SHEET NAMES HERE THAT YOU WANT TO BE DISREGARDED
for(var x=0; x<sheets.length; x++){
if(disregard.some(data => sheets[x].getName().includes(data))){
//E.g. Disregard any sheet names added on the "disregard" array
}else{
unlockCertainRanges(sheets[x]);
}
}
}
function unlockCertainRanges(currentSheet){ //Function to unlock certain ranges on your spreadsheet
var sheet = currentSheet;
// Remove all range protections in the spreadsheet
var protections = sheet.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
protection.remove();
}
var protection = sheet.protect();
//restrict editors to owner
protection.getRange().getA1Notation();
var eds = protection.getEditors();
protection.removeEditors(eds);
//set unprotected ranges
var ranges = protection.getUnprotectedRanges();
var data = ["B3:U27", "W3:AP27", "B29:U33", "W29:AP33"]; // ADD YOUR RANGES HERE
data.forEach(res => { //LOOPS INTO EVERY ARRAY CONTAINING SPECIFIC RANGES
ranges.push(sheet.getRange(res));
protection.setUnprotectedRanges(ranges); //REMOVES THE PROTECTION ON THE RANGE
});
}
Can it be something that has already been protected or have a padlock on them not to be touched?
I tried to find a way to retrieve the names of the already protected sheets.
I mean something like getSheetName() but for the protected ones.
Or maybe put it in the exceptions if there is already such protection on this description?
setDescription('Already protected');
I don't have much experience in coding; I found a very similar question but I didn't understand much of the code
Does anyone have an idea?

I believe what #MetaMan simply means is that, you need to check first if the sheet does contain a protected range. See code below.
Code:
function main() {
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
// Get list of sheets protected
var protections = SpreadsheetApp.getActiveSpreadsheet().getProtections(SpreadsheetApp.ProtectionType.SHEET);
var protectedSheets;
// If protections isn't set, initialize as empty array
if (protections)
protectedSheets = protections.map(protection => protection.getDescription());
else
protectedSheets = [];
var disregard = ["List", "Data", "Template"]; //ADD SHEET NAMES HERE THAT YOU WANT TO BE DISREGARDED
for (var x = 0; x < sheets.length; x++) {
if (disregard.some(data => sheets[x].getName().includes(data))) {
//E.g. Disregard any sheet names added on the "disregard" array
} else {
// If protectedSheets doesn't include the name, process the sheet
if (!protectedSheets.includes(sheets[x].getName()))
unlockCertainRanges(sheets[x]);
}
}
}
function unlockCertainRanges(currentSheet) {
Logger.log("\"" + currentSheet.getName() + "\" is being processed");
var sheet = currentSheet;
var protections = sheet.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
protection.remove();
}
// set names of sheets as description for future checks
var protection = sheet.protect().setDescription(currentSheet.getName());
//restrict editors to owner
protection.getRange().getA1Notation();
var eds = protection.getEditors();
protection.removeEditors(eds);
//set unprotected ranges
var ranges = protection.getUnprotectedRanges();
var data = ["B3:U27", "W3:AP27", "B29:U33", "W29:AP33"]; // ADD YOUR RANGES HERE
data.forEach(res => { //LOOPS INTO EVERY ARRAY CONTAINING SPECIFIC RANGES
ranges.push(sheet.getRange(res));
protection.setUnprotectedRanges(ranges); //REMOVES THE PROTECTION ON THE RANGE
});
}
// function to delete all existing protections
function deleteAllProtections() {
var protections = SpreadsheetApp.getActiveSpreadsheet().getProtections(SpreadsheetApp.ProtectionType.SHEET);
protections.forEach(protection => protection.remove());
}
Note:
Note that the first run will need to run deleteAllProtections() so all sheets will have no protections for the first time. Succeeding runs will now skip those sheet with protections.
Reference:
getProtections

Related

Protecting Range through Google Apps Script

I am trying to protect a range through google apps script in my google sheet. Whenever the data point in column O is checked i.e. value is yes. I would like that row be protected.
Secondly, I have prior protected columns E:H and M:N, which should stay protected.
There are around 1000 rows, 10-15 new rows that need to be protected daily. By the code I have currently written, it removes the protection and then re-adds it, which takes a lot of time. If I remove the part where it removes the rights then it still re-adds the same protection regardless.
Is there anyway to check if the cell is protected, if protected move onto the next row?
Secondly, I'm unable to provide access to the other emails i.e. "add editors"
function removecompleted(){
var ss = SpreadsheetApp.getActiveSpreadsheet()
var purchased = ss.getSheetByName("Purchased Inventory")
last_row_purchased = purchased.getLastRow()
var emails = [
'user#domain.com'
];
var protections = purchased.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
if (protection.canEdit()) {
protection.remove();
}
}
for(var i = 1; i < last_row_purchased; i++) {
if(purchased.getRange(i, 15).getValue() == "Yes")
{
target_range = purchased.getRange(i, 1, 1, 15)
protection = target_range.protect().setDescription('Sample protected range');
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors())
protection.addEditors(emails);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
}
var range = purchased.getRange('E:H');
var protection = range.protect().setDescription('Always Protected');
var range = purchased.getRange('M:N');
var protection = range.protect().setDescription('Always Protected');
}
You can do this on edit, but rather than checking to see if the cell is protected (which is not that much faster than just setting the protection anyway) you can just check the row of each protected range and remove the protection if its row is equal to the row of the cell that was just unticked:
function onEdit(e) {
// List of sheet names the function should run on
const sheets = ["Sheet1", "Sheet2", "Sheet3", "etc"]
const sheet = e.source.getActiveSheet()
// if current sheet is not in permitted sheets, return
if (!~sheets.indexOf(sheet.getName())) {
return
}
const row = e.range.getRow()
if (e.range.getColumn() === 15) {
const ss = e.source
if (e.value === "TRUE") {
range = sheet.getRange(`${row}:${row}`)
const me = Session.getEffectiveUser().getEmail()
const protection = range.protect()
protection.getEditors().forEach(user => {
protection.removeEditor(user.getEmail())
})
protection.addEditor(me)
}
else {
const protections = sheet.getProtections(SpreadsheetApp.ProtectionType.RANGE)
protections.forEach(function(p) {
if (p.getRange().getRow() === row) {
if (row === 1 && p.getRange().getColumn() !== 1) return
p.remove()
}
})
}
}
}
Code Rundown:
Get the row number of the edited range
Check if the edited cell was column 15
If the value of the cell is "TRUE", protect the row, and remove all editors aside yourself.
If the value of the cell is "FALSE", then get all the sheet's protections, check the value of their row, and if the row matches the edited cell's row, then remove that protection
NB : The final check also has a condition to check if the column is not equal to 1 so to not remove the protections on E:H or M:N.
References:
Class Protection
Class: Sheet
Class: Range

How to automatically merge cells in Google Sheets upon creation of new Sheet

I am trying to figure out a way to make Google Sheets automatically merge Cells A1-C1 when a new sheet is created. My coworker and I have been trying to figure out the script that would make this happen, but everything we have tried only changes the previous Sheet we were working on, not the new one.
So far these are the two scripts we have tried, just to get some sort of result we are looking for:
function formatCells() {
var ss = SpreadsheetApp.getActiveSpreadsheet ();
var s = ss.getSheetByName('Combined')
var range = s.getDataRange()
var values = range.getValues();
for( var row = values.length -1; row >= 0; --row)
if (values[row][1] == 'Hello')
{s.getRange(row+1,1).mergeAcross();
}
}
and
function newSheetTrigger() {
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger('newSheet')
.forSpreadsheet(ss)
.onChange()
.create();
}
function newSheet(e){
if (e.changeType == 'INSERT_GRID') {
SpreadsheetApp.flush();
SpreadsheetApp.getActiveSheet().getRange('A1:C1').merge();
}
}
Does anyone have an idea of where we went wrong?
The problem is that theonChange trigger is not able to detect the active sheet correctly
Retrieving the active sheet on trigger will always return you the first sheet, as you can easily verify with
function myFunction(e) {
Logger.log(e.changeType);
if(e.changeType=="INSERT_GRID"){
Logger.log(SpreadsheetApp.getActive().getActiveSheet().getName());
}
}
So you need to implement a workaround.
For example:
Strore the present sheet names in Script properties
When the trigger fires and the condition e.changeType=="INSERT_GRID" is fullfilled:
Compare the currently present sheet number to the one stored in script properties to evaluate either a new sheet has been inserted
If the sheet number increased - find the name of the new sheet with indexOf()
Merge cells on the new sheet and update the script properties
Code snippet:
var ss = SpreadsheetApp.getActive();
//run me once
function firstSetUp(){
var sheets = ss.getSheets();
var names = [];
for (var i = 0; i < sheets.length; i++){
names.push(sheets[i].getName())
}
PropertiesService.getScriptProperties().setProperty("sheets", JSON.stringify(names) );
}
//run me on trigger
function newSheet(e) {
if(e.changeType=="INSERT_GRID"){
var newSheets = ss.getSheets();
var oldSheetNames = JSON.parse(PropertiesService.getScriptProperties().getProperty("sheets"));
Logger.log(oldSheetNames);
var length = oldSheetNames.length;
Logger.log("length : " + length);
if (length != newSheets.length){
for (var i = 0; i < newSheets.length; i++){
if(oldSheetNames.indexOf(newSheets[i].getName()) == -1){
var newSheet = newSheets[i];
Logger.log(newSheet.getName());
newSheet.getRange('A1:C1').merge();
oldSheetNames.push(newSheet.getName());
PropertiesService.getScriptProperties().setProperty("sheets", JSON.stringify(oldSheetNames));
break;
}
}
}
}
}

Google Sheets - Locking Cells after value reached and one other thing

Making a self-scheduling spreadsheet for my co-workers. The goal is for them to put their hours (12 or 6) in the cell that corresponds with the date and their name. I am trying to have it so that a date column will lockout after the sum cell for that column reaches a certain total.
I am using this (Google Script Lock Cells) as my basis but I am not having much luck.
function myFunction() {
function onOpen() {
var ss = SpreadsheetApp.getActive();
var source = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var cell = source.getRange("B12").getValue();
var range = ss.getRange("B2:B11");
if (cell == 10) {
// Protect range B2:B11 if cell 'B12' = 10
var protection = range.protect().setDescription('Sample protected range');
Logger.log
} else {
// Remove protection if cell 'B12' is anything other than 10
var protections = ss.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
protection.remove();
}
}
}
}
I am also looking to have the sum represent shits rather than hours so if a person wrote "12" the sum column would record "1"
Any help would be amazing.
You should change several things
Do not nest onOpen inside myFunction - it will not work
If you want the protections to be update on every update of B12 - you should use the onEdit trigger instead of onOpen
It is better to replace cell == 10 through cell >= 10 to account for the possibility that the value accidentally trespasses the maximum
4.It is not enough to create a protection, to make it useful you need to remove editors from it
Only the owner of the script (you) should maintain access
For this, use an installable trigger instead of a simple one, to make sure that it runs always as you and not the user that opens the document
Sample (to be bound to an installable onEdit trigger):
function bindAnOnEditTiggerToMe() {
var ss = SpreadsheetApp.getActive();
var source = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var cell = source.getRange("B12").getValue();
var range = ss.getRange("B2:B11");
if (cell >= 10) {
// Protect range B2:B11 if cell 'B12' = 10
var protection = range.protect().setDescription('Sample protected range');
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
} else {
// Remove protection if cell 'B12' is anything other than 10
var protections = ss.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
protection.remove();
}
}
}

Google sheet conditional formatting and lock cell [duplicate]

I am trying to implement a script that locks a certain range of cells when a condition is true. Here is the link to my document:
https://docs.google.com/spreadsheets/d/1XShGxlz2fA2w2omth-TvYc7cK0nXvVMrhwRKafzVjOA/edit?usp=sharing
Basically I am sharing this document with a group of people so they fill their mail addresses in column B and put a number 1 in column C so it increments my counters for each slot. What I am trying to do is to lock each slot when it is full so other people can no more edit these slots but the problem is with my if statement if (cell1 == 10). The range is always locked even if the if condition is not realized. Here is my code :
function onOpen() {
var ss = SpreadsheetApp.getActive();
var source = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var cell=60;
var cell1 = source.getRange("G2").getValue();
if (cell1 == 10){
// Protect range B2:B11, then remove all other users from the list of editors.
var ss = SpreadsheetApp.getActive();
var range = ss.getRange('B2:B11');
var protection = range.protect().setDescription('Sample protected range');
// Ensure the current user is an editor before removing others. Otherwise, if the user's edit
// permission comes from a group, the script will throw an exception upon removing the group.
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
}
As Andy says in the comments, you need to explicitly remove the protection if cell G2 does not equal 10. (This code removes all protections).
Reading the Protection Class page, where you got the snippets from, I couldn't get a handle on the way editor privileges would factor into your needs, so this script will work if others are editors. If you don't want them to be editors, you'll have to add the relevant code.
function onOpen() {
var ss = SpreadsheetApp.getActive();
var source = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var cell = source.getRange("G2").getValue();
var range = ss.getRange('B2:B11');
if (cell == 10) {
// Protect range B2:B11 if cell 'G2' = 10
var protection = range.protect().setDescription('Sample protected range');
Logger.log
} else {
// Remove protection if cell 'G2' is anything other than 10
var protections = ss.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
protection.remove();
}
}
}

Google Script Lock Cells

I am trying to implement a script that locks a certain range of cells when a condition is true. Here is the link to my document:
https://docs.google.com/spreadsheets/d/1XShGxlz2fA2w2omth-TvYc7cK0nXvVMrhwRKafzVjOA/edit?usp=sharing
Basically I am sharing this document with a group of people so they fill their mail addresses in column B and put a number 1 in column C so it increments my counters for each slot. What I am trying to do is to lock each slot when it is full so other people can no more edit these slots but the problem is with my if statement if (cell1 == 10). The range is always locked even if the if condition is not realized. Here is my code :
function onOpen() {
var ss = SpreadsheetApp.getActive();
var source = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var cell=60;
var cell1 = source.getRange("G2").getValue();
if (cell1 == 10){
// Protect range B2:B11, then remove all other users from the list of editors.
var ss = SpreadsheetApp.getActive();
var range = ss.getRange('B2:B11');
var protection = range.protect().setDescription('Sample protected range');
// Ensure the current user is an editor before removing others. Otherwise, if the user's edit
// permission comes from a group, the script will throw an exception upon removing the group.
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
}
As Andy says in the comments, you need to explicitly remove the protection if cell G2 does not equal 10. (This code removes all protections).
Reading the Protection Class page, where you got the snippets from, I couldn't get a handle on the way editor privileges would factor into your needs, so this script will work if others are editors. If you don't want them to be editors, you'll have to add the relevant code.
function onOpen() {
var ss = SpreadsheetApp.getActive();
var source = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var cell = source.getRange("G2").getValue();
var range = ss.getRange('B2:B11');
if (cell == 10) {
// Protect range B2:B11 if cell 'G2' = 10
var protection = range.protect().setDescription('Sample protected range');
Logger.log
} else {
// Remove protection if cell 'G2' is anything other than 10
var protections = ss.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
protection.remove();
}
}
}