| @@ -10,13 +10,13 @@ | ||
| 10 | 10 | cssJSToolbox::import('framework:mvc:controller-ajax.inc.php'); |
| 11 | 11 | |
| 12 | 12 | /** |
| 13 | 13 | * Serve blocks page Ajax requests. |
| 14 | -* | |
| 14 | +* | |
| 15 | 15 | * The Actions resident here is global for only the blocks page, its not |
| 16 | 16 | * for a specific/single block. You can find single block |
| 17 | 17 | * actions in block-ajax.php file. |
| 18 | -* | |
| 18 | +* | |
| 19 | 19 | * @deprecated DONT ADD MORE ACTIONS HERE! |
| 20 | 20 | * @author Ahmed Said |
| 21 | 21 | * @version 6 |
| 22 | 22 | */ |
| @@ -23,16 +23,16 @@ | ||
| 23 | 23 | class CJTBlocksAjaxController extends CJTAjaxController { |
| 24 | 24 | |
| 25 | 25 | /** |
| 26 | 26 | * put your comment there... |
| 27 | - * | |
| 27 | + * | |
| 28 | 28 | * @var mixed |
| 29 | 29 | */ |
| 30 | 30 | protected $controllerInfo = array('model' => 'blocks'); |
| 31 | - | |
| 31 | + | |
| 32 | 32 | /** |
| 33 | 33 | * Initialize controller object. |
| 34 | - * | |
| 34 | + * | |
| 35 | 35 | * @see CJTController for more details |
| 36 | 36 | * @return void |
| 37 | 37 | */ |
| 38 | 38 | public function __construct() { |
| @@ -41,27 +41,26 @@ | ||
| 41 | 41 | $this->registryAction('create_block'); |
| 42 | 42 | $this->registryAction('get_view'); |
| 43 | 43 | $this->registryAction('save_blocks'); |
| 44 | 44 | $this->registryAction('saveOrder'); |
| 45 | - $this->registryAction('loadBlock'); | |
| 46 | 45 | } |
| 47 | - | |
| 46 | + | |
| 48 | 47 | /** |
| 49 | 48 | * Create new block. |
| 50 | - * | |
| 49 | + * | |
| 51 | 50 | * Once this method is called a new block is saved into |
| 52 | 51 | * the database. |
| 53 | - * | |
| 52 | + * | |
| 54 | 53 | * Call this method using GET method with the following parameters. |
| 55 | 54 | * - array ids Ids for all the available blocks. |
| 56 | 55 | * - [name] string Block name. |
| 57 | 56 | * - [state] string Block state. |
| 58 | 57 | * - [location] string Block hook location. |
| 59 | - * | |
| 58 | + * | |
| 60 | 59 | * Response body is array with the following elements. |
| 61 | 60 | * - integer id New block id. |
| 62 | 61 | * - string view Block HTML code. |
| 63 | - * | |
| 62 | + * | |
| 64 | 63 | * @return void |
| 65 | 64 | */ |
| 66 | 65 | public function createBlockAction($blockId = null, $blockType = null, $pinPoint = null, $viewName = null) { |
| 67 | 66 | $response = array(); |
| @@ -66,19 +65,10 @@ | ||
| 66 | 65 | public function createBlockAction($blockId = null, $blockType = null, $pinPoint = null, $viewName = null) { |
| 67 | 66 | $response = array(); |
| 68 | 67 | // If viewName not provided read it from request vars. |
| 69 | 68 | if (!$viewName) { |
| 70 | - $viewName = filter_input(INPUT_GET, 'viewName', FILTER_UNSAFE_RAW); | |
| 71 | - $viewName = is_string($viewName) ? sanitize_text_field($viewName) : null; | |
| 69 | + $viewName = filter_input(INPUT_GET, 'viewName', FILTER_SANITIZE_STRING); | |
| 72 | 70 | } |
| 73 | - // $viewName is interpolated into a filesystem path by CJTController::getView(), | |
| 74 | - // which require_once's "views/blocks/{$viewName}/view.php". Restrict it to the | |
| 75 | - // known block views so traversal sequences can never reach that include. | |
| 76 | - $allowedBlockViews = array('cjt-block', 'block', 'metabox', 'create-metabox'); | |
| 77 | - if ($viewName && !in_array($viewName, $allowedBlockViews, true)) { | |
| 78 | - $this->httpCode = '403 Forbidden'; | |
| 79 | - return; | |
| 80 | - } | |
| 81 | 71 | // Prepare parameters. |
| 82 | 72 | $defaultBlockName = 'block_' . hexdec(substr(md5(time()), 0, 6)); |
| 83 | 73 | $wordpressMYSQLTime = current_time('mysql'); |
| 84 | 74 | // Block data to insert. |
| @@ -99,20 +89,18 @@ | ||
| 99 | 89 | if (array_key_exists($name, $_GET)) { |
| 100 | 90 | $blockData[$name] = $_GET[$name]; |
| 101 | 91 | } |
| 102 | 92 | } |
| 103 | - // Harden the block name server-side (CVE-2025-13533 defence in depth). | |
| 104 | - $blockData['name'] = self::sanitizeBlockName($blockData['name']); | |
| 105 | 93 | // Import block model. |
| 106 | 94 | require_once CJTOOLBOX_MODELS_PATH . '/block.php'; |
| 107 | 95 | $block = new CJTBlockModel($blockData); |
| 108 | 96 | // Add block. |
| 109 | 97 | $blocksModel =& $this->model; |
| 110 | - $blockId = $blocksModel->add($block->getValues(), true); | |
| 98 | + $blockId = $blocksModel->add($block->getValues()); | |
| 111 | 99 | $blocksModel->save(); |
| 112 | 100 | // Read newly added block from database. |
| 113 | - $newBlockData = $blocksModel->getBlock($blockId, array('returnCodeFile' => true)); | |
| 114 | - | |
| 101 | + $newBlockData = $blocksModel->getBlock($blockId); | |
| 102 | + | |
| 115 | 103 | if ($newBlockData === null) { |
| 116 | 104 | throw new Exception('Could not add new block!!!'); |
| 117 | 105 | } |
| 118 | 106 | else { |
| @@ -123,25 +111,25 @@ | ||
| 123 | 111 | // Push vars into the view. |
| 124 | 112 | $blockView->setBlock($block); |
| 125 | 113 | $response['view'] = $blockView->getTemplate('new'); |
| 126 | 114 | } |
| 127 | - $response['id'] = $blockId; | |
| 115 | + $response['id'] = $blockId; | |
| 128 | 116 | // Set response object. |
| 129 | - $this->response = $response; | |
| 117 | + $this->response = $response; | |
| 130 | 118 | } |
| 131 | 119 | } |
| 132 | - | |
| 120 | + | |
| 133 | 121 | /** |
| 134 | 122 | * Get view content through ajax request. |
| 135 | - * | |
| 123 | + * | |
| 136 | 124 | * The method is useful for requesting Popup forms through ajax (e.g ThickBox). |
| 137 | 125 | * You can request any view specified in the $allowedViews array. |
| 138 | - * | |
| 126 | + * | |
| 139 | 127 | * Call this method using GET method with the following parameters. |
| 140 | 128 | * - viewName string Name of the view. |
| 141 | - * | |
| 129 | + * | |
| 142 | 130 | * Response body is the view content string. |
| 143 | - * | |
| 131 | + * | |
| 144 | 132 | * @return void |
| 145 | 133 | */ |
| 146 | 134 | public function getViewAction() { |
| 147 | 135 | // Some views required objects to be pushed into it before displaying |
| @@ -150,10 +138,9 @@ | ||
| 150 | 138 | $allowedViews = array( |
| 151 | 139 | 'blocks/new' => array(), |
| 152 | 140 | ); |
| 153 | 141 | // Prepare parameters. |
| 154 | - $viewName = filter_input(INPUT_GET, 'viewName', FILTER_UNSAFE_RAW); | |
| 155 | - $viewName = is_string($viewName) ? sanitize_text_field($viewName) : ''; | |
| 142 | + $viewName = filter_input(INPUT_GET, 'viewName', FILTER_SANITIZE_STRING); | |
| 156 | 143 | if (array_key_exists($viewName, $allowedViews) === FALSE) { |
| 157 | 144 | $this->httpCode = '403 Forbidden'; |
| 158 | 145 | } |
| 159 | 146 | else { |
| @@ -179,90 +166,51 @@ | ||
| 179 | 166 | } |
| 180 | 167 | |
| 181 | 168 | /** |
| 182 | 169 | * put your comment there... |
| 183 | - * | |
| 170 | + * | |
| 184 | 171 | */ |
| 185 | - public function loadBlockAction() { | |
| 186 | - // Block Id. | |
| 187 | - $blockId = (int) $_GET['blockId']; | |
| 188 | - // Get block content. | |
| 189 | - $view = CJTView::getInstance('blocks/cjt-block'); | |
| 190 | - $view->setBlock(CJTModel::create('blocks')->getBlock($blockId, array('returnCodeFile' => true))); | |
| 191 | - // Return View content. | |
| 192 | - $view->getTemplate('default'); | |
| 193 | - $this->response = $view->structuredContent; | |
| 194 | - } | |
| 195 | - | |
| 196 | - /** | |
| 197 | - * put your comment there... | |
| 198 | - * | |
| 199 | - */ | |
| 200 | - public function saveBlocksAction() | |
| 201 | - { | |
| 172 | + public function saveBlocksAction() { | |
| 202 | 173 | $response = array(); |
| 203 | - | |
| 174 | + // Single block model class. | |
| 175 | + require_once CJTOOLBOX_MODELS_PATH . '/block.php'; | |
| 204 | 176 | // Blocks are sent ins single array list. |
| 205 | - $blocksToSave = filter_input( INPUT_POST, 'blocks', FILTER_UNSAFE_RAW, FILTER_REQUIRE_ARRAY ); | |
| 206 | - $calculatePinPoint = ( bool ) filter_input( INPUT_POST, 'calculatePinPoint', FILTER_SANITIZE_NUMBER_INT ); | |
| 207 | - $createRevision = ( bool ) filter_input( INPUT_POST, 'createRevision', FILTER_SANITIZE_NUMBER_INT ); | |
| 208 | - | |
| 209 | - // For any reason that cause Client/JavaScript to send empty blocks, | |
| 177 | + $blocksToSave = filter_input(INPUT_POST, 'blocks', FILTER_UNSAFE_RAW, FILTER_REQUIRE_ARRAY); | |
| 178 | + $calculatePinPoint = (bool) filter_input(INPUT_POST, 'calculatePinPoint', FILTER_SANITIZE_NUMBER_INT); | |
| 179 | + $createRevision = (bool) filter_input(INPUT_POST, 'createRevision', FILTER_SANITIZE_NUMBER_INT); | |
| 180 | + // For any reason that cause Client/Javascript to send empty blocks, | |
| 210 | 181 | // make sure we're save. |
| 211 | - if ( is_array( $blocksToSave ) && ! empty( $blocksToSave ) ) | |
| 212 | - { | |
| 213 | - | |
| 214 | - foreach ( $blocksToSave as $id => $postedblockPartialData ) | |
| 215 | - { | |
| 182 | + if (is_array($blocksToSave) && !empty($blocksToSave)) { | |
| 183 | + foreach ($blocksToSave as $id => $postedblockPartialData) { | |
| 216 | 184 | // Push block id into block data. |
| 217 | - $blockData = ( object ) $postedblockPartialData; | |
| 185 | + $blockData = (object) $postedblockPartialData; | |
| 218 | 186 | $blockData->id = $id; |
| 219 | - | |
| 220 | - // Harden the block name server-side (CVE-2025-13533 defence in depth). | |
| 221 | - if ( isset( $blockData->name ) ) { | |
| 222 | - $blockData->name = self::sanitizeBlockName( $blockData->name ); | |
| 223 | - } | |
| 224 | - | |
| 225 | 187 | // Recalculate pinPoint field value. |
| 226 | - ! $calculatePinPoint or ( CJTBlockModel::arrangePins( $blockData ) && CJTBlockModel::calculateBlockPinPoint( $blockData ) ); | |
| 227 | - | |
| 188 | + !$calculatePinPoint or CJTBlockModel::calculateBlockPinPoint($blockData); | |
| 228 | 189 | // Create block revision. |
| 229 | - ! $createRevision or $this->model->addRevision( $id, $blockData->activeFileId ); | |
| 230 | - | |
| 190 | + !$createRevision or $this->model->addRevision($id); | |
| 231 | 191 | // Set lastModified field to current time. |
| 232 | - $blockData->lastModified = current_time( 'mysql' ); | |
| 233 | - | |
| 192 | + $blockData->lastModified = current_time('mysql'); | |
| 234 | 193 | // Update database. |
| 235 | - $this->model->update( $blockData, $calculatePinPoint ); | |
| 194 | + $this->model->update($blockData, $calculatePinPoint); | |
| 236 | 195 | $this->model->save(); |
| 237 | - | |
| 238 | 196 | // Send the changes properties back to client. |
| 239 | - $updatedBlockData = $this->model->getBlock($id, null, array('*'), ARRAY_A); | |
| 240 | - | |
| 241 | - foreach ( $updatedBlockData as $property => $value ) | |
| 242 | - { | |
| 243 | - $response[ $id ][ $property ][ 'value' ] = $value; | |
| 197 | + foreach ($postedblockPartialData as $property => $value) { | |
| 198 | + $response[$id][$property]['value'] = $value; | |
| 244 | 199 | } |
| 245 | - | |
| 246 | 200 | } |
| 247 | - | |
| 248 | 201 | } |
| 249 | - | |
| 250 | 202 | // Delete other blocks. |
| 251 | - empty( $_POST[ 'deletedBlocks' ] ) or $this->model->delete( $_POST[ 'deletedBlocks' ] ); | |
| 252 | - | |
| 203 | + empty($_POST['deletedBlocks']) or $this->model->delete($_POST['deletedBlocks']); | |
| 253 | 204 | // Save changes. |
| 254 | 205 | $this->model->save(); |
| 255 | - | |
| 256 | - // Return | |
| 257 | 206 | // Set response. |
| 258 | 207 | $this->response = $response; |
| 259 | - | |
| 260 | 208 | } |
| 261 | - | |
| 209 | + | |
| 262 | 210 | /** |
| 263 | 211 | * put your comment there... |
| 264 | - * | |
| 212 | + * | |
| 265 | 213 | */ |
| 266 | 214 | public function saveOrderAction() { |
| 267 | 215 | // Read order. |
| 268 | 216 | $order = array('normal' => $_GET['order']); |
| @@ -269,31 +217,6 @@ | ||
| 269 | 217 | // Centralized orders to be shared between all users! |
| 270 | 218 | $this->model->setOrder($order); |
| 271 | 219 | $this->response = array('order' => $order, 'state' => 'saved'); |
| 272 | 220 | } |
| 273 | - | |
| 274 | - /** | |
| 275 | - * Sanitize a code-block name coming from the request. | |
| 276 | - * | |
| 277 | - * Defence-in-depth for the stored-XSS issue (CVE-2025-13533): the client-side UI | |
| 278 | - * already restricts block names to A-Z, 0-9, space, '-' and '_', but that check is | |
| 279 | - * trivially bypassed (e.g. via an HTTP proxy). We enforce the exact same contract | |
| 280 | - * server-side so characters that could break out of an HTML attribute/element - | |
| 281 | - * such as < > " ' - can never be persisted. Output is still escaped at every sink | |
| 282 | - * as the primary defence; this simply keeps the stored data clean. | |
| 283 | - * | |
| 284 | - * @param mixed $name Raw name value from the request. | |
| 285 | - * @return string Sanitized name (never empty). | |
| 286 | - */ | |
| 287 | - public static function sanitizeBlockName($name) { | |
| 288 | - // Keep only the documented allowed characters. | |
| 289 | - $name = preg_replace('/[^A-Za-z0-9 _-]/', '', (string) $name); | |
| 290 | - // Collapse to a trimmed value and guard against an empty result. | |
| 291 | - $name = trim($name); | |
| 292 | - if ($name === '') { | |
| 293 | - $name = 'block_' . hexdec(substr(md5((string) time()), 0, 6)); | |
| 294 | - } | |
| 295 | - // Respect the 50 char column/UI limit. | |
| 296 | - return substr($name, 0, 50); | |
| 297 | - } | |
| 298 | - | |
| 221 | + | |
| 299 | 222 | } // End class. |