0

I am currently working on a SharePoint online project. My code generates 6 documents in 6 different document libraries along with other metadata. When I save data to a List, the event receiver fires and creates all the 6 documents. But sometimes it stops creating the 6 documents and left with 1 or 2 documents. Sometimes documents has been created but without any metadata.

The same code is working fine in my on-premises environment, but it sometimes breaks in the Office 365 environment. I also created a log list to track the issue as we cannot debug in Office 365. In the log list I found a error message saying "Thread was being aborted." Below is my code. please help it is happening only in Office 365 environment.

namespace ExcelGen.ExcelGenReceiver
{
using System;

using Microsoft.SharePoint;

/// <summary>
/// List Item Events
/// </summary>
public class ExcelGenReceiver : SPItemEventReceiver
{
    /// <summary>
    /// The template URL
    /// </summary>
    private const string TemplateUrl = "/Quotation Analysis Electrical/Forms/Quotation Analysis Sheet ELEC_Blank.xlsm";

    /// <summary>
    /// The template url1
    /// </summary>
    private const string TemplateUrl1 = "/Quotation Analysis Mechanical/Forms/Quotation Analysis Sheet MECH_Blank.xlsm";

    /// <summary>
    /// The template url2
    /// </summary>
    private const string TemplateUrl2 = "/TenderSummaryLib/Forms/TenderSummaryBlankMaster1.xlsm";

    /// <summary>
    /// The RFI template URL
    /// </summary>
    private const string RfiTemplateUrl = "/EstimatingRFI/Forms/RFI Schedule MASTER.docx";

    /// <summary>
    /// The drawing template URL
    /// </summary>
    private const string DrawingTemplateUrl = "/Tender and Drawing Schedule/Forms/Tender Document Drawing Schedule.docx";

    /// <summary>
    /// The tender return template URL
    /// </summary>
    private const string TenderReturnTemplateUrl = "/Est_Tender_Pricing_Document/Forms/Tender Pricing Document blank.xlsm";

    /// <summary>
    /// The project number
    /// </summary>
    private string projectNumber = string.Empty;

    /// <summary>
    /// The project name
    /// </summary>
    private string projectName = string.Empty;

    /// <summary>
    /// The no bid
    /// </summary>
    private string noBid;

    /// <summary>
    /// The team
    /// </summary>
    private string team;

    /// <summary>
    /// The description
    /// </summary>
    private string description;

    /// <summary>
    /// The status
    /// </summary>
    private string status;

    /// <summary>
    /// The electrical
    /// </summary>
    private SPUser electrical;

    /// <summary>
    /// The mechanical
    /// </summary>
    private SPUser mechanical;

    /// <summary>
    /// The document date
    /// </summary>
    private DateTime? docDate;

    /// <summary>
    /// The tender received
    /// </summary>
    private DateTime? tenderReceived;

    /// <summary>
    /// The tender return
    /// </summary>
    private DateTime? tenderReturn;

    /// <summary>
    /// The pre construction program start date
    /// </summary>
    private DateTime? preConstructionProgramStart;

    /// <summary>
    /// The pre construction program end date
    /// </summary>
    private DateTime? preConstructionProgramEnd;

    /// <summary>
    /// The sector
    /// </summary>
    private string sector;

    /// <summary>
    /// The design build
    /// </summary>
    private string designBuild;

    /// <summary>
    /// The build type
    /// </summary>
    private string buildType;

    /// <summary>
    /// The service program start date
    /// </summary>
    private DateTime? serviceProgramStart;

    /// <summary>
    /// The service program completion date
    /// </summary>
    private DateTime? serviceProgramCompletion;

    /// <summary>
    /// The client1
    /// </summary>
    private string client1;

    /// <summary>
    /// The client2
    /// </summary>
    private string client2;

    /// <summary>
    /// The client3
    /// </summary>
    private string client3;

    /// <summary>
    /// The client4
    /// </summary>
    private string client4;

    /// <summary>
    /// The consultant
    /// </summary>
    private string consultant;

