[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * Joomla! Content Management System 5 * 6 * @copyright (C) 2019 Open Source Matters, Inc. <https://www.joomla.org> 7 * @license GNU General Public License version 2 or later; see LICENSE.txt 8 */ 9 10 namespace Joomla\CMS\MVC\Controller; 11 12 use Joomla\CMS\Access\Exception\NotAllowed; 13 use Joomla\CMS\Application\CMSApplication; 14 use Joomla\CMS\Component\ComponentHelper; 15 use Joomla\CMS\Form\Form; 16 use Joomla\CMS\Language\Text; 17 use Joomla\CMS\MVC\Factory\MVCFactoryInterface; 18 use Joomla\CMS\MVC\Model\ListModel; 19 use Joomla\CMS\MVC\View\JsonApiView; 20 use Joomla\CMS\Object\CMSObject; 21 use Joomla\Input\Input; 22 use Joomla\String\Inflector; 23 use Tobscure\JsonApi\Exception\InvalidParameterException; 24 25 // phpcs:disable PSR1.Files.SideEffects 26 \defined('JPATH_PLATFORM') or die; 27 // phpcs:enable PSR1.Files.SideEffects 28 29 /** 30 * Base class for a Joomla API Controller 31 * 32 * Controller (controllers are where you put all the actual code) Provides basic 33 * functionality, such as rendering views (aka displaying templates). 34 * 35 * @since 4.0.0 36 */ 37 class ApiController extends BaseController 38 { 39 /** 40 * The content type of the item. 41 * 42 * @var string 43 * @since 4.0.0 44 */ 45 protected $contentType; 46 47 /** 48 * The URL option for the component. 49 * 50 * @var string 51 * @since 4.0.0 52 */ 53 protected $option; 54 55 /** 56 * The prefix to use with controller messages. 57 * 58 * @var string 59 * @since 4.0.0 60 */ 61 protected $text_prefix; 62 63 /** 64 * The context for storing internal data, e.g. record. 65 * 66 * @var string 67 * @since 4.0.0 68 */ 69 protected $context; 70 71 /** 72 * Items on a page 73 * 74 * @var integer 75 */ 76 protected $itemsPerPage = 20; 77 78 /** 79 * The model state to inject 80 * 81 * @var CMSObject 82 */ 83 protected $modelState; 84 85 /** 86 * Constructor. 87 * 88 * @param array $config An optional associative array of configuration settings. 89 * Recognized key values include 'name', 'default_task', 'model_path', and 90 * 'view_path' (this list is not meant to be comprehensive). 91 * @param MVCFactoryInterface $factory The factory. 92 * @param CMSApplication $app The Application for the dispatcher 93 * @param Input $input Input 94 * 95 * @since 4.0.0 96 * @throws \Exception 97 */ 98 public function __construct($config = array(), MVCFactoryInterface $factory = null, ?CMSApplication $app = null, ?Input $input = null) 99 { 100 $this->modelState = new CMSObject(); 101 102 parent::__construct($config, $factory, $app, $input); 103 104 // Guess the option as com_NameOfController 105 if (empty($this->option)) { 106 $this->option = ComponentHelper::getComponentName($this, $this->getName()); 107 } 108 109 // Guess the \Text message prefix. Defaults to the option. 110 if (empty($this->text_prefix)) { 111 $this->text_prefix = strtoupper($this->option); 112 } 113 114 // Guess the context as the suffix, eg: OptionControllerContent. 115 if (empty($this->context)) { 116 $r = null; 117 118 if (!preg_match('/(.*)Controller(.*)/i', \get_class($this), $r)) { 119 throw new \Exception(Text::sprintf('JLIB_APPLICATION_ERROR_GET_NAME', __METHOD__), 500); 120 } 121 122 $this->context = str_replace('\\', '', strtolower($r[2])); 123 } 124 } 125 126 /** 127 * Basic display of an item view 128 * 129 * @param integer $id The primary key to display. Leave empty if you want to retrieve data from the request 130 * 131 * @return static A \JControllerLegacy object to support chaining. 132 * 133 * @since 4.0.0 134 */ 135 public function displayItem($id = null) 136 { 137 if ($id === null) { 138 $id = $this->input->get('id', 0, 'int'); 139 } 140 141 $viewType = $this->app->getDocument()->getType(); 142 $viewName = $this->input->get('view', $this->default_view); 143 $viewLayout = $this->input->get('layout', 'default', 'string'); 144 145 try { 146 /** @var JsonApiView $view */ 147 $view = $this->getView( 148 $viewName, 149 $viewType, 150 '', 151 ['base_path' => $this->basePath, 'layout' => $viewLayout, 'contentType' => $this->contentType] 152 ); 153 } catch (\Exception $e) { 154 throw new \RuntimeException($e->getMessage()); 155 } 156 157 $modelName = $this->input->get('model', Inflector::singularize($this->contentType)); 158 159 // Create the model, ignoring request data so we can safely set the state in the request from the controller 160 $model = $this->getModel($modelName, '', ['ignore_request' => true, 'state' => $this->modelState]); 161 162 if (!$model) { 163 throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE')); 164 } 165 166 try { 167 $modelName = $model->getName(); 168 } catch (\Exception $e) { 169 throw new \RuntimeException($e->getMessage()); 170 } 171 172 $model->setState($modelName . '.id', $id); 173 174 // Push the model into the view (as default) 175 $view->setModel($model, true); 176 177 $view->document = $this->app->getDocument(); 178 $view->displayItem(); 179 180 return $this; 181 } 182 183 /** 184 * Basic display of a list view 185 * 186 * @return static A \JControllerLegacy object to support chaining. 187 * 188 * @since 4.0.0 189 */ 190 public function displayList() 191 { 192 // Assemble pagination information (using recommended JsonApi pagination notation for offset strategy) 193 $paginationInfo = $this->input->get('page', [], 'array'); 194 $limit = null; 195 $offset = null; 196 197 if (\array_key_exists('offset', $paginationInfo)) { 198 $offset = $paginationInfo['offset']; 199 $this->modelState->set($this->context . '.limitstart', $offset); 200 } 201 202 if (\array_key_exists('limit', $paginationInfo)) { 203 $limit = $paginationInfo['limit']; 204 $this->modelState->set($this->context . '.list.limit', $limit); 205 } 206 207 $viewType = $this->app->getDocument()->getType(); 208 $viewName = $this->input->get('view', $this->default_view); 209 $viewLayout = $this->input->get('layout', 'default', 'string'); 210 211 try { 212 /** @var JsonApiView $view */ 213 $view = $this->getView( 214 $viewName, 215 $viewType, 216 '', 217 ['base_path' => $this->basePath, 'layout' => $viewLayout, 'contentType' => $this->contentType] 218 ); 219 } catch (\Exception $e) { 220 throw new \RuntimeException($e->getMessage()); 221 } 222 223 $modelName = $this->input->get('model', $this->contentType); 224 225 /** @var ListModel $model */ 226 $model = $this->getModel($modelName, '', ['ignore_request' => true, 'state' => $this->modelState]); 227 228 if (!$model) { 229 throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE')); 230 } 231 232 // Push the model into the view (as default) 233 $view->setModel($model, true); 234 235 if ($offset) { 236 $model->setState('list.start', $offset); 237 } 238 239 /** 240 * Sanity check we don't have too much data being requested as regularly in html we automatically set it back to 241 * the last page of data. If there isn't a limit start then set 242 */ 243 if ($limit) { 244 $model->setState('list.limit', $limit); 245 } else { 246 $model->setState('list.limit', $this->itemsPerPage); 247 } 248 249 if (!is_null($offset) && $offset > $model->getTotal()) { 250 throw new Exception\ResourceNotFound(); 251 } 252 253 $view->document = $this->app->getDocument(); 254 255 $view->displayList(); 256 257 return $this; 258 } 259 260 /** 261 * Removes an item. 262 * 263 * @param integer $id The primary key to delete item. 264 * 265 * @return void 266 * 267 * @since 4.0.0 268 */ 269 public function delete($id = null) 270 { 271 if (!$this->app->getIdentity()->authorise('core.delete', $this->option)) { 272 throw new NotAllowed('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED', 403); 273 } 274 275 if ($id === null) { 276 $id = $this->input->get('id', 0, 'int'); 277 } 278 279 $modelName = $this->input->get('model', Inflector::singularize($this->contentType)); 280 281 /** @var \Joomla\CMS\MVC\Model\AdminModel $model */ 282 $model = $this->getModel($modelName, '', ['ignore_request' => true]); 283 284 if (!$model) { 285 throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE')); 286 } 287 288 // Remove the item. 289 if (!$model->delete($id)) { 290 if ($model->getError() !== false) { 291 throw new \RuntimeException($model->getError(), 500); 292 } 293 294 throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_DELETE'), 500); 295 } 296 297 $this->app->setHeader('status', 204); 298 } 299 300 /** 301 * Method to add a new record. 302 * 303 * @return void 304 * 305 * @since 4.0.0 306 * @throws NotAllowed 307 * @throws \RuntimeException 308 */ 309 public function add() 310 { 311 // Access check. 312 if (!$this->allowAdd()) { 313 throw new NotAllowed('JLIB_APPLICATION_ERROR_CREATE_RECORD_NOT_PERMITTED', 403); 314 } 315 316 $recordId = $this->save(); 317 318 $this->displayItem($recordId); 319 } 320 321 /** 322 * Method to edit an existing record. 323 * 324 * @return static A \JControllerLegacy object to support chaining. 325 * 326 * @since 4.0.0 327 */ 328 public function edit() 329 { 330 /** @var \Joomla\CMS\MVC\Model\AdminModel $model */ 331 $model = $this->getModel(Inflector::singularize($this->contentType)); 332 333 if (!$model) { 334 throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE')); 335 } 336 337 try { 338 $table = $model->getTable(); 339 } catch (\Exception $e) { 340 throw new \RuntimeException($e->getMessage()); 341 } 342 343 $recordId = $this->input->getInt('id'); 344 345 if (!$recordId) { 346 throw new Exception\ResourceNotFound(Text::_('JLIB_APPLICATION_ERROR_RECORD'), 404); 347 } 348 349 $key = $table->getKeyName(); 350 351 // Access check. 352 if (!$this->allowEdit(array($key => $recordId), $key)) { 353 throw new NotAllowed('JLIB_APPLICATION_ERROR_CREATE_RECORD_NOT_PERMITTED', 403); 354 } 355 356 // Attempt to check-out the new record for editing and redirect. 357 if ($table->hasField('checked_out') && !$model->checkout($recordId)) { 358 // Check-out failed, display a notice but allow the user to see the record. 359 throw new Exception\CheckinCheckout(Text::sprintf('JLIB_APPLICATION_ERROR_CHECKOUT_FAILED', $model->getError())); 360 } 361 362 $this->save($recordId); 363 $this->displayItem($recordId); 364 365 return $this; 366 } 367 368 /** 369 * Method to save a record. 370 * 371 * @param integer $recordKey The primary key of the item (if exists) 372 * 373 * @return integer The record ID on success, false on failure 374 * 375 * @since 4.0.0 376 */ 377 protected function save($recordKey = null) 378 { 379 /** @var \Joomla\CMS\MVC\Model\AdminModel $model */ 380 $model = $this->getModel(Inflector::singularize($this->contentType)); 381 382 if (!$model) { 383 throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_MODEL_CREATE')); 384 } 385 386 try { 387 $table = $model->getTable(); 388 } catch (\Exception $e) { 389 throw new \RuntimeException($e->getMessage()); 390 } 391 392 $key = $table->getKeyName(); 393 $data = $this->input->get('data', json_decode($this->input->json->getRaw(), true), 'array'); 394 $checkin = property_exists($table, $table->getColumnAlias('checked_out')); 395 $data[$key] = $recordKey; 396 397 if ($this->input->getMethod() === 'PATCH') { 398 if ($recordKey && $table->load($recordKey)) { 399 $fields = $table->getFields(); 400 401 foreach ($fields as $field) { 402 if (array_key_exists($field->Field, $data)) { 403 continue; 404 } 405 406 $data[$field->Field] = $table->{$field->Field}; 407 } 408 } 409 } 410 411 $data = $this->preprocessSaveData($data); 412 413 // @todo: Not the cleanest thing ever but it works... 414 Form::addFormPath(JPATH_COMPONENT_ADMINISTRATOR . '/forms'); 415 416 // Needs to be set because com_fields needs the data in jform to determine the assigned catid 417 $this->input->set('jform', $data); 418 419 // Validate the posted data. 420 $form = $model->getForm($data, false); 421 422 if (!$form) { 423 throw new \RuntimeException(Text::_('JLIB_APPLICATION_ERROR_FORM_CREATE')); 424 } 425 426 // Test whether the data is valid. 427 $validData = $model->validate($form, $data); 428 429 // Check for validation errors. 430 if ($validData === false) { 431 $errors = $model->getErrors(); 432 $messages = []; 433 434 // Push up to three validation messages out to the user. 435 for ($i = 0, $n = \count($errors); $i < $n && $i < 3; $i++) { 436 if ($errors[$i] instanceof \Exception) { 437 $messages[] = "{$errors[$i]->getMessage()}"; 438 } else { 439 $messages[] = "{$errors[$i]}"; 440 } 441 } 442 443 throw new InvalidParameterException(implode("\n", $messages)); 444 } 445 446 if (!isset($validData['tags'])) { 447 $validData['tags'] = array(); 448 } 449 450 // Attempt to save the data. 451 if (!$model->save($validData)) { 452 throw new Exception\Save(Text::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError())); 453 } 454 455 try { 456 $modelName = $model->getName(); 457 } catch (\Exception $e) { 458 throw new \RuntimeException($e->getMessage()); 459 } 460 461 // Ensure we have the record ID in case we created a new article 462 $recordId = $model->getState($modelName . '.id'); 463 464 if ($recordId === null) { 465 throw new Exception\CheckinCheckout(Text::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError())); 466 } 467 468 // Save succeeded, so check-in the record. 469 if ($checkin && $model->checkin($recordId) === false) { 470 throw new Exception\CheckinCheckout(Text::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError())); 471 } 472 473 return $recordId; 474 } 475 476 /** 477 * Method to check if you can edit an existing record. 478 * 479 * Extended classes can override this if necessary. 480 * 481 * @param array $data An array of input data. 482 * @param string $key The name of the key for the primary key; default is id. 483 * 484 * @return boolean 485 * 486 * @since 4.0.0 487 */ 488 protected function allowEdit($data = array(), $key = 'id') 489 { 490 return $this->app->getIdentity()->authorise('core.edit', $this->option); 491 } 492 493 /** 494 * Method to check if you can add a new record. 495 * 496 * Extended classes can override this if necessary. 497 * 498 * @param array $data An array of input data. 499 * 500 * @return boolean 501 * 502 * @since 4.0.0 503 */ 504 protected function allowAdd($data = array()) 505 { 506 $user = $this->app->getIdentity(); 507 508 return $user->authorise('core.create', $this->option) || \count($user->getAuthorisedCategories($this->option, 'core.create')); 509 } 510 511 /** 512 * Method to allow extended classes to manipulate the data to be saved for an extension. 513 * 514 * @param array $data An array of input data. 515 * 516 * @return array 517 * 518 * @since 4.0.0 519 */ 520 protected function preprocessSaveData(array $data): array 521 { 522 return $data; 523 } 524 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |