From 7bf6103ad95c281c2ed680c9eb9b07cc584ddc91 Mon Sep 17 00:00:00 2001
From: Cosmic Linden <cosmic@lindenlab.com>
Date: Tue, 25 Jul 2023 14:52:56 -0700
Subject: [PATCH] SL-20024: (WIP) (not tested) Improved behavior for saving
 material to inventory. Check perms, keep perms.

---
 indra/llinventory/llpermissions.h  |   4 +-
 indra/newview/llmaterialeditor.cpp | 164 +++++++++++++++++++++--------
 indra/newview/llmaterialeditor.h   |   8 +-
 indra/newview/llpanelface.cpp      |   2 +-
 4 files changed, 129 insertions(+), 49 deletions(-)

diff --git a/indra/llinventory/llpermissions.h b/indra/llinventory/llpermissions.h
index 27252f7b97d..32fae844fbf 100644
--- a/indra/llinventory/llpermissions.h
+++ b/indra/llinventory/llpermissions.h
@@ -251,7 +251,9 @@ class LLPermissions
 	BOOL setNextOwnerBits(const LLUUID& agent, const LLUUID& group, BOOL set, PermissionMask bits);
 	
 	// This is currently only used in the Viewer to handle calling cards
-	// where the creator is actually used to store the target. Use with care.
+	// where the creator is actually used to store the target.
+	// It is also used for saving a GLTF material on a prim.
+	// Use with care.
 	void setCreator(const LLUUID& creator) { mCreator = creator; }
 	
 	//
diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp
index b30f94e1458..6e043ecee5f 100644
--- a/indra/newview/llmaterialeditor.cpp
+++ b/indra/newview/llmaterialeditor.cpp
@@ -237,6 +237,7 @@ struct LLSelectedTEGetMatData : public LLSelectedTEFunctor
     LLUUID mTexEmissiveId;
     LLUUID mTexNormalId;
     LLUUID mObjectId;
+    LLViewerObject* mObject = nullptr;
     S32 mObjectTE;
     LLUUID mMaterialId;
     LLPointer<LLGLTFMaterial> mMaterial;
@@ -293,6 +294,7 @@ bool LLSelectedTEGetMatData::apply(LLViewerObject* objectp, S32 te_index)
                 mTexNormalId = tex_normal_id;
                 mObjectTE = te_index;
                 mObjectId = objectp->getID();
+                mObject = objectp;
                 mFirst = false;
             }
             else
@@ -1194,7 +1196,9 @@ bool LLMaterialEditor::saveIfNeeded()
     { 
         //make a new inventory item
         std::string res_desc = buildMaterialDescription();
-        createInventoryItem(buffer, mMaterialName, res_desc);
+        LLPermissions local_permissions;
+        local_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null);
+        createInventoryItem(buffer, mMaterialName, res_desc, local_permissions);
 
         // We do not update floater with uploaded asset yet, so just close it.
         closeFloater();
@@ -1288,7 +1292,7 @@ bool LLMaterialEditor::updateInventoryItem(const std::string &buffer, const LLUU
     return true;
 }
 