    /// <summary>
    /// An item is being added.
    /// </summary>
    /// <param name="properties">The Item Event properties</param>
    public override void ItemAdded(SPItemEventProperties properties)
    {
        //this.EventFiringEnabled = false;

        var web = properties.Web;
        var listItem = properties.ListItem;

        try
        {
            LogIssue(web, null, "Item Added", "List Item Id {0}", listItem.ID);

            if (!this.AttemptCopyProcess(listItem))
            {
                LogIssue(web, null, "List Id : " + listItem.ID, "AttemptCopyProcess failed.");
            }
        }
        catch (Exception ex)
        {
            LogIssue(web, ex, "List Id : " + listItem.ID, "AttemptCopyProcess failed.");
        }
        finally
        {
            //this.EventFiringEnabled = true;
            LogIssue(web, null, "List Id : " + listItem.ID, "Event Receiver completed sucessfully.");
        }
    }

    /// <summary>
    /// Logs any issues found
    /// </summary>
    /// <param name="webContext">The web context.</param>
    /// <param name="exception">The exception, if null not exception details are written</param>
    /// <param name="contextId">The context identifier, a unique identifier that allows us to know where the call originated, i.e. a List and ListItem Id, or a Page Url</param>
    /// <param name="comment">The comment.</param>
    /// <param name="args">The arguments.</param>
    private static void LogIssue(SPWeb webContext, Exception exception, string contextId, string comment, params object[] args)
    {
        //// if (webContext.AllProperties.ContainsKey("EnableLogging"))
        //// {
            var list = webContext.Lists.TryGetList("ErrorIssues");
            if (list != null)
            {
                var item = list.AddItem();

                item["Title"] = contextId;

                if (exception != null)
                {
                    item["Message"] = exception.Message;
                    item["InnerException"] = exception.InnerException ?? (object)string.Empty;
                    item["StackTrace"] = exception.StackTrace;
                }

                if (!string.IsNullOrEmpty(comment))
                {
                    item["Comment"] = string.Format(comment, args);
                }

                item.Update();
            }
        //// }
    }

    /// <summary>
    /// Assigns the field.
    /// </summary>
    /// <param name="listItem">The list item.</param>
    /// <param name="fieldName">Name of the field.</param>
    /// <param name="contextId">The context identifier.</param>
    /// <returns>The fields string value if present, otherwise string.empty.</returns>
    private static string AssignField(SPListItem listItem, string fieldName, string contextId)
    {
        var fieldValue = string.Empty;

        if (listItem.Fields.ContainsField(fieldName) && listItem[fieldName] != null)
        {
            fieldValue = listItem[fieldName].ToString();
        }
        else
        {
            LogIssue(listItem.Web, null, contextId, string.Format("Field not available : {0}", fieldName));
        }

        return fieldValue;
    }

    /// <summary>
    /// Assigns the field as a DateTime
    /// </summary>
    /// <param name="listItem">The list item.</param>
    /// <param name="fieldName">Name of the field.</param>
    /// <param name="contextId">The context identifier.</param>
    /// <returns>Returns the date if found, otherwise returns DateTime.MinValue</returns>
    private static DateTime? AssignDateField(SPListItem listItem, string fieldName, string contextId)
    {
        DateTime? fieldValue = null;

        if (listItem.Fields.ContainsField(fieldName) && listItem[fieldName] != null)
        {
            fieldValue = Convert.ToDateTime(listItem[fieldName].ToString());
        }
        else
        {
            LogIssue(listItem.Web, null, contextId, string.Format("Field not available : {0}", fieldName));
        }

        return fieldValue;
    }

    /// <summary>
    /// Creates the folder.
    /// </summary>
    /// <param name="listdoc">The list.</param>
    /// <param name="folderName">Name of the folder.</param>
    private static void CreateFolder(SPList listdoc, string folderName)
    {

        LogIssue(listdoc.ParentWeb, null, "List Id : " + listdoc.ID, "Creating folder {0} in {1}", folderName, listdoc.RootFolder.ServerRelativeUrl);

        // Updated by Indusnet
        SPListItem folder1 = listdoc.Items.Add(listdoc.RootFolder.ServerRelativeUrl, SPFileSystemObjectType.Folder);
        folder1["Name"] = folderName;
        folder1.Update();
        listdoc.Update();
        // End Updated
    }

