| @@ -1,17 +1,19 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | |
| 3 | 3 | namespace AcyMailing\Controllers; |
| 4 | 4 | |
| 5 | +use AcyMailing\Classes\ListClass; | |
| 6 | +use AcyMailing\Classes\MailClass; | |
| 5 | 7 | use AcyMailing\Classes\UserClass; |
| 6 | 8 | use AcyMailing\Core\AcymController; |
| 7 | 9 | |
| 8 | 10 | class ToggleController extends AcymController |
| 9 | 11 | { |
| 10 | - var $toggleableColumns = []; | |
| 11 | - var $icons = []; | |
| 12 | - var $tooltips = []; | |
| 13 | - var $deletableRows = []; | |
| 12 | + protected array $toggleableColumns = []; | |
| 13 | + protected array $icons = []; | |
| 14 | + protected array $tooltips = []; | |
| 15 | + private array $deletableRows = []; | |
| 14 | 16 | |
| 15 | 17 | public function __construct() |
| 16 | 18 | { |
| 17 | 19 | parent::__construct(); |
| @@ -19,13 +21,15 @@ | ||
| 19 | 21 | $this->defaulttask = 'toggle'; |
| 20 | 22 | |
| 21 | 23 | $this->defineToggles(); |
| 22 | 24 | |
| 25 | + // Avoid caching issue in Internet Explorer | |
| 23 | 26 | acym_noCache(); |
| 24 | 27 | } |
| 25 | 28 | |
| 26 | - protected function defineToggles() | |
| 29 | + protected function defineToggles(): void | |
| 27 | 30 | { |
| 31 | + // $this->toggleableColumns[TABLE NAME WITHOUT PREFIX] = array(COLUMN NAME => PRIMARY KEY); | |
| 28 | 32 | $this->toggleableColumns['automation'] = ['active' => 'id']; |
| 29 | 33 | $this->toggleableColumns['field'] = [ |
| 30 | 34 | 'active' => 'id', |
| 31 | 35 | 'required' => 'id', |
| @@ -44,8 +48,9 @@ | ||
| 44 | 48 | $this->toggleableColumns['mail_override'] = ['active' => 'id']; |
| 45 | 49 | $this->toggleableColumns['mailbox_action'] = ['active' => 'id']; |
| 46 | 50 | $this->toggleableColumns['scenario'] = ['active' => 'id']; |
| 47 | 51 | |
| 52 | + // $this->icons[TABLE NAME WITHOUT PREFIX][COLUMN NAME][VALUE] = ICON CLASS; | |
| 48 | 53 | $this->icons['automation']['active'][1] = 'acymicon-check-circle acym__color__green'; |
| 49 | 54 | $this->icons['automation']['active'][0] = 'acymicon-times-circle acym__color__red'; |
| 50 | 55 | $this->icons['field']['active'][1] = 'acymicon-check-circle acym__color__green'; |
| 51 | 56 | $this->icons['field']['active'][0] = 'acymicon-times-circle acym__color__red'; |
| @@ -96,24 +101,32 @@ | ||
| 96 | 101 | $this->tooltips['campaign']['visible'][1] = 'ACYM_VISIBLE'; |
| 97 | 102 | $this->tooltips['mail_override']['active'][0] = 'ACYM_INACTIVE'; |
| 98 | 103 | $this->tooltips['mail_override']['active'][1] = 'ACYM_ACTIVE'; |
| 99 | 104 | |
| 105 | + // $this->deletableRows[] = TABLE NAME WITHOUT PREFIX; | |
| 100 | 106 | $this->deletableRows[] = 'mail'; |
| 101 | 107 | $this->deletableRows[] = 'queue'; |
| 102 | 108 | } |
| 103 | 109 | |
| 104 | - public function toggle() | |
| 110 | + public function toggle(): void | |
| 105 | 111 | { |
| 106 | - acym_checkToken(); | |
| 112 | + wp_verify_nonce(acym_getVar('cmd', '_wpnonce'), 'acymnonce') || die('Invalid Token'); | |
| 107 | 113 | |
| 108 | 114 | $table = acym_getVar('word', 'table', ''); |
| 109 | 115 | $field = acym_getVar('cmd', 'field', ''); |
| 110 | 116 | $id = acym_getVar('int', 'id', 0); |
| 111 | 117 | $newValue = acym_getVar('int', 'value', 0); |
| 112 | - if (!empty($newValue)) $newValue = 1; | |
| 118 | + if (!empty($newValue)) { | |
| 119 | + $newValue = 1; | |
| 120 | + } | |
| 113 | 121 | |
| 114 | - if (empty($table) || empty($field) || empty($id) || empty($this->toggleableColumns[$table][$field])) exit; | |
| 122 | + if (empty($table) || empty($field) || empty($id) || empty($this->toggleableColumns[$table][$field])) { | |
| 123 | + exit; | |
| 124 | + } | |
| 115 | 125 | |
| 126 | + // Security (ACL): per-table check — the 'toggle' exception opens this controller to ~10 roles regardless of table. | |
| 127 | + $this->checkToggleAcl($table); | |
| 128 | + | |
| 116 | 129 | $preciseMethod = $table.ucfirst($field); |
| 117 | 130 | $globalMethod = $table.'Global'; |
| 118 | 131 | if (method_exists($this, $preciseMethod)) { |
| 119 | 132 | $this->$preciseMethod($id, $table, $field, $newValue); |
| @@ -124,8 +137,9 @@ | ||
| 124 | 137 | } |
| 125 | 138 | |
| 126 | 139 | acym_trigger('onAcymToggle'.ucfirst($table).ucfirst($field), [&$id, &$newValue]); |
| 127 | 140 | |
| 141 | + // Return the replacement icon | |
| 128 | 142 | if (empty($this->icons[$table][$field][$newValue])) { |
| 129 | 143 | acym_sendAjaxResponse(acym_translation('ACYM_UNAUTHORIZED_ACCESS'), [], false); |
| 130 | 144 | } |
| 131 | 145 | |
| @@ -139,38 +153,83 @@ | ||
| 139 | 153 | |
| 140 | 154 | acym_sendAjaxResponse('', $data); |
| 141 | 155 | } |
| 142 | 156 | |
| 143 | - protected function doToggle($id, $table, $field, $newValue) | |
| 157 | + // Each toggle icon is only rendered on the page whose ACL the user already holds, so this per-table check blocks only forged cross-feature requests (the 'toggle' exception opens the controller to ~10 roles); overridden as a no-op by FronttoggleController. | |
| 158 | + protected function checkToggleAcl(string $table): void | |
| 144 | 159 | { |
| 160 | + $tableAcl = [ | |
| 161 | + 'user' => 'users', | |
| 162 | + 'list' => 'lists', | |
| 163 | + 'campaign' => 'campaigns', | |
| 164 | + 'followup' => 'campaigns', | |
| 165 | + 'automation' => 'automation', | |
| 166 | + 'segment' => 'segments', | |
| 167 | + 'scenario' => 'scenarios', | |
| 168 | + 'form' => 'forms', | |
| 169 | + 'field' => 'fields', | |
| 170 | + 'mailbox_action' => 'bounces', | |
| 171 | + 'rule' => 'bounces', | |
| 172 | + 'mail_override' => 'override', | |
| 173 | + ]; | |
| 174 | + | |
| 175 | + if (empty($tableAcl[$table]) || !acym_isAllowed($tableAcl[$table])) { | |
| 176 | + acym_sendAjaxResponse(acym_translation('ACYM_ACCESS_DENIED'), [], false); | |
| 177 | + } | |
| 178 | + } | |
| 179 | + | |
| 180 | + protected function userActive(int $id, string $table, string $field, int $newValue): void | |
| 181 | + { | |
| 182 | + $userClass = new UserClass(); | |
| 183 | + if ($newValue === 0) { | |
| 184 | + $userClass->deactivate([$id]); | |
| 185 | + } else { | |
| 186 | + $userClass->reactivate([$id]); | |
| 187 | + } | |
| 188 | + } | |
| 189 | + | |
| 190 | + protected function doToggle(int $id, string $table, string $field, int $newValue): void | |
| 191 | + { | |
| 145 | 192 | $updateQuery = 'UPDATE '.acym_secureDBColumn(ACYM_DBPREFIX.$table); |
| 146 | - $updateQuery .= ' SET `'.acym_secureDBColumn($field).'` = '.intval($newValue); | |
| 147 | - $updateQuery .= ' WHERE `'.acym_secureDBColumn($this->toggleableColumns[$table][$field]).'` = '.intval($id); | |
| 193 | + $updateQuery .= ' SET `'.acym_secureDBColumn($field).'` = '.$newValue; | |
| 194 | + $updateQuery .= ' WHERE `'.acym_secureDBColumn($this->toggleableColumns[$table][$field]).'` = '.$id; | |
| 148 | 195 | $updateQuery .= ' LIMIT 1'; |
| 149 | 196 | acym_query($updateQuery); |
| 150 | 197 | } |
| 151 | 198 | |
| 152 | - public function delete() | |
| 199 | + public function delete(): void | |
| 153 | 200 | { |
| 154 | 201 | if (!acym_isAdmin()) exit; |
| 155 | - acym_checkToken(); | |
| 202 | + wp_verify_nonce(acym_getVar('cmd', '_wpnonce'), 'acymnonce') || die('Invalid Token'); | |
| 156 | 203 | |
| 157 | 204 | $table = acym_getVar('word', 'table', ''); |
| 158 | - $id = acym_getVar('cmd', 'id', 0); | |
| 159 | - $method = acym_getVar('word', 'method', 'delete'); | |
| 205 | + $id = acym_getVar('int', 'id', 0); | |
| 160 | 206 | |
| 161 | 207 | if (empty($table) || !in_array($table, $this->deletableRows) || empty($id)) { |
| 162 | 208 | exit; |
| 163 | 209 | } |
| 164 | 210 | |
| 211 | + if ($table === 'queue') { | |
| 212 | + $allowed = acym_isAllowed('queue'); | |
| 213 | + } else { | |
| 214 | + $mailClass = new MailClass(); | |
| 215 | + $allowed = $mailClass->hasUserAccess($id, true); | |
| 216 | + } | |
| 217 | + | |
| 218 | + if (!$allowed) { | |
| 219 | + acym_sendAjaxResponse(acym_translation('ACYM_ACCESS_DENIED'), [], false); | |
| 220 | + } | |
| 221 | + | |
| 222 | + $method = $table === 'queue' ? 'deleteQueuedByUserIds' : 'delete'; | |
| 223 | + | |
| 165 | 224 | $namespaceClass = 'AcyMailing\\Classes\\'.ucfirst($table).'Class'; |
| 166 | - $elementClass = new $namespaceClass; | |
| 167 | - $elementClass->$method($id); | |
| 225 | + $elementClass = new $namespaceClass(); | |
| 226 | + $elementClass->$method([$id]); | |
| 168 | 227 | |
| 169 | 228 | exit; |
| 170 | 229 | } |
| 171 | 230 | |
| 172 | - public function setDoNotRemindMe() | |
| 231 | + public function setDoNotRemindMe(): void | |
| 173 | 232 | { |
| 174 | 233 | $newValue = acym_getVar('string', 'value'); |
| 175 | 234 | |
| 176 | 235 | if (empty($newValue)) { |
| @@ -176,14 +235,14 @@ | ||
| 176 | 235 | if (empty($newValue)) { |
| 177 | 236 | acym_sendAjaxResponse(acym_translation('ACYM_ERROR_SAVING'), [], false); |
| 178 | 237 | } |
| 179 | 238 | |
| 180 | - $newConfig = new \stdClass(); | |
| 181 | - $newConfig->remindme = json_decode($this->config->get('remindme', '[]')); | |
| 182 | - if (!in_array($newValue, $newConfig->remindme)) array_push($newConfig->remindme, $newValue); | |
| 183 | - $newConfig->remindme = json_encode($newConfig->remindme); | |
| 239 | + $remindMe = json_decode($this->config->get('remindme', '[]')); | |
| 240 | + if (!in_array($newValue, $remindMe)) { | |
| 241 | + $remindMe[] = $newValue; | |
| 242 | + } | |
| 184 | 243 | |
| 185 | - if ($this->config->save($newConfig)) { | |
| 244 | + if ($this->config->saveConfig(['remindme' => json_encode($remindMe)])) { | |
| 186 | 245 | acym_sendAjaxResponse(acym_translation('ACYM_THANKS')); |
| 187 | 246 | } else { |
| 188 | 247 | acym_sendAjaxResponse(acym_translation('ACYM_ERROR_SAVING'), [], false); |
| 189 | 248 | } |
| @@ -188,16 +247,26 @@ | ||
| 188 | 247 | acym_sendAjaxResponse(acym_translation('ACYM_ERROR_SAVING'), [], false); |
| 189 | 248 | } |
| 190 | 249 | } |
| 191 | 250 | |
| 192 | - public function subscribeOnClick() | |
| 251 | + public function subscribeOnClick(): void | |
| 193 | 252 | { |
| 194 | - $userId = acym_getVar('int', 'userid'); | |
| 195 | - $listId = acym_getVar('int', 'listid'); | |
| 253 | + wp_verify_nonce(acym_getVar('cmd', '_wpnonce'), 'acymnonce') || die('Invalid Token'); | |
| 254 | + // Security (ACL): subscription management is restricted to the 'users' role (backend); no-op on front (ownership). | |
| 255 | + $this->checkToggleAcl('user'); | |
| 196 | 256 | |
| 257 | + $userIds = acym_getVar('array', 'userid', []); | |
| 258 | + $listIds = acym_getVar('array', 'listid', []); | |
| 259 | + | |
| 197 | 260 | $userClass = new UserClass(); |
| 198 | - $result = $userClass->subscribe($userId, [$listId]); | |
| 261 | + $userClass->onlyManageableUsers($userIds); | |
| 199 | 262 | |
| 263 | + $listClass = new ListClass(); | |
| 264 | + $listClass->onlyManageableLists($listIds); | |
| 265 | + | |
| 266 | + $userClass = new UserClass(); | |
| 267 | + $result = $userClass->subscribe($userIds, $listIds); | |
| 268 | + | |
| 200 | 269 | if ($result) { |
| 201 | 270 | acym_sendAjaxResponse(); |
| 202 | 271 | } else { |
| 203 | 272 | acym_sendAjaxResponse('', [], false); |
| @@ -203,16 +272,26 @@ | ||
| 203 | 272 | acym_sendAjaxResponse('', [], false); |
| 204 | 273 | } |
| 205 | 274 | } |
| 206 | 275 | |
| 207 | - public function unsubscribeOnClick() | |
| 276 | + public function unsubscribeOnClick(): void | |
| 208 | 277 | { |
| 209 | - $userId = acym_getVar('int', 'userid'); | |
| 210 | - $listId = acym_getVar('int', 'listid'); | |
| 278 | + wp_verify_nonce(acym_getVar('cmd', '_wpnonce'), 'acymnonce') || die('Invalid Token'); | |
| 279 | + // Security (ACL): subscription management is restricted to the 'users' role (backend); no-op on front (ownership). | |
| 280 | + $this->checkToggleAcl('user'); | |
| 211 | 281 | |
| 282 | + $userIds = acym_getVar('array', 'userid', []); | |
| 283 | + $listIds = acym_getVar('array', 'listid', []); | |
| 284 | + | |
| 212 | 285 | $userClass = new UserClass(); |
| 213 | - $result = $userClass->unsubscribe($userId, [$listId]); | |
| 286 | + $userClass->onlyManageableUsers($userIds); | |
| 214 | 287 | |
| 288 | + $listClass = new ListClass(); | |
| 289 | + $listClass->onlyManageableLists($listIds); | |
| 290 | + | |
| 291 | + $userClass = new UserClass(); | |
| 292 | + $result = $userClass->unsubscribe($userIds, $listIds); | |
| 293 | + | |
| 215 | 294 | if ($result) { |
| 216 | 295 | acym_sendAjaxResponse(); |
| 217 | 296 | } else { |
| 218 | 297 | acym_sendAjaxResponse('', [], false); |
| @@ -217,6 +296,5 @@ | ||
| 217 | 296 | } else { |
| 218 | 297 | acym_sendAjaxResponse('', [], false); |
| 219 | 298 | } |
| 220 | 299 | } |
| 221 | - | |
| 222 | 300 | } |