-void LLMaterialEditor::createInventoryItem(const std::string &buffer, const std::string &name, const std::string &desc)
+void LLMaterialEditor::createInventoryItem(const std::string &buffer, const std::string &name, const std::string &desc, const LLPermissions& owner_permissions)
 {
     // gen a new uuid for this asset
     LLTransactionID tid;
@@ -1299,7 +1303,7 @@ void LLMaterialEditor::createInventoryItem(const std::string &buffer, const std:
 
     create_inventory_item(gAgent.getID(), gAgent.getSessionID(), parent, tid, name, desc,
         LLAssetType::AT_MATERIAL, LLInventoryType::IT_MATERIAL, subtype, next_owner_perm,
-        new LLBoostFuncInventoryCallback([output = buffer](LLUUID const& inv_item_id)
+        new LLBoostFuncInventoryCallback([output = buffer, owner_permissions](LLUUID const& inv_item_id)
     {
         LLViewerInventoryItem* item = gInventory.getItem(inv_item_id);
         if (item)
@@ -1309,8 +1313,15 @@ void LLMaterialEditor::createInventoryItem(const std::string &buffer, const std:
             if (perm.getMaskEveryone() != LLFloaterPerms::getEveryonePerms("Materials")
                 || perm.getMaskGroup() != LLFloaterPerms::getGroupPerms("Materials"))
             {
-                perm.setMaskEveryone(LLFloaterPerms::getEveryonePerms("Materials"));
-                perm.setMaskGroup(LLFloaterPerms::getGroupPerms("Materials"));
+                LLPermissions floater_perm;
+                floater_perm.set(perm);
+                floater_perm.setMaskEveryone(LLFloaterPerms::getEveryonePerms("Materials"));
+                floater_perm.setMaskGroup(LLFloaterPerms::getGroupPerms("Materials"));
+                perm.accumulate(floater_perm);
+                perm.accumulate(owner_permissions);
+                // TODO: Decide if these are needed
+                perm.setCreator(owner_permissions.getCreator());
+                perm.setLastOwner(owner_permissions.getLastOwner());
 
                 item->setPermissions(perm);
 
@@ -1794,55 +1805,112 @@ void LLMaterialEditor::loadLive()
     }
 }
 
-void LLMaterialEditor::saveObjectsMaterialAs()
+// *NOTE: permissions_out ignores user preferences for new item creation
+// (LLFloaterPerms).  Those are applied later on in
+// LLMaterialEditor::createInventoryItem.
+bool can_save_objects_material(LLSelectedTEGetMatData& func, LLPermissions& permissions_out)
 {
-    LLSelectedTEGetMatData func(false);
     LLSelectMgr::getInstance()->getSelection()->applyToTEs(&func, true /*first applicable*/);
-    saveMaterialAs(func.mMaterial, func.mLocalMaterial);
-}
-void LLMaterialEditor::savePickedMaterialAs()
-{
-    LLPickInfo pick = LLToolPie::getInstance()->getPick();
-    if (pick.mPickType != LLPickInfo::PICK_OBJECT || !pick.getObject())
+    LLViewerObject* selected_object = func.mObject;
+    llassert(selected_object); // Note the function name
+    const LLViewerInventoryItem* item = selected_object->getInventoryItemByAsset(func.mMaterialId);
+
+    LLPermissions item_permissions;
+    const bool previous_item_owned = item && item->getPermissions().getLastOwner().notNull();
+    if (item)
     {
-        return;
+        item_permissions.set(item->getPermissions());
+        if (!gAgent.allowOperation(PERM_MODIFY, item_permissions, GP_OBJECT_MANIPULATE))
+        {
+            return false;
+        }
+        if (!gAgent.allowOperation(PERM_COPY, item_permissions, GP_OBJECT_MANIPULATE))
+        {
+            return false;
+        }
+        if (!item_permissions.setOwnerAndGroup(LLUUID::null, gAgent.getID(), LLUUID::null, true))
+        {
+            llassert(false);
+            return false;
+        }
     }
-
-    LLPointer<LLGLTFMaterial> render_material;
-    LLPointer<LLLocalGLTFMaterial> local_material;
-
-    LLViewerObject *objectp = pick.getObject();
-    LLUUID mat_id = objectp->getRenderMaterialID(pick.mObjectFace);
-    if (mat_id.notNull() && objectp->permCopy())
+    else
     {
-        // Try a face user picked first
-        // (likely the only method we need, but in such case
-        // enable_object_save_gltf_material will need to check this)
-        LLTextureEntry *tep = objectp->getTE(pick.mObjectFace);
-        LLGLTFMaterial *mat = tep->getGLTFMaterial();
-        LLLocalGLTFMaterial *local_mat = dynamic_cast<LLLocalGLTFMaterial*>(mat);
+        item_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null);
+    }
 
-        if (local_mat)
+    LLPermissions* object_permissions_p = LLSelectMgr::getInstance()->findObjectPermissions(selected_object);
+    LLPermissions object_permissions;
+    const bool previous_object_owned = object_permissions_p && object_permissions_p->getLastOwner().notNull();
+    if (object_permissions_p)
+    {
+        object_permissions.set(*object_permissions_p);
+        if (!gAgent.allowOperation(PERM_MODIFY, object_permissions, GP_OBJECT_MANIPULATE))
         {
-            local_material = local_mat;
+            return false;
+        }
+        if (!gAgent.allowOperation(PERM_COPY, object_permissions, GP_OBJECT_MANIPULATE))
+        {
+            return false;
+        }
+        if (!object_permissions.setOwnerAndGroup(LLUUID::null, gAgent.getID(), LLUUID::null, true))
+        {
+            llassert(false);
+            return false;
         }
-        render_material = tep->getGLTFRenderMaterial();
     }
     else
     {
-        // Find an applicable material.
-        // Do this before showing message, because
-        // message is going to drop selection.
-        LLSelectedTEGetMatData func(false);
-        LLSelectMgr::getInstance()->getSelection()->applyToTEs(&func, true /*first applicable*/);
-        local_material = func.mLocalMaterial;
-        render_material = func.mMaterial;
+        object_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null);
     }
 
-    saveMaterialAs(render_material, local_material);
+    permissions_out.set(item_permissions);
+    permissions_out.accumulate(object_permissions);
+
+    if (previous_item_owned != previous_object_owned)
+    {
+        // Likely ownership history conflict. Prefer the item history. Fall
+        // back to object history if no associated item.
+        if (previous_item_owned)
+        {
+            permissions_out.setCreator(item_permissions.getCreator());
+            permissions_out.setLastOwner(item_permissions.getLastOwner());
+        }
+        else if (previous_object_owned)
+        {
+            permissions_out.setCreator(object_permissions.getCreator());
+            permissions_out.setLastOwner(object_permissions.getLastOwner());
+        }
+    }
+
+    llassert(permissions_out.getOwner() == gAgent.getID());
+    llassert(permissions_out.getCreator().notNull());
+    llassert(permissions_out.getGroup().isNull());
+
+    return true;
+}
+
+bool LLMaterialEditor::canSaveObjectsMaterial()
+{
+    LLSelectedTEGetMatData func(false);
+    LLPermissions permissions;
+    return can_save_objects_material(func, permissions);
 }
 
-void LLMaterialEditor::saveMaterialAs(const LLGLTFMaterial* render_material, const LLLocalGLTFMaterial *local_material)
+void LLMaterialEditor::saveObjectsMaterialAs()
+{
+    LLSelectedTEGetMatData func(false);
+    LLPermissions permissions;
+    bool allowed = can_save_objects_material(func, permissions);
+    if (!allowed)
+    {
+        LL_WARNS("MaterialEditor") << "Failed to save GLTF material from object" << LL_ENDL;
+        return;
+    }
+    saveMaterialAs(func.mMaterial, func.mLocalMaterial, permissions);
+}
+
+void LLMaterialEditor::saveMaterialAs(const LLGLTFMaterial* render_material, const LLLocalGLTFMaterial *local_material, const LLPermissions& permissions)
 {
     if (local_material)
     {
@@ -1918,10 +1986,20 @@ void LLMaterialEditor::saveMaterialAs(const LLGLTFMaterial* render_material, con
     LLSD args;
     args["DESC"] = LLTrans::getString("New Material");
 
-    LLNotificationsUtil::add("SaveMaterialAs", args, payload, boost::bind(&LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback, _1, _2));
+    // At this point, last owner, etc should be set. LLFloaterPerms will be honored later on
+    if (local_material)
+    {
+        LLPermissions local_permissions;
+        local_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null);
+        LLNotificationsUtil::add("SaveMaterialAs", args, payload, boost::bind(&LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback, _1, _2, local_permissions));
+    }
+    else
+    {
+        LLNotificationsUtil::add("SaveMaterialAs", args, payload, boost::bind(&LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback, _1, _2, permissions));
+    }
 }
 
-void LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback(const LLSD& notification, const LLSD& response)
+void LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback(const LLSD& notification, const LLSD& response, const LLPermissions& permissions)
 {
     S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
     if (0 == option)
@@ -1936,7 +2014,7 @@ void LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback(const LLSD& notificati
         LLSDSerialize::serialize(asset, str, LLSDSerialize::LLSD_BINARY);
 
         std::string new_name = response["message"].asString();
-        createInventoryItem(str.str(), new_name, std::string());
+        createInventoryItem(str.str(), new_name, std::string(), permissions);
     }
 }
 
@@ -2710,7 +2788,7 @@ bool LLMaterialEditor::setFromSelection()
     if (func.mMaterial.notNull())
     {
         setFromGLTFMaterial(func.mMaterial);
-        LLViewerObject* selected_object = selected_objects->getFirstSelectedObject(NULL);
+        LLViewerObject* selected_object = func.mObject;
         const LLViewerInventoryItem* item = selected_object->getInventoryItemByAsset(func.mMaterialId);
         const bool allow_modify = !item || canModify(selected_object, item);
         setEnableEditing(allow_modify);
diff --git a/indra/newview/llmaterialeditor.h b/indra/newview/llmaterialeditor.h
index 6f674a41708..5ee42f65f4b 100644
--- a/indra/newview/llmaterialeditor.h
+++ b/indra/newview/llmaterialeditor.h
@@ -112,9 +112,9 @@ class LLMaterialEditor : public LLPreview, public LLVOInventoryListener
     static void updateLive(const LLUUID &object_id, S32 te);
     static void loadLive();
 
+    static bool canSaveObjectsMaterial();
     static void saveObjectsMaterialAs();
-    static void savePickedMaterialAs();
-    static void onSaveObjectsMaterialAsMsgCallback(const LLSD& notification, const LLSD& response);
+    static void onSaveObjectsMaterialAsMsgCallback(const LLSD& notification, const LLSD& response, const LLPermissions& permissions);
 
     static void onLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status);
 
@@ -229,10 +229,10 @@ class LLMaterialEditor : public LLPreview, public LLVOInventoryListener
     static bool capabilitiesAvailable();
 
 private:
-    static void saveMaterialAs(const LLGLTFMaterial *render_material, const LLLocalGLTFMaterial *local_material);
+    static void saveMaterialAs(const LLGLTFMaterial *render_material, const LLLocalGLTFMaterial *local_material, const LLPermissions& permissions);
 
     static bool updateInventoryItem(const std::string &buffer, const LLUUID &item_id, const LLUUID &task_id);
-    static void createInventoryItem(const std::string &buffer, const std::string &name, const std::string &desc);
+    static void createInventoryItem(const std::string &buffer, const std::string &name, const std::string &desc, const LLPermissions& owner_permissions);
 
     void setFromGLTFMaterial(LLGLTFMaterial* mat);
     bool setFromSelection();
diff --git a/indra/newview/llpanelface.cpp b/indra/newview/llpanelface.cpp
index e91b14a453b..b9daf19284f 100644
--- a/indra/newview/llpanelface.cpp
+++ b/indra/newview/llpanelface.cpp
@@ -1856,7 +1856,7 @@ void LLPanelFace::updateUIGLTF(LLViewerObject* objectp, bool& has_pbr_material,
 
     getChildView("pbr_from_inventory")->setEnabled(editable && has_pbr_capabilities);
     getChildView("edit_selected_pbr")->setEnabled(editable && has_pbr_material && !has_faces_without_pbr && has_pbr_capabilities);
-    getChildView("save_selected_pbr")->setEnabled(objectp->permCopy() && has_pbr_material && identical_pbr && has_pbr_capabilities);
+    getChildView("save_selected_pbr")->setEnabled(LLMaterialEditor::canSaveObjectsMaterial() && identical_pbr);
 
     const bool show_pbr = mComboMatMedia->getCurrentIndex() == MATMEDIA_PBR && mComboMatMedia->getEnabled();
     if (show_pbr)
-- 
GitLab