    /// <summary>
    /// Assigns the user field.
    /// </summary>
    /// <param name="listItem">The list item.</param>
    /// <param name="fieldName">Name of the field.</param>
    /// <param name="contextId">The context identifier.</param>
    /// <returns>Returns the user if found, otherwise null.</returns>
    private static SPUser AssignUserField(SPListItem listItem, string fieldName, string contextId)
    {
        SPUser user = null;

        if (listItem.Fields.ContainsField(fieldName) && listItem[fieldName] != null)
        {
            var userField = (SPFieldUser)listItem.Fields.GetField(fieldName);
            var fieldValue = (SPFieldUserValue)userField.GetFieldValue(listItem["electrical_proj_manager"].ToString());

            user = fieldValue.User;
        }
        else
        {
            LogIssue(listItem.Web, null, contextId, string.Format("Field not available : {0}", fieldName));
        }

        return user;
    }

    /// <summary>
    /// Attempts the copy process.
    /// </summary>
    /// <param name="listItem">The list item.</param>
    /// <returns>True if it successfully processed, false otherwise.</returns>
    private bool AttemptCopyProcess(SPListItem listItem)
    {
        if (listItem.ParentList.Title != "Enquiry_Template")
        {
            LogIssue(listItem.Web, null, "List Id : " + listItem.ID, "ListItem titles is not Enquiry_Template, aborting.");
            return false;
        }

        var finalNum = "15-" + new Random().Next(0, 9999).ToString("D4");

        this.Initialize(listItem, finalNum);

        if (this.noBid != "Yes")
        {
            LogIssue(listItem.Web, null, "List Id : " + listItem.ID, "The noBid field does not equal Yes, aborting.");
            return false;
        }

        this.CopyFiles(listItem.Web, finalNum);
        this.CreateFolders(listItem.Web);



        return true;
    }

