modrestcontroller.class.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792
  1. <?php
  2. /*
  3. * This file is part of the MODX Revolution package.
  4. *
  5. * Copyright (c) MODX, LLC
  6. *
  7. * For complete copyright and license information, see the COPYRIGHT and LICENSE
  8. * files found in the top-level directory of this distribution.
  9. *
  10. */
  11. /**
  12. * Abstract controller class for modRestService; all REST controllers must extend this class to be properly
  13. * implemented.
  14. *
  15. * @package modx
  16. * @subpackage rest
  17. */
  18. abstract class modRestController {
  19. /** @var modX $modx The modX instance */
  20. public $modx;
  21. /** @var array $config An array of configuration properties, passed from modRestService */
  22. public $config = array();
  23. /** @var array $properties An array of request parameters passed */
  24. public $properties = array();
  25. /** @var array $headers An array of HTTP headers passed */
  26. public $headers = array();
  27. /** @var string $primaryKeyField The primary key field for this controller; useful when automating REST calls */
  28. public $primaryKeyField = 'id';
  29. /** @var array $errors An array of errors that may have occurred for this controller */
  30. public $errors = array();
  31. /** @var string $errorMessage A generic error message for this response */
  32. public $errorMessage = '';
  33. /** @var boolean $protected Whether or not this controller is "protected" - meaning whether or not verifyAuthentication will be called*/
  34. protected $protected = true;
  35. /** @var \modRestServiceRequest $request The request object passed to this controller */
  36. protected $request;
  37. /** @var string $response The response being sent by this controller */
  38. protected $response;
  39. /** @var string $responseStatus The response status being sent by this controller */
  40. protected $responseStatus;
  41. /**
  42. * The following options are used if the default get/put/post/delete methods are not overridden. They automate
  43. * the display and manipulation of data based on the classKey that is specified on the controller class, allowing
  44. * for quick and easy controller creation based on standard CRUD concepts.
  45. */
  46. /** @var string $classKey The xPDO class to use */
  47. public $classKey;
  48. /** @var string $classAlias The alias of the class when used in the getList method */
  49. public $classAlias;
  50. /** @var string $defaultSortField The default field to sort by in the getList method */
  51. public $defaultSortField = 'name';
  52. /** @var string $defaultSortDirection The default direction to sort in the getList method */
  53. public $defaultSortDirection = 'ASC';
  54. /** @var int $defaultLimit The default number of records to return in the getList method */
  55. public $defaultLimit = 20;
  56. /** @var int $defaultOffset The default offset in the getList method */
  57. public $defaultOffset = 0;
  58. /** @var xPDOObject $object */
  59. public $object;
  60. /** @var array $searchFields Optional. An array of fields to use when the search parameter is passed */
  61. public $searchFields = array();
  62. /** @var array $postRequiredFields An array of required field keys that must be passed for POST requests */
  63. public $postRequiredFields = array();
  64. /** @var array $postRequiredRelatedObjects An array of classKey/field pairings for checking related objects on POST */
  65. public $postRequiredRelatedObjects = array();
  66. /** @var string $postMethod The method on the object to call for POST requests */
  67. public $postMethod = 'save';
  68. /** @var array $putRequiredFields An array of required field keys that must be passed for PUT requests */
  69. public $putRequiredFields = array();
  70. /** @var array $postRequiredRelatedObjects An array of classKey/field pairings for checking related objects on PUT */
  71. public $putRequiredRelatedObjects = array();
  72. /** @var string $putMethod The method on the object to call for PUT requests */
  73. public $putMethod = 'save';
  74. /** @var array $deleteRequiredFields An array of required field keys that must be passed for DELETE requests */
  75. public $deleteRequiredFields = array();
  76. /** @var string $deleteMethod The method on the object to call for DELETE requests */
  77. public $deleteMethod = 'remove';
  78. /** @var array $allowedMethods An array of allowed request methods */
  79. public $allowedMethods = array('GET', 'POST', 'PUT', 'DELETE');
  80. /** @var array $allowedMethods An array of allowed request methods */
  81. public $allowedHeaders = array('Content-Type');
  82. /**
  83. * @param modX $modx The modX instance
  84. * @param modRestServiceRequest $request The rest service request class instance
  85. * @param array $config An array of configuration properties, passed through from modRestService
  86. */
  87. public function __construct(modX $modx,modRestServiceRequest $request,array $config = array()) {
  88. $this->modx =& $modx;
  89. $this->request =& $request;
  90. $this->config = array_merge($this->config,$config);
  91. }
  92. /**
  93. * Get a configuration option for this controller
  94. *
  95. * @param string $key
  96. * @param mixed $default
  97. * @return mixed
  98. */
  99. public function getOption($key,$default = null) {
  100. return array_key_exists($key,$this->config) ? $this->config[$key] : $default;
  101. }
  102. /**
  103. * Initialize the controller
  104. */
  105. public function initialize() {}
  106. /**
  107. * Override to verify authentication on this specific controller. Useful for managing permissions.
  108. *
  109. * @return boolean
  110. */
  111. public function verifyAuthentication() {
  112. return true;
  113. }
  114. /**
  115. * Return whether or not this controller is set to be protected
  116. * @final
  117. * @return bool
  118. */
  119. final public function isProtected() {
  120. return $this->protected;
  121. }
  122. /**
  123. * Check for any empty fields
  124. *
  125. * @param array $fields
  126. * @param boolean $setFieldError
  127. * @return bool|string
  128. */
  129. public function checkRequiredFields(array $fields = array(),$setFieldError = true) {
  130. $missing = array();
  131. foreach ($fields as $field) {
  132. $value = $this->getProperty($field);
  133. $isEmptyString = empty($value) && $value !== "0";
  134. $isNotBase = !is_int($value) && !is_bool($value);
  135. if ($isEmptyString && $isNotBase) {
  136. $missing[] = $field;
  137. if ($setFieldError) {
  138. $this->addFieldError($field,$this->modx->lexicon('rest.err_field_required'));
  139. }
  140. }
  141. }
  142. if (!empty($missing)) {
  143. return $this->modx->lexicon('rest.err_fields_required',array(
  144. 'fields' => implode(', ',$missing),
  145. ));
  146. }
  147. return true;
  148. }
  149. /**
  150. * Check to ensure the existence of required related objects on the passed request
  151. *
  152. * @param array $pairs An array of arrays in the format: 'field' => 'classKey'
  153. * @return boolean
  154. */
  155. public function checkRequiredRelatedObjects(array $pairs = array()) {
  156. $passed = true;
  157. foreach ($pairs as $field => $classKey) {
  158. if (!empty($classKey) && !empty($field)) {
  159. $relatedObject = $this->modx->getObject($classKey,$this->getProperty($field));
  160. if (empty($relatedObject)) {
  161. $objectName = substr($classKey,2);
  162. $this->addFieldError($field,$this->modx->lexicon('err.obj_nf',array('name' => $objectName)));
  163. $passed = false;
  164. }
  165. }
  166. }
  167. return $passed;
  168. }
  169. /**
  170. * Get a REQUEST property for the controller
  171. *
  172. * @param string $key
  173. * @param mixed $default
  174. * @return mixed
  175. */
  176. public function getProperty($key,$default =null) {
  177. $value = $default;
  178. if (array_key_exists($key,$this->properties)) {
  179. $value = $this->properties[$key];
  180. }
  181. return $value;
  182. }
  183. /**
  184. * Set a request property for the controller
  185. *
  186. * @param string $key
  187. * @param string $value
  188. */
  189. public function setProperty($key,$value) {
  190. $this->properties[$key] = $value;
  191. }
  192. /**
  193. * Unset a request property for the controller
  194. * @param string $key
  195. */
  196. public function unsetProperty($key) {
  197. unset($this->properties[$key]);
  198. }
  199. /**
  200. * Get the request properties for the controller
  201. * @return array
  202. */
  203. public function getProperties() {
  204. return $this->properties;
  205. }
  206. /**
  207. * Set a collection of properties for the controller
  208. *
  209. * @param array $properties An array of properties
  210. * @param bool $merge Optionally, only merge properties in if this is true
  211. */
  212. public function setProperties(array $properties = array(),$merge = false) {
  213. $this->properties = $merge ? array_merge($this->properties,$properties) : $properties;
  214. }
  215. /**
  216. * Set the HTTP request headers for this controller
  217. *
  218. * @param array $headers An array of headers
  219. * @param bool $merge Optionally, only merge headers in if this is true
  220. */
  221. public function setHeaders(array $headers = array(),$merge = false) {
  222. $this->headers = $merge ? array_merge($this->headers,$headers) : $headers;
  223. }
  224. /**
  225. * Get the request headers for this controller
  226. * @return array
  227. */
  228. final public function getHeaders() {
  229. return $this->headers;
  230. }
  231. /**
  232. * Return a success message for this controller, with an optional return object
  233. *
  234. * @param string $message Optional. The success response message.
  235. * @param array|xPDOObject $object Optional. An xPDOObject or array to send as the return object.
  236. * @param int $status Optional. The status code to send.
  237. */
  238. public function success($message = '',$object = array(),$status = null) {
  239. if (empty($status)) $status = $this->getOption('defaultSuccessStatusCode',200);
  240. $this->process(true,$message,$object,$status);
  241. }
  242. /**
  243. * Return a failure message for this controller, with an optional return object. Will also automatically
  244. * send errors in an errors root node if any are found.
  245. *
  246. * @param string $message Optional. The failure response message.
  247. * @param array|xPDOObject $object Optional. An xPDOObject or array to send as the return object.
  248. * @param int $status Optional. The status code to send.
  249. */
  250. public function failure($message = '',$object = array(),$status = null) {
  251. if (empty($status)) $status = $this->getOption('defaultFailureStatusCode',200);
  252. $this->process(false,$message,$object,$status);
  253. }
  254. /**
  255. * Process the response and format in the proper response format.
  256. *
  257. * @param bool $success Whether or not this response is successful.
  258. * @param string $message Optional. The response message.
  259. * @param array|xPDOObject $object Optional. The response return object.
  260. * @param int $status Optional. The response code.
  261. */
  262. protected function process($success = true,$message = '',$object = array(),$status = 200) {
  263. $response = array(
  264. $this->getOption('responseSuccessKey','success') => $success,
  265. $this->getOption('responseMessageKey','message') => $message,
  266. $this->getOption('responseObjectKey','object') => is_object($object) ? $object->toArray() : $object,
  267. 'code' => $status
  268. );
  269. if (empty($success) && !empty($this->errors)) {
  270. $response[$this->getOption('responseErrorsKey','errors')] = $this->errors;
  271. }
  272. $this->modx->log(modX::LOG_LEVEL_DEBUG,'[REST] Sending REST response: '.print_r($response,true));
  273. $this->response = $response;
  274. $this->responseStatus = empty($status) ? (empty($success) ? $this->getOption('defaultFailureStatusCode',200) : $this->getOption('defaultSuccessStatusCode',200)) : $status;
  275. }
  276. /**
  277. * Return the response status code
  278. * @return string
  279. */
  280. final public function getResponseStatus() {
  281. return $this->responseStatus;
  282. }
  283. /**
  284. * Return the response payload
  285. *
  286. * @return string
  287. */
  288. final public function getResponse() {
  289. return $this->response;
  290. }
  291. /**
  292. * Get any errors that may have been set on this controller
  293. *
  294. * @return array
  295. */
  296. public function getErrors() {
  297. return $this->errors;
  298. }
  299. /**
  300. * See if any errors have been set during the request on this controller
  301. *
  302. * @return bool
  303. */
  304. public function hasErrors() {
  305. return !empty($this->errors) || !empty($this->errorMessage);
  306. }
  307. /**
  308. * Set an error for a specific field
  309. * @param string $k The key of the field to set
  310. * @param string $v The error message to set
  311. * @param boolean $append Whether or not to append the error message or overwrite it
  312. */
  313. public function addFieldError($k,$v,$append = true) {
  314. if ($append && !empty($this->errors[$k])) {
  315. $separator = $this->getOption('errorMessageSeparator',' ');
  316. $this->errors[$k] .= $separator.$v;
  317. } else {
  318. $this->errors[$k] = $v;
  319. }
  320. }
  321. /**
  322. * Remove an error from a field
  323. *
  324. * @param string $k
  325. */
  326. public function removeFieldError($k) {
  327. unset($this->errors[$k]);
  328. }
  329. /**
  330. * Set the general error message
  331. *
  332. * @param string $message
  333. */
  334. public function setErrorMessage($message) {
  335. $this->errorMessage = $message;
  336. }
  337. /**
  338. * Clear the general error message
  339. */
  340. public function clearErrorMessage() {
  341. $this->errorMessage = '';
  342. }
  343. /**
  344. * Set a default value for a property on this controller request.
  345. * @param string $k The key of the field
  346. * @param mixed $v The default value to set
  347. * @param bool $useNotEmpty Whether or not to use empty() for checking set status
  348. * @return boolean True if the default was used
  349. */
  350. public function setDefault($k,$v,$useNotEmpty = false) {
  351. $isSet = false;
  352. if ($useNotEmpty) {
  353. if (!empty($this->properties[$k])) $isSet = true;
  354. } else if (array_key_exists($k,$this->properties)) {
  355. $isSet = true;
  356. }
  357. if (!$isSet) {
  358. $this->properties[$k] = $v;
  359. }
  360. return !$isSet;
  361. }
  362. /**
  363. * Set an array of default values for properties on this controller request
  364. * @param array $array
  365. * @param bool $useNotEmpty
  366. */
  367. public function setDefaults(array $array,$useNotEmpty = false) {
  368. foreach ($array as $k => $v) {
  369. $this->setDefault($k,$v,$useNotEmpty);
  370. }
  371. }
  372. /**
  373. * Output a collection of objects as a list.
  374. *
  375. * @param array $list
  376. * @param int|boolean $total
  377. * @param int $status
  378. */
  379. public function collection($list = array(),$total = false,$status = null) {
  380. if (empty($status)) $status = $this->getOption('defaultSuccessStatusCode',200);
  381. if ($total === false) {
  382. $total = count($list);
  383. }
  384. $this->response = array(
  385. $this->getOption('collectionResultsKey','results') => $list,
  386. $this->getOption('collectionTotalKey','total') => $total,
  387. );
  388. $this->responseStatus = $status;
  389. }
  390. /**
  391. * Route GET requests
  392. * @return array
  393. */
  394. public function get() {
  395. $pk = $this->getProperty($this->primaryKeyField);
  396. if (empty($pk)) {
  397. return $this->getList();
  398. }
  399. return $this->read($pk);
  400. }
  401. /**
  402. * Abstract method for routing GET requests without a primary key passed. Must be defined in your derivative
  403. * controller. Handles fetching of collections of objects.
  404. *
  405. * @abstract
  406. * @return array
  407. */
  408. public function getList() {
  409. $this->getProperties();
  410. $c = $this->modx->newQuery($this->classKey);
  411. $c = $this->addSearchQuery($c);
  412. $c = $this->prepareListQueryBeforeCount($c);
  413. $total = $this->modx->getCount($this->classKey,$c);
  414. $alias = !empty($this->classAlias) ? $this->classAlias : $this->classKey;
  415. $c->select($this->modx->getSelectColumns($this->classKey,$alias));
  416. $c = $this->prepareListQueryAfterCount($c);
  417. $c->sortby($this->getProperty($this->getOption('propertySort','sort'),$this->defaultSortField),$this->getProperty($this->getOption('propertySortDir','dir'),$this->defaultSortDirection));
  418. $limit = $this->getProperty($this->getOption('propertyLimit','limit'),$this->defaultLimit);
  419. if (empty($limit)) $limit = $this->defaultLimit;
  420. $c->limit($limit,$this->getProperty($this->getOption('propertyOffset','start'),$this->defaultOffset));
  421. $objects = $this->modx->getCollection($this->classKey,$c);
  422. if (empty($objects)) $objects = array();
  423. $list = array();
  424. /** @var xPDOObject $object */
  425. foreach ($objects as $object) {
  426. $list[] = $this->prepareListObject($object);
  427. }
  428. return $this->collection($list,$total);
  429. }
  430. /**
  431. * Add a search query to listing calls
  432. *
  433. * @param xPDOQuery $c
  434. * @return xPDOQuery
  435. */
  436. protected function addSearchQuery(xPDOQuery $c) {
  437. $search = $this->getProperty($this->getOption('propertySearch','search'),false);
  438. if (!empty($search) && !empty($this->searchFields)) {
  439. $searchQuery = array();
  440. $i = 0;
  441. foreach ($this->searchFields as $searchField) {
  442. $or = $i > 0 ? 'OR:' : '';
  443. $searchQuery[$or.$searchField.':LIKE'] = '%'.$search.'%';
  444. $i++;
  445. }
  446. if (!empty($searchQuery)) {
  447. $c->where($searchQuery);
  448. }
  449. }
  450. return $c;
  451. }
  452. /**
  453. * Allows manipulation of the query object before the COUNT statement is called on listing calls. Override to
  454. * provide custom functionality.
  455. *
  456. * @param xPDOQuery $c
  457. * @return xPDOQuery
  458. */
  459. protected function prepareListQueryBeforeCount(xPDOQuery $c) {
  460. return $c;
  461. }
  462. /**
  463. * Allows manipulation of the query object after the COUNT statement is called on listing calls. Override to
  464. * provide custom functionality.
  465. *
  466. * @param xPDOQuery $c
  467. * @return xPDOQuery
  468. */
  469. protected function prepareListQueryAfterCount(xPDOQuery $c) {
  470. return $c;
  471. }
  472. /**
  473. * Returns an array of field-value pairs for the object when listing. Override to provide custom functionality.
  474. *
  475. * @param xPDOObject $object The current iterated object
  476. * @return array An array of field-value pairs of data
  477. */
  478. protected function prepareListObject(xPDOObject $object) {
  479. return $object->toArray();
  480. }
  481. /**
  482. * Get the criteria for the getObject call for GET/PUT/DELETE requests
  483. * @param mixed $id
  484. * @return array
  485. */
  486. public function getPrimaryKeyCriteria($id) {
  487. return array($this->primaryKeyField => $id);
  488. }
  489. /**
  490. * Abstract method for routing GET requests with a primary key passed. Must be defined in your derivative
  491. * controller.
  492. * @abstract
  493. * @param $id
  494. */
  495. public function read($id) {
  496. if (empty($id)) {
  497. return $this->failure($this->modx->lexicon('rest.err_field_ns',array(
  498. 'field' => $this->primaryKeyField,
  499. )));
  500. }
  501. /** @var xPDOObject $object */
  502. $c = $this->getPrimaryKeyCriteria($id);
  503. $this->object = $this->modx->getObject($this->classKey,$c);
  504. if (empty($this->object)) {
  505. return $this->failure($this->modx->lexicon('rest.err_obj_nf',array(
  506. 'class_key' => $this->classKey,
  507. )));
  508. }
  509. $objectArray = $this->object->toArray();
  510. $afterRead = $this->afterRead($objectArray);
  511. if ($afterRead !== true && $afterRead !== null) {
  512. return $this->failure($afterRead === false ? $this->errorMessage : $afterRead);
  513. }
  514. return $this->success('',$objectArray);
  515. }
  516. /**
  517. * Fires after reading the object. Override to provide custom functionality.
  518. *
  519. * @param array $objectArray A reference to the outputting array
  520. * @return boolean|string Either return true/false or a string message
  521. */
  522. public function afterRead(array &$objectArray) {
  523. return !$this->hasErrors();
  524. }
  525. /**
  526. * Method for routing POST requests. Can be overridden; default behavior automates xPDOObject, class-based requests.
  527. * @abstract
  528. */
  529. public function post() {
  530. $properties = $this->getProperties();
  531. if (!empty($this->postRequiredFields)) {
  532. if (!$this->checkRequiredFields($this->postRequiredFields)) {
  533. return $this->failure($this->modx->lexicon('error'));
  534. }
  535. }
  536. if (!empty($this->postRequiredRelatedObjects)) {
  537. if (!$this->checkRequiredRelatedObjects($this->postRequiredRelatedObjects)) {
  538. return $this->failure();
  539. }
  540. }
  541. /** @var xPDOObject $object */
  542. $this->object = $this->modx->newObject($this->classKey);
  543. $this->object->fromArray($properties);
  544. $beforePost = $this->beforePost();
  545. if ($beforePost !== true && $beforePost !== null) {
  546. return $this->failure($beforePost === false ? $this->errorMessage : $beforePost);
  547. }
  548. if (!$this->object->{$this->postMethod}()) {
  549. $this->setObjectErrors();
  550. if ($this->hasErrors()) {
  551. return $this->failure();
  552. } else {
  553. return $this->failure($this->modx->lexicon('rest.err_class_save',array(
  554. 'class_key' => $this->classKey,
  555. )));
  556. }
  557. }
  558. $objectArray = $this->object->toArray();
  559. $this->afterPost($objectArray);
  560. return $this->success('',$objectArray);
  561. }
  562. /**
  563. * Fires before saving the new object. Override to provide custom functionality.
  564. * @return boolean
  565. */
  566. public function beforePost() {
  567. return !$this->hasErrors();
  568. }
  569. /**
  570. * Fires after saving the new object. Override to provide custom functionality.
  571. *
  572. * @param array $objectArray A reference to the outputting array
  573. */
  574. public function afterPost(array &$objectArray) {}
  575. /**
  576. * Handles updating of objects
  577. * @return array
  578. */
  579. public function put() {
  580. $id = $this->getProperty($this->primaryKeyField,false);
  581. if (empty($id)) {
  582. return $this->failure($this->modx->lexicon('rest.err_field_ns',array(
  583. 'field' => $this->primaryKeyField,
  584. )));
  585. }
  586. $c = $this->getPrimaryKeyCriteria($id);
  587. $this->object = $this->modx->getObject($this->classKey,$c);
  588. if (empty($this->object)) {
  589. return $this->failure($this->modx->lexicon('rest.err_obj_nf',array(
  590. 'class_key' => $this->classKey,
  591. )));
  592. }
  593. if (!empty($this->putRequiredFields)) {
  594. if (!$this->checkRequiredFields($this->putRequiredFields)) {
  595. return $this->failure();
  596. }
  597. }
  598. if (!empty($this->putRequiredRelatedObjects)) {
  599. if (!$this->checkRequiredRelatedObjects($this->putRequiredRelatedObjects)) {
  600. return $this->failure();
  601. }
  602. }
  603. $this->object->fromArray($this->getProperties());
  604. $beforePut = $this->beforePut();
  605. if ($beforePut !== true && $beforePut !== null) {
  606. return $this->failure($beforePut === false ? $this->errorMessage : $beforePut);
  607. }
  608. if (!$this->object->{$this->putMethod}()) {
  609. $this->setObjectErrors();
  610. if ($this->hasErrors()) {
  611. return $this->failure();
  612. } else {
  613. return $this->failure($this->modx->lexicon('rest.err_class_save',array(
  614. 'class_key' => $this->classKey,
  615. )));
  616. }
  617. }
  618. $objectArray = $this->object->toArray();
  619. $this->afterPut($objectArray);
  620. return $this->success('',$objectArray);
  621. }
  622. /**
  623. * Fires before saving an existing object. Override to provide custom functionality.
  624. * @return boolean
  625. */
  626. public function beforePut() {
  627. return !$this->hasErrors();
  628. }
  629. /**
  630. * Fires after saving an existing object. Override to provide custom functionality.
  631. * @param array $objectArray A reference to the outputting array
  632. */
  633. public function afterPut(array &$objectArray) {}
  634. /**
  635. * Handle DELETE requests
  636. * @return array
  637. */
  638. public function delete() {
  639. $id = $this->getProperty($this->primaryKeyField,false);
  640. if (empty($id)) {
  641. return $this->failure($this->modx->lexicon('rest.err_field_ns',array(
  642. 'field' => $this->primaryKeyField,
  643. )));
  644. }
  645. $c = $this->getPrimaryKeyCriteria($id);
  646. $this->object = $this->modx->getObject($this->classKey,$c);
  647. if (empty($this->object)) {
  648. return $this->failure($this->modx->lexicon('rest.err_obj_nf',array(
  649. 'class_key' => $this->classKey,
  650. )));
  651. }
  652. if (!empty($this->deleteRequiredFields)) {
  653. if (!$this->checkRequiredFields($this->deleteRequiredFields)) {
  654. return $this->failure();
  655. }
  656. }
  657. $this->object->fromArray($this->getProperties());
  658. $beforeDelete = $this->beforeDelete();
  659. if ($beforeDelete !== true) {
  660. return $this->failure($beforeDelete === false ? $this->errorMessage : $beforeDelete);
  661. }
  662. if (!$this->object->{$this->deleteMethod}()) {
  663. $this->setObjectErrors();
  664. return $this->failure($this->modx->lexicon('rest.err_class_remove',array(
  665. 'class_key' => $this->classKey,
  666. )));
  667. }
  668. $objectArray = $this->object->toArray();
  669. $this->afterDelete($objectArray);
  670. return $this->success('',$objectArray);
  671. }
  672. /**
  673. * Fires before deleting an existing object. Override to provide custom functionality.
  674. * @return boolean
  675. */
  676. public function beforeDelete() {
  677. return !$this->hasErrors();
  678. }
  679. /**
  680. * Fires after deleting an existing object. Override to provide custom functionality.
  681. *
  682. * @param array $objectArray
  683. */
  684. public function afterDelete(array &$objectArray) {}
  685. /**
  686. * Handle OPTIONS requests
  687. * @return array
  688. */
  689. public function options() {
  690. header('Access-Control-Allow-Methods: ' . implode(', ', $this->allowedMethods));
  691. header('Access-Control-Allow-Headers: ' . implode(', ', $this->allowedHeaders));
  692. $this->responseStatus = 200;
  693. }
  694. /**
  695. * Set object-specific model-layer errors for classes that implement the getErrors/addFieldError methods
  696. */
  697. public function setObjectErrors() {
  698. if (method_exists($this->object,'getErrors')) {
  699. $errors = $this->object->getErrors();
  700. foreach ($errors as $k => $msg) {
  701. $this->addFieldError($k,$msg);
  702. }
  703. }
  704. }
  705. /**
  706. * If an object is set, set the object fields with the passed values
  707. *
  708. * @param object $object
  709. * @param array $values
  710. * @return mixed
  711. */
  712. public function setObjectFields(&$object,array $values = array()) {
  713. foreach ($values as $key => $value) {
  714. if (is_array($value)) {
  715. foreach ($value as $k => $v) {
  716. $object->{$key[$k]} = $v;
  717. }
  718. } else {
  719. $object->{$key} = $value;
  720. }
  721. }
  722. return $object;
  723. }
  724. }