Merge pull request #21217 from colemanw/searchKitButtonTokens
[civicrm-core.git] / CRM / Profile / Form / Edit.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17
18 /**
19 * This class generates form components for custom data
20 *
21 * It delegates the work to lower level subclasses and integrates the changes
22 * back in. It also uses a lot of functionality with the CRM API's, so any change
23 * made here could potentially affect the API etc. Be careful, be aware, use unit tests.
24 *
25 */
26 class CRM_Profile_Form_Edit extends CRM_Profile_Form {
27 protected $_postURL = NULL;
28 protected $_cancelURL = NULL;
29 protected $_errorURL = NULL;
30 protected $_context;
31 protected $_blockNo;
32 protected $_prefix;
33 protected $returnExtra;
34
35 /**
36 * Pre processing work done here.
37 *
38 * @param
39 *
40 */
41 public function preProcess() {
42 $this->_mode = CRM_Profile_Form::MODE_CREATE;
43
44 $this->_onPopupClose = CRM_Utils_Request::retrieve('onPopupClose', 'String', $this);
45 $this->assign('onPopupClose', $this->_onPopupClose);
46
47 //set the context for the profile
48 $this->_context = CRM_Utils_Request::retrieve('context', 'Alphanumeric', $this);
49
50 //set the block no
51 $this->_blockNo = CRM_Utils_Request::retrieve('blockNo', 'String', $this);
52
53 //set the prefix
54 $this->_prefix = CRM_Utils_Request::retrieve('prefix', 'String', $this);
55
56 // Fields for the EntityRef widget
57 $this->returnExtra = CRM_Utils_Request::retrieve('returnExtra', 'String', $this);
58
59 $this->assign('context', $this->_context);
60
61 if ($this->_blockNo) {
62 $this->assign('blockNo', $this->_blockNo);
63 $this->assign('prefix', $this->_prefix);
64 }
65
66 $this->assign('createCallback', CRM_Utils_Request::retrieve('createCallback', 'String', $this));
67
68 if ($this->get('skipPermission')) {
69 $this->_skipPermission = TRUE;
70 }
71
72 if ($this->get('edit')) {
73 // make sure we have right permission to edit this user
74 $userID = CRM_Core_Session::getLoggedInContactID();
75
76 // Set the ID from the query string, otherwise default to the current user
77 $id = CRM_Utils_Request::retrieve('id', 'Positive', $this, FALSE, $userID);
78
79 if ($id) {
80 // this is edit mode.
81 $this->_mode = CRM_Profile_Form::MODE_EDIT;
82
83 if ($id != $userID) {
84 // do not allow edit for anon users in joomla frontend, CRM-4668, unless u have checksum CRM-5228
85 // see also CRM-19079 for modifications to the condition
86 $config = CRM_Core_Config::singleton();
87 if ($config->userFrameworkFrontend && $config->userSystem->is_joomla) {
88 CRM_Contact_BAO_Contact_Permission::validateOnlyChecksum($id, $this);
89 }
90 else {
91 CRM_Contact_BAO_Contact_Permission::validateChecksumContact($id, $this);
92 }
93 $this->_isPermissionedChecksum = TRUE;
94 }
95 }
96
97 // CRM-16784: If there is no ID then this can't be an 'edit'
98 else {
99 CRM_Core_Error::statusBounce(ts('No user/contact ID was specified, so the Profile cannot be used in edit mode.'));
100 }
101
102 }
103
104 parent::preProcess();
105
106 // and also the profile is of type 'Profile'
107 $query = "
108 SELECT module,is_reserved
109 FROM civicrm_uf_group
110 LEFT JOIN civicrm_uf_join ON uf_group_id = civicrm_uf_group.id
111 WHERE civicrm_uf_group.id = %1
112 ";
113
114 $params = [1 => [$this->_gid, 'Integer']];
115 $dao = CRM_Core_DAO::executeQuery($query, $params);
116
117 $isProfile = FALSE;
118 while ($dao->fetch()) {
119 $isProfile = ($isProfile || ($dao->module == "Profile"));
120 }
121
122 //Check that the user has the "add contacts" Permission
123 $canAdd = CRM_Core_Permission::check("add contacts");
124
125 //Remove need for Profile module type when using reserved profiles [CRM-14488]
126 if (!$dao->N || (!$isProfile && !($dao->is_reserved && $canAdd))) {
127 CRM_Core_Error::statusBounce(ts('The requested Profile (gid=%1) is not configured to be used for \'Profile\' edit and view forms in its Settings. Contact the site administrator if you need assistance.',
128 [1 => $this->_gid]
129 ));
130 }
131 }
132
133 /**
134 * Build the form object.
135 *
136 */
137 public function buildQuickForm() {
138 if (empty($this->_ufGroup['id'])) {
139 CRM_Core_Error::statusBounce(ts('Invalid'));
140 }
141
142 // set the title
143 if ($this->_multiRecord && $this->_customGroupTitle) {
144 $groupTitle = ($this->_multiRecord & CRM_Core_Action::UPDATE) ? 'Edit ' . $this->_customGroupTitle . ' Record' : $this->_customGroupTitle;
145
146 }
147 else {
148 $groupTitle = CRM_Core_BAO_UFGroup::getFrontEndTitle($this->_ufGroup['id']);
149 }
150 CRM_Utils_System::setTitle($groupTitle);
151 $this->assign('recentlyViewed', FALSE);
152
153 if ($this->_context != 'dialog') {
154 $this->_postURL = $this->_ufGroup['post_URL'];
155 $this->_cancelURL = $this->_ufGroup['cancel_URL'];
156
157 $gidString = $this->_gid;
158 if (!empty($this->_profileIds)) {
159 $gidString = implode(',', $this->_profileIds);
160 }
161
162 if (!$this->_postURL) {
163 if ($this->_context == 'Search') {
164 $this->_postURL = CRM_Utils_System::url('civicrm/contact/search');
165 }
166 elseif ($this->_id && $this->_gid) {
167 $urlParams = "reset=1&id={$this->_id}&gid={$gidString}";
168 if ($this->_isContactActivityProfile && $this->_activityId) {
169 $urlParams .= "&aid={$this->_activityId}";
170 }
171 // get checksum if present
172 if ($this->get('cs')) {
173 $urlParams .= "&cs=" . $this->get('cs');
174 }
175 $this->_postURL = CRM_Utils_System::url('civicrm/profile/view', $urlParams);
176 }
177 }
178
179 if (!$this->_cancelURL) {
180 $this->_cancelURL = CRM_Utils_System::url('civicrm/profile',
181 "reset=1&gid={$gidString}"
182 );
183 }
184
185 // we do this gross hack since qf also does entity replacement
186 $this->_postURL = str_replace('&amp;', '&', $this->_postURL);
187 $this->_cancelURL = str_replace('&amp;', '&', $this->_cancelURL);
188
189 // also retain error URL if set
190 $this->_errorURL = $_POST['errorURL'] ?? NULL;
191 if ($this->_errorURL) {
192 // we do this gross hack since qf also does entity replacement
193 $this->_errorURL = str_replace('&amp;', '&', $this->_errorURL);
194 $this->addElement('hidden', 'errorURL', $this->_errorURL);
195 }
196
197 // replace the session stack in case user cancels (and we dont go into postProcess)
198 $session = CRM_Core_Session::singleton();
199 $session->replaceUserContext($this->_postURL);
200 }
201
202 parent::buildQuickForm();
203
204 $this->assign('cancelURL', $this->_cancelURL);
205
206 $cancelButtonValue = !empty($this->_ufGroup['cancel_button_text']) ? $this->_ufGroup['cancel_button_text'] : ts('Cancel');
207 $this->assign('cancelButtonText', $cancelButtonValue);
208 $this->assign('includeCancelButton', CRM_Utils_Array::value('add_cancel_button', $this->_ufGroup));
209
210 if (($this->_multiRecord & CRM_Core_Action::DELETE) && $this->_recordExists) {
211 $this->_deleteButtonName = $this->getButtonName('upload', 'delete');
212 $this->addElement('xbutton', $this->_deleteButtonName, ts('Delete'), [
213 'type' => 'submit',
214 'value' => 1,
215 'class' => 'crm-button',
216 ]);
217
218 return;
219 }
220
221 //get the value from session, this is set if there is any file
222 //upload field
223 $uploadNames = $this->get('uploadNames');
224
225 if (!empty($uploadNames)) {
226 $buttonName = 'upload';
227 }
228 else {
229 $buttonName = 'next';
230 }
231
232 $buttons[] = [
233 'type' => $buttonName,
234 'name' => !empty($this->_ufGroup['submit_button_text']) ? $this->_ufGroup['submit_button_text'] : ts('Save'),
235 'isDefault' => TRUE,
236 ];
237
238 $this->addButtons($buttons);
239
240 $this->addFormRule(['CRM_Profile_Form', 'formRule'], $this);
241 }
242
243 /**
244 * Process the user submitted custom data values.
245 *
246 */
247 public function postProcess() {
248 parent::postProcess();
249
250 // Send back data for the EntityRef widget
251 if ($this->returnExtra) {
252 $contact = civicrm_api3('Contact', 'getsingle', [
253 'id' => $this->_id,
254 'return' => $this->returnExtra,
255 ]);
256 foreach (explode(',', $this->returnExtra) as $field) {
257 $field = trim($field);
258 $this->ajaxResponse['extra'][$field] = $contact[$field] ?? NULL;
259 }
260 }
261
262 // When saving (not deleting) and not in an ajax popup
263 if (empty($_POST[$this->_deleteButtonName]) && $this->_context != 'dialog') {
264 CRM_Core_Session::setStatus(ts('Your information has been saved.'), ts('Thank you.'), 'success');
265 }
266
267 $session = CRM_Core_Session::singleton();
268 // only replace user context if we do not have a postURL
269 if (!$this->_postURL) {
270 $gidString = $this->_gid;
271 if (!empty($this->_profileIds)) {
272 $gidString = implode(',', $this->_profileIds);
273 }
274
275 $urlParams = "reset=1&id={$this->_id}&gid={$gidString}";
276 if ($this->_isContactActivityProfile && $this->_activityId) {
277 $urlParams .= "&aid={$this->_activityId}";
278 }
279 // Get checksum if present
280 if ($this->get('cs')) {
281 $urlParams .= "&cs=" . $this->get('cs');
282 }
283 // Generate one if needed
284 elseif (!CRM_Contact_BAO_Contact_Permission::allow($this->_id)) {
285 $urlParams .= "&cs=" . CRM_Contact_BAO_Contact_Utils::generateChecksum($this->_id);
286 }
287 $url = CRM_Utils_System::url('civicrm/profile/view', $urlParams);
288 }
289 else {
290 // Replace tokens from post URL
291 $contactParams = [
292 'contact_id' => $this->_id,
293 'version' => 3,
294 ];
295
296 $contact = civicrm_api('contact', 'get', $contactParams);
297 $contact = reset($contact['values']);
298
299 $dummyMail = new CRM_Mailing_BAO_Mailing();
300 $dummyMail->body_text = $this->_postURL;
301 $tokens = $dummyMail->getTokens();
302
303 $url = CRM_Utils_Token::replaceContactTokens($this->_postURL, $contact, FALSE, CRM_Utils_Array::value('text', $tokens));
304 }
305
306 $session->replaceUserContext($url);
307 }
308
309 /**
310 * Intercept QF validation and do our own redirection.
311 *
312 * We use this to send control back to the user for a user formatted page
313 * This allows the user to maintain the same state and display the error messages
314 * in their own theme along with any modifications
315 *
316 * This is a first version and will be tweaked over a period of time
317 *
318 *
319 * @return bool
320 * true if no error found
321 */
322 public function validate() {
323 $errors = parent::validate();
324
325 if (!$errors && !empty($_POST['errorURL'])) {
326 $message = NULL;
327 foreach ($this->_errors as $name => $mess) {
328 $message .= $mess;
329 $message .= '<p>';
330 }
331
332 CRM_Utils_System::setUFMessage($message);
333
334 $message = urlencode($message);
335
336 $errorURL = $_POST['errorURL'];
337 if (strpos($errorURL, '?') !== FALSE) {
338 $errorURL .= '&';
339 }
340 else {
341 $errorURL .= '?';
342 }
343 $errorURL .= "gid={$this->_gid}&msg=$message";
344 CRM_Utils_System::redirect($errorURL);
345 }
346
347 return $errors;
348 }
349
350 }