    /// <summary>
    /// Copies the files.
    /// </summary>
    /// <param name="web">The web.</param>
    /// <param name="finalNum">The final number.</param>
    private void CopyFiles(SPWeb web, string finalNum)
    {
        LogIssue(web, null, "Web Id : " + web.ID, "Setting the copying of files ...");

        var mechanicalQuoteList = web.Lists["Quotation Analysis Mechanical"];
        var electricalQuoteList = web.Lists["Quotation Analysis Electrical"];
        var tenderSummarList = web.Lists["TenderSummaryLib"];
        var estimatingList = web.Lists["EstimatingRFI"];
        var tenderSheduleList = web.Lists["Tender and Drawing Schedule"];
        var tenderPricingList = web.Lists["Est_Tender_Pricing_Document"];

        var url1 = mechanicalQuoteList.RootFolder.ServerRelativeUrl;
        var url = electricalQuoteList.RootFolder.ServerRelativeUrl;
        var url2 = tenderSummarList.RootFolder.ServerRelativeUrl;
        var urlA = estimatingList.RootFolder.ServerRelativeUrl;
        var urlB = tenderSheduleList.RootFolder.ServerRelativeUrl;
        var urlC = tenderPricingList.RootFolder.ServerRelativeUrl;

        var foldername1 = string.Empty;
        var foldername = string.Empty;
        var foldername2 = string.Empty;
        var foldernameA = string.Empty;
        var foldernameB = string.Empty;
        var foldernameC = string.Empty;

        var folder1 = web.Folders[url1 + "/" + foldername1];
        var folder = web.Folders[url + "/" + foldername];
        var folder2 = web.Folders[url2 + "/" + foldername2];
        var folderA = web.Folders[urlA + "/" + foldernameA];
        var folderB = web.Folders[urlB + "/" + foldernameB];
        var folderC = web.Folders[urlC + "/" + foldernameC];

        if (!folder1.Exists && !folder.Exists && !folder2.Exists && !folderA.Exists && !folderB.Exists && !folderC.Exists)
        {
            var folders1 = web.GetFolder(url1).SubFolders;
            var folders = web.GetFolder(url).SubFolders;
            var folders2 = web.GetFolder(url2).SubFolders;
            var foldersA = web.GetFolder(urlA).SubFolders;
            var foldersB = web.GetFolder(urlB).SubFolders;
            var foldersC = web.GetFolder(urlC).SubFolders;
            folders1.Add(foldername1);
            folders.Add(foldername);
            folders2.Add(foldername2);
            foldersA.Add(foldernameA);
            foldersB.Add(foldernameB);
            foldersC.Add(foldernameC);
        }

        var file1 = web.GetFile(web.Site.Url + TemplateUrl1);
        var file = web.GetFile(web.Site.Url + TemplateUrl);
        var file2 = web.GetFile(web.Site.Url + TemplateUrl2);
        var fileA = web.GetFile(web.Site.Url + RfiTemplateUrl);
        var fileB = web.GetFile(web.Site.Url + DrawingTemplateUrl);
        var fileC = web.GetFile(web.Site.Url + TenderReturnTemplateUrl);

        if (file1 != null && file != null && file2 != null && fileA != null && fileB != null && fileC != null)
        {

            var fileName = string.Format("{0}/{1}{2}", folder1.ServerRelativeUrl, this.projectName, ".xlsm");
            LogIssue(web, null, "Web Id : " + web.ID, "Copying file 1 {0} to {1}...", file1.Name, fileName);

            var byteArray1 = file1.OpenBinary();
            var uploadedFile1 = folder1.Files.Add(fileName, byteArray1, true);
            LogIssue(web, null, "Web Id : " + web.ID, "File 1 Uploaded with new ID of {0}", uploadedFile1.Item.ID);

            this.EventFiringEnabled = false;
            var listitem1 = uploadedFile1.Item;
            listitem1["Name"] = this.projectName;
            listitem1["EnquiryNo"] = finalNum;
            listitem1["Project_Name"] = this.projectName;
            listitem1["Tender_Received"] = this.tenderReceived;
            listitem1["Tender_Return"] = this.tenderReturn;

            listitem1["Quotation_Analysis_Mech_Url"] = "https://my site url/Quotation%20Analysis%20Mechanical/Forms/DispForm.aspx?ID=" + listitem1.ID;
            listitem1.SystemUpdate(false);
            this.EventFiringEnabled = true;
            LogIssue(web, null, "Web Id : " + web.ID, "Copied file 1.");




            fileName = string.Format("{0}/{1}{2}", folder.ServerRelativeUrl, this.projectName, ".xlsm");
            LogIssue(web, null, "Web Id : " + web.ID, "Copying file 2 {0} to {1}...", file.Name, fileName);

            var byteArray = file.OpenBinary();
            var uploadedFile = folder.Files.Add(fileName, byteArray, true);
            LogIssue(web, null, "Web Id : " + web.ID, "File 2 Uploaded with new ID of {0}", uploadedFile.Item.ID);

            this.EventFiringEnabled = false;
            var listitem = uploadedFile.Item;
            listitem["Name"] = this.projectName;
            listitem["EnquiryNo"] = finalNum;
            listitem["Project_Name"] = this.projectName;
            listitem["Tender_Received"] = this.tenderReceived;
            listitem["Tender_Return"] = this.tenderReturn;

            listitem["Quotation_Analysis_Elec_Url"] = "https://my site url/Quotation%20Analysis%20Electrical/Forms/DispForm.aspx?ID=" + listitem.ID;
            listitem.SystemUpdate(false);
            this.EventFiringEnabled = true;
            LogIssue(web, null, "Web Id : " + web.ID, "Copied file 2.");



            fileName = string.Format("{0}/{1}{2}", folderA.ServerRelativeUrl, this.projectName, ".docx");
            LogIssue(web, null, "Web Id : " + web.ID, "Copying file 3 {0} to {1}...", fileA.Name, fileName);

            var byteArrayA = fileA.OpenBinary();
            var uploadedFileA = folderA.Files.Add(fileName, byteArrayA, true);
            LogIssue(web, null, "Web Id : " + web.ID, "File 3 Uploaded with new ID of {0}", uploadedFileA.Item.ID);
            this.EventFiringEnabled = false;
            var listitemA = uploadedFileA.Item;
            listitemA["Name"] = this.projectName;
            listitemA["EnquiryNo"] = finalNum;
            listitemA["Project_Name"] = this.projectName;
            listitemA["Date"] = this.docDate;
            listitemA["Description"] = this.description;
            listitemA["ProjectNo"] = this.projectNumber;
            listitemA["RFIUrl"] = "https://my site url/EstimatingRFI/Forms/DispForm.aspx?ID=" + listitemA.ID;
            listitemA.SystemUpdate(false);
            this.EventFiringEnabled = true;
            LogIssue(web, null, "Web Id : " + web.ID, "Copied file 3.");



            fileName = string.Format("{0}/{1}{2}", folderB.ServerRelativeUrl, this.projectName, ".docx");
            LogIssue(web, null, "Web Id : " + web.ID, "Copying file 4 {0} to {1}...", fileB.Name, fileName);

            var byteArrayB = fileB.OpenBinary();
            var uploadedFileB = folderB.Files.Add(fileName, byteArrayB, true);
            LogIssue(web, null, "Web Id : " + web.ID, "File 4 Uploaded with new ID of {0}", uploadedFileB.Item.ID);

            this.EventFiringEnabled = false;
            var listitemB = uploadedFileB.Item;
            listitemB["Name"] = this.projectName;
            listitemB["EnquiryNo"] = finalNum;
            listitemB["Project_Name"] = this.projectName;
            listitemB["Date"] = this.docDate;
            listitemB["Description"] = this.description;
            listitemB["ProjectNo"] = this.projectNumber;
            listitemB["DrawingURL"] = "https://my site url/Tender%20and%20Drawing%20Schedule/Forms/DispForm.aspx?ID=" + listitemB.ID;
            listitemB.SystemUpdate(false);
            this.EventFiringEnabled = true;
            LogIssue(web, null, "Web Id : " + web.ID, "Copied file 4.");


            fileName = string.Format("{0}/{1}{2}", folderC.ServerRelativeUrl, this.projectName, ".xlsm");
            LogIssue(web, null, "Web Id : " + web.ID, "Copying file 5 {0} to {1}...", fileC.Name, fileName);

            var byteArrayC = fileC.OpenBinary();
            var uploadedFileC = folderC.Files.Add(fileName, byteArrayC, true);

            LogIssue(web, null, "Web Id : " + web.ID, "File 5 Uploaded with new ID of {0}", uploadedFileC.Item.ID);
            this.EventFiringEnabled = false;
            var listitemC = uploadedFileC.Item;
            listitemC["Name"] = this.projectName;
            listitemC["EnquiryNo"] = finalNum;
            listitemC["Project_Name"] = this.projectName;
            listitemC["Date"] = this.docDate;
            listitemC["Description"] = this.description;
            listitemC["ProjectNo"] = this.projectNumber;
            listitemC["PricingURL"] = "https://my site url/Est_Tender_Pricing_Document/Forms/DispForm.aspx?ID=" + listitemC.ID;
            listitemC.SystemUpdate(false);
            this.EventFiringEnabled = true;
            LogIssue(web, null, "Web Id : " + web.ID, "Copied file 5.");



            fileName = string.Format("{0}/{1}{2}", folder2.ServerRelativeUrl, this.projectName, ".xlsm");
            LogIssue(web, null, "Web Id : " + web.ID, "Copying file 5 {0} to {1}...", file2.Name, fileName);

            var byteArray2 = file2.OpenBinary();
            var uploadedFile2 = folder2.Files.Add(fileName, byteArray2, true);

            LogIssue(web, null, "Web Id : " + web.ID, "File 6 Uploaded with new ID of {0}", uploadedFile2.Item.ID);
            this.EventFiringEnabled = false;
            var listitem2 = uploadedFile2.Item;
            listitem2["Name"] = this.projectName;
            listitem2["EnquiryNo"] = finalNum;
            listitem2["Project_Name"] = this.projectName;
            listitem2["Date"] = this.docDate;
            listitem2["Team"] = this.team;
            listitem2["Estimator_Electrical"] = this.electrical;
            listitem2["Estimator_Mechanical"] = this.mechanical;
            listitem2["Status"] = this.status;
            listitem2["Tender_Received"] = this.tenderReceived;
            listitem2["Tender_Return"] = this.tenderReturn;
            listitem2["Sector"] = this.sector;
            listitem2["Design_Build"] = this.designBuild;
            listitem2["Build_Type"] = this.buildType;
            listitem2["Service_Prog_Start"] = this.serviceProgramStart;
            listitem2["Service_Prog_Completion"] = this.serviceProgramCompletion;
            listitem2["Client_1"] = this.client1;
            listitem2["Client_2"] = this.client2;
            listitem2["Client_3"] = this.client3;
            listitem2["Client_4"] = this.client4;
            listitem2["Consultant"] = this.consultant;
            listitem2["Pre-construction_Prog_Start"] = this.preConstructionProgramStart;
            listitem2["Pre-construction_Prog_End"] = this.preConstructionProgramEnd;
            listitem2["Tender_Summary_Url"] = "https://my site url/TenderSummaryLib/Forms/DispForm.aspx?ID=" + listitem2.ID;
            listitem2.SystemUpdate(false);
            this.EventFiringEnabled = true;
            LogIssue(web, null, "Web Id : " + web.ID, "Copied file 6.");
        }
    }

