@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
4final class PhabricatorAuthContactNumber
5 extends PhabricatorAuthDAO
6 implements
7 PhabricatorApplicationTransactionInterface,
8 PhabricatorPolicyInterface,
9 PhabricatorDestructibleInterface,
10 PhabricatorEditEngineMFAInterface {
11
12 protected $objectPHID;
13 protected $contactNumber;
14 protected $uniqueKey;
15 protected $status;
16 protected $isPrimary;
17 protected $properties = array();
18
19 const STATUS_ACTIVE = 'active';
20 const STATUS_DISABLED = 'disabled';
21
22 protected function getConfiguration() {
23 return array(
24 self::CONFIG_SERIALIZATION => array(
25 'properties' => self::SERIALIZATION_JSON,
26 ),
27 self::CONFIG_AUX_PHID => true,
28 self::CONFIG_COLUMN_SCHEMA => array(
29 'contactNumber' => 'text255',
30 'status' => 'text32',
31 'uniqueKey' => 'bytes12?',
32 'isPrimary' => 'bool',
33 ),
34 self::CONFIG_KEY_SCHEMA => array(
35 'key_object' => array(
36 'columns' => array('objectPHID'),
37 ),
38 'key_unique' => array(
39 'columns' => array('uniqueKey'),
40 'unique' => true,
41 ),
42 ),
43 ) + parent::getConfiguration();
44 }
45
46 public static function initializeNewContactNumber($object) {
47 return id(new self())
48 ->setStatus(self::STATUS_ACTIVE)
49 ->setObjectPHID($object->getPHID())
50 ->setIsPrimary(0);
51 }
52
53 public function getPHIDType() {
54 return PhabricatorAuthContactNumberPHIDType::TYPECONST;
55 }
56
57 public function getURI() {
58 return urisprintf('/auth/contact/%s/', $this->getID());
59 }
60
61 public function getObjectName() {
62 return pht('Contact Number %d', $this->getID());
63 }
64
65 public function getDisplayName() {
66 return $this->getContactNumber();
67 }
68
69 public function isDisabled() {
70 return ($this->getStatus() === self::STATUS_DISABLED);
71 }
72
73 public function newIconView() {
74 if ($this->isDisabled()) {
75 return id(new PHUIIconView())
76 ->setIcon('fa-ban', 'grey')
77 ->setTooltip(pht('Disabled'));
78 }
79
80 if ($this->getIsPrimary()) {
81 return id(new PHUIIconView())
82 ->setIcon('fa-certificate', 'blue')
83 ->setTooltip(pht('Primary Number'));
84 }
85
86 return id(new PHUIIconView())
87 ->setIcon('fa-hashtag', 'bluegrey')
88 ->setTooltip(pht('Active Phone Number'));
89 }
90
91 public function newUniqueKey() {
92 $parts = array(
93 // This is future-proofing for a world where we have multiple types
94 // of contact numbers, so we might be able to avoid re-hashing
95 // everything.
96 'phone',
97 $this->getContactNumber(),
98 );
99
100 $parts = implode("\0", $parts);
101
102 return PhabricatorHash::digestForIndex($parts);
103 }
104
105 public function save() {
106 // We require that active contact numbers be unique, but it's okay to
107 // disable a number and then reuse it somewhere else.
108 if ($this->isDisabled()) {
109 $this->uniqueKey = null;
110 } else {
111 $this->uniqueKey = $this->newUniqueKey();
112 }
113
114 parent::save();
115
116 return $this->updatePrimaryContactNumber();
117 }
118
119 private function updatePrimaryContactNumber() {
120 // Update the "isPrimary" column so that at most one number is primary for
121 // each user, and no disabled number is primary.
122
123 $conn = $this->establishConnection('w');
124 $this_id = (int)$this->getID();
125
126 if ($this->getIsPrimary() && !$this->isDisabled()) {
127 // If we're trying to make this number primary and it's active, great:
128 // make this number the primary number.
129 $primary_id = $this_id;
130 } else {
131 // If we aren't trying to make this number primary or it is disabled,
132 // pick another number to make primary if we can. A number must be active
133 // to become primary.
134
135 // If there are multiple active numbers, pick the oldest one currently
136 // marked primary (usually, this should mean that we just keep the
137 // current primary number as primary).
138
139 // If none are marked primary, just pick the oldest one.
140 $primary_row = queryfx_one(
141 $conn,
142 'SELECT id FROM %R
143 WHERE objectPHID = %s AND status = %s
144 ORDER BY isPrimary DESC, id ASC
145 LIMIT 1',
146 $this,
147 $this->getObjectPHID(),
148 self::STATUS_ACTIVE);
149 if ($primary_row) {
150 $primary_id = (int)$primary_row['id'];
151 } else {
152 $primary_id = -1;
153 }
154 }
155
156 // Set the chosen number to primary, and all other numbers to nonprimary.
157
158 queryfx(
159 $conn,
160 'UPDATE %R SET isPrimary = IF(id = %d, 1, 0)
161 WHERE objectPHID = %s',
162 $this,
163 $primary_id,
164 $this->getObjectPHID());
165
166 $this->setIsPrimary((int)($primary_id === $this_id));
167
168 return $this;
169 }
170
171 public static function getStatusNameMap() {
172 return ipull(self::getStatusPropertyMap(), 'name');
173 }
174
175 private static function getStatusPropertyMap() {
176 return array(
177 self::STATUS_ACTIVE => array(
178 'name' => pht('Active'),
179 ),
180 self::STATUS_DISABLED => array(
181 'name' => pht('Disabled'),
182 ),
183 );
184 }
185
186 public function getSortVector() {
187 // Sort the primary number first, then active numbers, then disabled
188 // numbers. In each group, sort from oldest to newest.
189 return id(new PhutilSortVector())
190 ->addInt($this->getIsPrimary() ? 0 : 1)
191 ->addInt($this->isDisabled() ? 1 : 0)
192 ->addInt($this->getID());
193 }
194
195
196/* -( PhabricatorPolicyInterface )----------------------------------------- */
197
198
199 public function getCapabilities() {
200 return array(
201 PhabricatorPolicyCapability::CAN_VIEW,
202 PhabricatorPolicyCapability::CAN_EDIT,
203 );
204 }
205
206 public function getPolicy($capability) {
207 return $this->getObjectPHID();
208 }
209
210 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
211 return false;
212 }
213
214
215/* -( PhabricatorDestructibleInterface )----------------------------------- */
216
217
218 public function destroyObjectPermanently(
219 PhabricatorDestructionEngine $engine) {
220 $this->delete();
221 }
222
223
224/* -( PhabricatorApplicationTransactionInterface )------------------------- */
225
226
227 public function getApplicationTransactionEditor() {
228 return new PhabricatorAuthContactNumberEditor();
229 }
230
231 public function getApplicationTransactionTemplate() {
232 return new PhabricatorAuthContactNumberTransaction();
233 }
234
235
236/* -( PhabricatorEditEngineMFAInterface )---------------------------------- */
237
238
239 public function newEditEngineMFAEngine() {
240 return new PhabricatorAuthContactNumberMFAEngine();
241 }
242
243}