@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3/**
4 * Before a profile picture is destroyed, restore the builtin picture.
5 */
6final class PeopleProfilePictureBeforeDestructionEngineExtension
7 extends PhabricatorBeforeDestructionEngineExtension {
8
9 const EXTENSIONKEY = 'people-profiles';
10
11 public function getExtensionName(): string {
12 return pht('People Profile Pictures');
13 }
14
15 public function canBeforeDestroyObject(
16 PhabricatorDestructionEngine $destruction_engine,
17 $object): bool {
18 return ($object instanceof PhabricatorFile)
19 && $object->getIsProfileImage();
20 }
21
22 public function beforeDestroyObject(
23 PhabricatorDestructionEngine $destruction_engine,
24 $object): void {
25 // File that will be destroyed soon.
26 // The file PHID is always non-empty at this point.
27 $file_phid = $object->getPHID();
28
29 // Note that a file that is used as profile images have
30 // the authorPHID = null, so it's not so obvious which
31 // is the affected user.
32 // https://we.phorge.it/T15407
33
34 // Note that we could find the affected users by running this
35 // very inefficient query that would lead to a full table scan:
36 // SELECT * FROM user WHERE profileImagePHID = $file_phid
37 // In the future it might make sense to add an index on 'profileImagePHID'
38 // if more frontend features will read that info, so we can also avoid the
39 // following lines of code.
40 // https://we.phorge.it/T16080
41
42 // We look at the file attachments to find the affected user efficiently.
43 // Note that file attachments are only available before destroying the file,
44 // and... fortunately we are inside a "Before Destruction" engine.
45 // This query is efficient thanks to the database index on 'filePHID' and
46 // the low cardinality of this result set.
47 $viewer = $destruction_engine->getViewer();
48 $file_attachments_query = new PhabricatorFileAttachmentQuery();
49 $file_attachments =
50 $file_attachments_query
51 ->setViewer($viewer)
52 ->withFilePHIDs(array($file_phid))
53 ->withObjectPHIDType(PhabricatorPeopleUserPHIDType::TYPECONST)
54 ->withAttachmentModes(array(PhabricatorFileAttachment::MODE_ATTACH))
55 ->execute();
56 $attached_objects = mpull($file_attachments, 'getObject');
57
58 // Be 100% sure to only operate on users,
59 // and that these are really using this picture.
60 $affected_users = array();
61 foreach ($attached_objects as $attached_object) {
62 if (($attached_object instanceof PhabricatorUser) &&
63 ($attached_object->getProfileImagePHID() == $file_phid)) {
64 $affected_users[] = $attached_object;
65 }
66 }
67
68 $user_table = new PhabricatorUser();
69
70 if (!$affected_users) {
71 // The above fast speculation has found no users.
72 // It can happen when somebody manually used the "Detach File" button
73 // from the file (why people can generally do that? uhm).
74 // Only in this desperate case, we run this inefficient query.
75 $affected_users = $user_table
76 ->loadAllWhere(
77 'profileImagePHID = %s',
78 $file_phid);
79 }
80
81 // Avoid opening an empty transaction.
82 if (!$affected_users) {
83 return;
84 }
85
86 // Set the builtin profile image to each affected user.
87 // Premising that it's supposed to be just one user.
88 // Maybe in the future multiple users may use the same
89 // profile picture, so let's covers more corner cases,
90 // because we can.
91 $user_table->openTransaction();
92 foreach ($affected_users as $affected_user) {
93 $affected_user->setProfileImagePHID(null);
94 $affected_user->save();
95 }
96 $user_table->saveTransaction();
97 }
98
99}