    /// <summary>
    /// Creates the folders.
    /// </summary>
    /// <param name="web">The web.</param>
    private void CreateFolders(SPWeb web)
    {
        var projectListId = web.Lists.Add(this.projectName.Replace(' ', '_'), string.Empty, SPListTemplateType.DocumentLibrary);
        var projectList = web.Lists[projectListId];
        projectList.OnQuickLaunch = true; // The document library will appear in Quick Launch bar.

        CreateFolder(projectList, "1.Tender Documents");
        CreateFolder(projectList, "2. Electrical");
        CreateFolder(projectList, "3. Mechanical");
        CreateFolder(projectList, "4. Correspondance");
        CreateFolder(projectList, "5. Settlement Meeting Docs");
        CreateFolder(projectList, "6. Tender Return Docs");
        CreateFolder(projectList, "7. Tender Handover");

        projectList.Update();
    }

    /// <summary>
    /// Initializes the specified list item.
    /// </summary>
    /// <param name="listItem">The list item.</param>
    /// <param name="finalNum">The final number.</param>
    private void Initialize(SPListItem listItem, string finalNum)
    {
        var contextId = string.Format("List:{0}, Id:{1}", listItem.ParentList.Title, listItem.ID);

        this.noBid = AssignField(listItem, "Bid", contextId);
        this.projectName = AssignField(listItem, "Project_Name", contextId);

        var teamlookup = AssignField(listItem, "Team", contextId);
        var lookupParts = teamlookup.Split(new[] { ";#" }, StringSplitOptions.None);
        this.team = lookupParts[1];

        this.description = AssignField(listItem, "Description", contextId);
        this.status = AssignField(listItem, "enquiry_status", contextId);

        this.electrical = AssignUserField(listItem, "electrical_proj_manager", contextId);
        this.mechanical = AssignUserField(listItem, "mechanical_proj_manager", contextId);

        this.docDate = AssignDateField(listItem, "Date", contextId);
        this.tenderReceived = AssignDateField(listItem, "Tender_Received", contextId);
        this.tenderReturn = AssignDateField(listItem, "Tender_Return", contextId);
        this.preConstructionProgramStart = AssignDateField(listItem, "Pre-construction_Prog_Start", contextId);
        this.preConstructionProgramEnd = AssignDateField(listItem, "Pre-construction_Prog_End", contextId);

        this.sector = AssignField(listItem, "Sector", contextId);
        this.designBuild = AssignField(listItem, "Design_Build", contextId);
        this.buildType = AssignField(listItem, "Build_Type", contextId);

        this.serviceProgramStart = AssignDateField(listItem, "Service_Prog_Start", contextId);
        this.serviceProgramCompletion = AssignDateField(listItem, "Service_Prog_Completion", contextId);

        this.client1 = AssignField(listItem, "Client_1", contextId);
        this.client2 = AssignField(listItem, "Client_2", contextId);
        this.client3 = AssignField(listItem, "Client_3", contextId);
        this.client4 = AssignField(listItem, "Client_4", contextId);
        this.consultant = AssignField(listItem, "Consultant", contextId);

        if (this.status == "Won")
        {
            this.projectNumber = string.Format("15-{0}-{1}", this.team, new Random().Next(0, 9999).ToString("D4"));
        }

        if (this.status == "Active" || this.status == "Closed")
        {
            this.projectNumber = "No Project No";
        }

        listItem["ProjectNo"] = this.projectNumber;
        listItem["EnquiryNo"] = finalNum;

        listItem.Web.AllowUnsafeUpdates = true;
        listItem.SystemUpdate(false);

    }
}
}
0

I am digging out event receiver for SharePoint online for few weeks. I believe SP online supports only Remote Event Receivers. But your code looks like Server side event receiver.

Please confirm how you are doing it.

Thanks, V.Vetrivelmurugan

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.