modprocessor.class.php 56 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799
  1. <?php
  2. /*
  3. * This file is part of MODX Revolution.
  4. *
  5. * Copyright (c) MODX, LLC. All Rights Reserved.
  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. * Abstracts a MODX processor, handling its response and error formatting.
  12. *
  13. * {@inheritdoc}
  14. *
  15. * @package modx
  16. */
  17. abstract class modProcessor {
  18. /**
  19. * A reference to the modX object.
  20. * @var modX $modx
  21. */
  22. public $modx = null;
  23. /**
  24. * The absolute path to this processor
  25. * @var string $path
  26. */
  27. public $path = '';
  28. /**
  29. * The array of properties being passed to this processor
  30. * @var array $properties
  31. */
  32. public $properties = array();
  33. /**
  34. * Creates a modProcessor object.
  35. *
  36. * @param modX $modx A reference to the modX instance
  37. * @param array $properties An array of properties
  38. */
  39. function __construct(modX & $modx,array $properties = array()) {
  40. $this->modx =& $modx;
  41. $this->setProperties($properties);
  42. }
  43. /**
  44. * Set the path of the processor
  45. * @param string $path The absolute path
  46. * @return void
  47. */
  48. public function setPath($path) {
  49. $this->path = $path;
  50. }
  51. /**
  52. * Set the runtime properties for the processor
  53. * @param array $properties The properties, in array and key-value form, to run on this processor
  54. * @return void
  55. */
  56. public function setProperties($properties) {
  57. unset($properties['HTTP_MODAUTH']);
  58. $this->properties = array_merge($this->properties,$properties);
  59. }
  60. /**
  61. * Completely unset a property from the properties array
  62. * @param string $key
  63. * @return void
  64. */
  65. public function unsetProperty($key) {
  66. unset($this->properties[$key]);
  67. }
  68. /**
  69. * Return true here to allow access to this processor.
  70. *
  71. * @return boolean
  72. */
  73. public function checkPermissions() { return true; }
  74. /**
  75. * Can be used to provide custom methods prior to processing. Return true to tell MODX that the Processor
  76. * initialized successfully. If you return anything else, MODX will output that return value as an error message.
  77. *
  78. * @return boolean
  79. */
  80. public function initialize() { return true; }
  81. /**
  82. * Load a collection of Language Topics for this processor.
  83. * Override this in your derivative class to provide the array of topics to load.
  84. * @return array
  85. */
  86. public function getLanguageTopics() {
  87. return array();
  88. }
  89. /**
  90. * Return a success message from the processor.
  91. * @param string $msg
  92. * @param mixed $object
  93. * @return array|string
  94. */
  95. public function success($msg = '',$object = null) {
  96. return $this->modx->error->success($msg,$object);
  97. }
  98. /**
  99. * Return a failure message from the processor.
  100. * @param string $msg
  101. * @param mixed $object
  102. * @return array|string
  103. */
  104. public function failure($msg = '',$object = null) {
  105. return $this->modx->error->failure($msg,$object);
  106. }
  107. /**
  108. * Return whether or not the processor has errors
  109. * @return boolean
  110. */
  111. public function hasErrors() {
  112. return $this->modx->error->hasError();
  113. }
  114. /**
  115. * Add an error to the field
  116. * @param string $key
  117. * @param string $message
  118. * @return mixed
  119. */
  120. public function addFieldError($key,$message = '') {
  121. return $this->modx->error->addField($key,$message);
  122. }
  123. /**
  124. * Return the proper instance of the derived class. This can be used to override how MODX loads a processor
  125. * class; for example, when handling derivative classes with class_key settings.
  126. *
  127. * @static
  128. * @param modX $modx A reference to the modX object.
  129. * @param string $className The name of the class that is being requested.
  130. * @param array $properties An array of properties being run with the processor
  131. * @return modProcessor The class specified by $className
  132. */
  133. public static function getInstance(modX &$modx,$className,$properties = array()) {
  134. /** @var modProcessor $processor */
  135. $processor = new $className($modx,$properties);
  136. return $processor;
  137. }
  138. /**
  139. * Run the processor and return the result. Override this in your derivative class to provide custom functionality.
  140. * Used here for pre-2.2-style processors.
  141. *
  142. * @return mixed
  143. */
  144. abstract public function process();
  145. /**
  146. * Run the processor, returning a modProcessorResponse object.
  147. * @return modProcessorResponse
  148. */
  149. public function run() {
  150. if (!$this->checkPermissions()) {
  151. $o = $this->failure($this->modx->lexicon('permission_denied'));
  152. } else {
  153. $topics = $this->getLanguageTopics();
  154. foreach ($topics as $topic) {
  155. $this->modx->lexicon->load($topic);
  156. }
  157. $initialized = $this->initialize();
  158. if ($initialized !== true) {
  159. $o = $this->failure($initialized);
  160. } else {
  161. $o = $this->process();
  162. }
  163. }
  164. $response = new modProcessorResponse($this->modx,$o);
  165. return $response;
  166. }
  167. /**
  168. * Get a specific property.
  169. * @param string $k
  170. * @param mixed $default
  171. * @return mixed
  172. */
  173. public function getProperty($k,$default = null) {
  174. return array_key_exists($k,$this->properties) ? $this->properties[$k] : $default;
  175. }
  176. /**
  177. * Set a property value
  178. *
  179. * @param string $k
  180. * @param mixed $v
  181. * @return void
  182. */
  183. public function setProperty($k,$v) {
  184. $this->properties[$k] = $v;
  185. }
  186. /**
  187. * Special helper method for handling checkboxes. Only set value if passed or $force is true, and check for a
  188. * not empty value or string 'false'.
  189. *
  190. * @param string $k
  191. * @param boolean $force
  192. * @return int|null
  193. */
  194. public function setCheckbox($k,$force = false) {
  195. $v = null;
  196. if ($force || isset($this->properties[$k])) {
  197. $v = empty($this->properties[$k]) || $this->properties[$k] === 'false' ? 0 : 1;
  198. $this->setProperty($k,$v);
  199. }
  200. return $v;
  201. }
  202. /**
  203. * Get an array of properties for this processor
  204. * @return array
  205. */
  206. public function getProperties() {
  207. return $this->properties;
  208. }
  209. /**
  210. * Sets default properties that only are set if they don't already exist in the request
  211. *
  212. * @param array $properties
  213. * @return array The newly merged properties array
  214. */
  215. public function setDefaultProperties(array $properties = array()) {
  216. $this->properties = array_merge($properties,$this->properties);
  217. return $this->properties;
  218. }
  219. /**
  220. * Return arrays of objects (with count) converted to JSON.
  221. *
  222. * The JSON result includes two main elements, total and results. This format is used for list
  223. * results.
  224. *
  225. * @access public
  226. * @param array $array An array of data objects.
  227. * @param mixed $count The total number of objects. Used for pagination.
  228. * @return string The JSON output.
  229. */
  230. public function outputArray(array $array,$count = false) {
  231. if ($count === false) { $count = count($array); }
  232. $output = json_encode(array(
  233. 'success' => true,
  234. 'total' => $count,
  235. 'results' => $array
  236. ));
  237. if ($output === false) {
  238. $this->modx->log(modX::LOG_LEVEL_ERROR, 'Processor failed creating output array due to JSON error '.json_last_error());
  239. return json_encode(array('success' => false));
  240. }
  241. return $output;
  242. }
  243. /**
  244. * Converts PHP to JSON with JavaScript literals left in-tact.
  245. *
  246. * JSON does not allow JavaScript literals, but this function encodes certain identifiable
  247. * literals and decodes them back into literals after modX::toJSON() formats the data.
  248. *
  249. * @access public
  250. * @param mixed $data The PHP data to be converted.
  251. * @return string The extended JSON-encoded string.
  252. */
  253. public function toJSON($data) {
  254. if (is_array($data)) {
  255. array_walk_recursive($data, array(&$this, '_encodeLiterals'));
  256. }
  257. return $this->_decodeLiterals($this->modx->toJSON($data));
  258. }
  259. /**
  260. * Encodes certain JavaScript literal strings for later decoding.
  261. *
  262. * @access protected
  263. * @param mixed &$value A reference to the value to be encoded if it is identified as a literal.
  264. * @param integer|string $key The array key corresponding to the value.
  265. */
  266. protected function _encodeLiterals(&$value, $key) {
  267. if (is_string($value)) {
  268. /* properly handle common literal structures */
  269. if (strpos($value, 'function(') === 0
  270. || strpos($value, 'new Function(') === 0
  271. || strpos($value, 'Ext.') === 0) {
  272. $value = '@@' . base64_encode($value) . '@@';
  273. }
  274. }
  275. }
  276. /**
  277. * Decodes strings encoded by _encodeLiterals to restore JavaScript literals.
  278. *
  279. * @access protected
  280. * @param string $string The JSON-encoded string with encoded literals.
  281. * @return string The JSON-encoded string with literals restored.
  282. */
  283. protected function _decodeLiterals($string) {
  284. $pattern = '/"@@(.*?)@@"/';
  285. $string = preg_replace_callback(
  286. $pattern,
  287. function ($matches) { return base64_decode($matches[1]); },
  288. $string
  289. );
  290. return $string;
  291. }
  292. /**
  293. * Processes a response from a Plugin Event invocation
  294. *
  295. * @param array|string $response The response generated by the invokeEvent call
  296. * @param string $separator The separator for each event response
  297. * @return string The processed response.
  298. */
  299. public function processEventResponse($response,$separator = "\n") {
  300. if (is_array($response)) {
  301. $result = false;
  302. foreach ($response as $msg) {
  303. if (!empty($msg)) {
  304. $result[] = $msg;
  305. }
  306. }
  307. if ($result) {
  308. $result = implode($separator, $result);
  309. }
  310. } else {
  311. $result = $response;
  312. }
  313. return $result;
  314. }
  315. }
  316. /**
  317. * A utility class for pre-2.2-style, or flat file, processors.
  318. *
  319. * @package modx
  320. */
  321. class modDeprecatedProcessor extends modProcessor {
  322. /**
  323. * Rather than load a class for processing, include the processor file directly.
  324. *
  325. * {@inheritDoc}
  326. * @return mixed
  327. */
  328. public function process() {
  329. $info = 'Flat file processor support, used for action ' . $this->getProperty('action', 'unknown action') . ' with path ' . $this->path . ',';
  330. $this->modx->deprecated('2.7.0', '', $info);
  331. $modx =& $this->modx;
  332. $scriptProperties = $this->getProperties();
  333. $o = include $this->path;
  334. return $o;
  335. }
  336. }
  337. /**
  338. * A utility class used for defining driver-specific processors
  339. *
  340. * @package modx
  341. */
  342. abstract class modDriverSpecificProcessor extends modProcessor {
  343. public static function getInstance(modX &$modx,$className,$properties = array()) {
  344. $className .= '_'.$modx->getOption('dbtype');
  345. /** @var modProcessor $processor */
  346. $processor = new $className($modx,$properties);
  347. return $processor;
  348. }
  349. }
  350. /**
  351. * Base class for object-specific processors
  352. * @abstract
  353. */
  354. abstract class modObjectProcessor extends modProcessor {
  355. /** @var xPDOObject|modAccessibleObject $object The object being grabbed */
  356. public $object;
  357. /** @var string $objectType The object "type", this will be used in various lexicon error strings */
  358. public $objectType = 'object';
  359. /** @var string $classKey The class key of the Object to iterate */
  360. public $classKey;
  361. /** @var string $primaryKeyField The primary key field to grab the object by */
  362. public $primaryKeyField = 'id';
  363. /** @var string $permission The Permission to use when checking against */
  364. public $permission = '';
  365. /** @var array $languageTopics An array of language topics to load */
  366. public $languageTopics = array();
  367. public function checkPermissions() {
  368. return !empty($this->permission) ? $this->modx->hasPermission($this->permission) : true;
  369. }
  370. public function getLanguageTopics() {
  371. return $this->languageTopics;
  372. }
  373. }
  374. /**
  375. * A utility abstract class for defining get-based processors
  376. * @abstract
  377. */
  378. abstract class modObjectGetProcessor extends modObjectProcessor {
  379. /** @var boolean $checkViewPermission If set to true, will check the view permission on modAccessibleObjects */
  380. public $checkViewPermission = true;
  381. /**
  382. * {@inheritDoc}
  383. * @return boolean
  384. */
  385. public function initialize() {
  386. $primaryKey = $this->getProperty($this->primaryKeyField,false);
  387. if (empty($primaryKey)) return $this->modx->lexicon($this->objectType.'_err_ns');
  388. $this->object = $this->modx->getObject($this->classKey,$primaryKey);
  389. if (empty($this->object)) return $this->modx->lexicon($this->objectType.'_err_nfs',array($this->primaryKeyField => $primaryKey));
  390. if ($this->checkViewPermission && $this->object instanceof modAccessibleObject && !$this->object->checkPolicy('view')) {
  391. return $this->modx->lexicon('access_denied');
  392. }
  393. return parent::initialize();
  394. }
  395. /**
  396. * {@inheritDoc}
  397. * @return mixed
  398. */
  399. public function process() {
  400. $this->beforeOutput();
  401. return $this->cleanup();
  402. }
  403. /**
  404. * Return the response
  405. * @return array
  406. */
  407. public function cleanup() {
  408. return $this->success('',$this->object->toArray());
  409. }
  410. /**
  411. * Used for adding custom data in derivative types
  412. * @return void
  413. */
  414. public function beforeOutput() { }
  415. }
  416. /**
  417. * A utility abstract class for defining getlist-based processors
  418. * @abstract
  419. */
  420. abstract class modObjectGetListProcessor extends modObjectProcessor {
  421. /** @var string $defaultSortField The default field to sort by */
  422. public $defaultSortField = 'name';
  423. /** @var string $defaultSortDirection The default direction to sort */
  424. public $defaultSortDirection = 'ASC';
  425. /** @var boolean $checkListPermission If true and object is a modAccessibleObject, will check list permission */
  426. public $checkListPermission = true;
  427. /** @var int $currentIndex The current index of successful iteration */
  428. public $currentIndex = 0;
  429. /**
  430. * {@inheritDoc}
  431. * @return boolean
  432. */
  433. public function initialize() {
  434. $this->setDefaultProperties(array(
  435. 'start' => 0,
  436. 'limit' => 20,
  437. 'sort' => $this->defaultSortField,
  438. 'dir' => $this->defaultSortDirection,
  439. 'combo' => false,
  440. 'query' => '',
  441. ));
  442. return parent::initialize();
  443. }
  444. /**
  445. * {@inheritDoc}
  446. * @return mixed
  447. */
  448. public function process() {
  449. $beforeQuery = $this->beforeQuery();
  450. if ($beforeQuery !== true) {
  451. return $this->failure($beforeQuery);
  452. }
  453. $data = $this->getData();
  454. $list = $this->iterate($data);
  455. return $this->outputArray($list,$data['total']);
  456. }
  457. /**
  458. * Allow stoppage of process before the query
  459. * @return boolean
  460. */
  461. public function beforeQuery() {
  462. return true;
  463. }
  464. /**
  465. * Iterate across the data
  466. *
  467. * @param array $data
  468. * @return array
  469. */
  470. public function iterate(array $data) {
  471. $list = array();
  472. $list = $this->beforeIteration($list);
  473. $this->currentIndex = 0;
  474. /** @var xPDOObject|modAccessibleObject $object */
  475. foreach ($data['results'] as $object) {
  476. if ($this->checkListPermission && $object instanceof modAccessibleObject && !$object->checkPolicy('list')) continue;
  477. $objectArray = $this->prepareRow($object);
  478. if (!empty($objectArray) && is_array($objectArray)) {
  479. $list[] = $objectArray;
  480. $this->currentIndex++;
  481. }
  482. }
  483. $list = $this->afterIteration($list);
  484. return $list;
  485. }
  486. /**
  487. * Can be used to insert a row before iteration
  488. * @param array $list
  489. * @return array
  490. */
  491. public function beforeIteration(array $list) {
  492. return $list;
  493. }
  494. /**
  495. * Can be used to insert a row after iteration
  496. * @param array $list
  497. * @return array
  498. */
  499. public function afterIteration(array $list) {
  500. return $list;
  501. }
  502. /**
  503. * Get the data of the query
  504. * @return array
  505. */
  506. public function getData() {
  507. $data = array();
  508. $limit = intval($this->getProperty('limit'));
  509. $start = intval($this->getProperty('start'));
  510. /* query for chunks */
  511. $c = $this->modx->newQuery($this->classKey);
  512. $c = $this->prepareQueryBeforeCount($c);
  513. $data['total'] = $this->modx->getCount($this->classKey,$c);
  514. $c = $this->prepareQueryAfterCount($c);
  515. $sortClassKey = $this->getSortClassKey();
  516. $sortKey = $this->modx->getSelectColumns($sortClassKey,$this->getProperty('sortAlias',$sortClassKey),'',array($this->getProperty('sort')));
  517. if (empty($sortKey)) $sortKey = $this->getProperty('sort');
  518. $c->sortby($sortKey,$this->getProperty('dir'));
  519. if ($limit > 0) {
  520. $c->limit($limit,$start);
  521. }
  522. $data['results'] = $this->modx->getCollection($this->classKey,$c);
  523. return $data;
  524. }
  525. /**
  526. * Can be used to provide a custom sorting class key for the default sorting columns
  527. * @return string
  528. */
  529. public function getSortClassKey() {
  530. return $this->classKey;
  531. }
  532. /**
  533. * Can be used to adjust the query prior to the COUNT statement
  534. *
  535. * @param xPDOQuery $c
  536. * @return xPDOQuery
  537. */
  538. public function prepareQueryBeforeCount(xPDOQuery $c) {
  539. return $c;
  540. }
  541. /**
  542. * Can be used to prepare the query after the COUNT statement
  543. *
  544. * @param xPDOQuery $c
  545. * @return xPDOQuery
  546. */
  547. public function prepareQueryAfterCount(xPDOQuery $c) {
  548. return $c;
  549. }
  550. /**
  551. * Prepare the row for iteration
  552. * @param xPDOObject $object
  553. * @return array
  554. */
  555. public function prepareRow(xPDOObject $object) {
  556. return $object->toArray();
  557. }
  558. }
  559. /**
  560. * A utility abstract class for defining create-based processors
  561. * @abstract
  562. */
  563. abstract class modObjectCreateProcessor extends modObjectProcessor {
  564. /** @var string $beforeSaveEvent The name of the event to fire before saving */
  565. public $beforeSaveEvent = '';
  566. /** @var string $afterSaveEvent The name of the event to fire after saving */
  567. public $afterSaveEvent = '';
  568. /**
  569. * {@inheritDoc}
  570. * @return boolean
  571. */
  572. public function initialize() {
  573. $this->object = $this->modx->newObject($this->classKey);
  574. return parent::initialize();
  575. }
  576. /**
  577. * Process the Object create processor
  578. * {@inheritDoc}
  579. * @return mixed
  580. */
  581. public function process() {
  582. /* Run the beforeSet method before setting the fields, and allow stoppage */
  583. $canSave = $this->beforeSet();
  584. if ($canSave !== true) {
  585. return $this->failure($canSave);
  586. }
  587. $this->object->fromArray($this->getProperties());
  588. /* run the before save logic */
  589. $canSave = $this->beforeSave();
  590. if ($canSave !== true) {
  591. return $this->failure($canSave);
  592. }
  593. /* run object validation */
  594. if (!$this->object->validate()) {
  595. /** @var modValidator $validator */
  596. $validator = $this->object->getValidator();
  597. if ($validator->hasMessages()) {
  598. foreach ($validator->getMessages() as $message) {
  599. $this->addFieldError($message['field'],$this->modx->lexicon($message['message']));
  600. }
  601. }
  602. }
  603. $preventSave = $this->fireBeforeSaveEvent();
  604. if (!empty($preventSave)) {
  605. return $this->failure($preventSave);
  606. }
  607. /* save element */
  608. if ($this->saveObject() == false) {
  609. $this->modx->error->checkValidation($this->object);
  610. return $this->failure($this->modx->lexicon($this->objectType.'_err_save'));
  611. }
  612. $this->afterSave();
  613. $this->fireAfterSaveEvent();
  614. $this->logManagerAction();
  615. return $this->cleanup();
  616. }
  617. /**
  618. * Abstract the saving of the object out to allow for transient and non-persistent object updating in derivative
  619. * classes
  620. * @return boolean
  621. */
  622. public function saveObject() {
  623. return $this->object->save();
  624. }
  625. /**
  626. * Return the success message
  627. * @return array
  628. */
  629. public function cleanup() {
  630. return $this->success('',$this->object);
  631. }
  632. /**
  633. * Override in your derivative class to do functionality before the fields are set on the object
  634. * @return boolean
  635. */
  636. public function beforeSet() { return !$this->hasErrors(); }
  637. /**
  638. * Override in your derivative class to do functionality before save() is run
  639. * @return boolean
  640. */
  641. public function beforeSave() { return !$this->hasErrors(); }
  642. /**
  643. * Override in your derivative class to do functionality after save() is run
  644. * @return boolean
  645. */
  646. public function afterSave() { return true; }
  647. /**
  648. * Fire before save event. Return true to prevent saving.
  649. * @return boolean
  650. */
  651. public function fireBeforeSaveEvent() {
  652. $preventSave = false;
  653. if (!empty($this->beforeSaveEvent)) {
  654. /** @var boolean|array $OnBeforeFormSave */
  655. $OnBeforeFormSave = $this->modx->invokeEvent($this->beforeSaveEvent,array(
  656. 'mode' => modSystemEvent::MODE_NEW,
  657. 'data' => $this->object->toArray(),
  658. $this->primaryKeyField => 0,
  659. $this->objectType => &$this->object,
  660. 'object' => &$this->object,
  661. ));
  662. if (is_array($OnBeforeFormSave)) {
  663. $preventSave = false;
  664. foreach ($OnBeforeFormSave as $msg) {
  665. if (!empty($msg)) {
  666. $preventSave .= $msg."\n";
  667. }
  668. }
  669. } else {
  670. $preventSave = $OnBeforeFormSave;
  671. }
  672. }
  673. return $preventSave;
  674. }
  675. /**
  676. * Fire the after save event
  677. * @return void
  678. */
  679. public function fireAfterSaveEvent() {
  680. if (!empty($this->afterSaveEvent)) {
  681. $this->modx->invokeEvent($this->afterSaveEvent,array(
  682. 'mode' => modSystemEvent::MODE_NEW,
  683. $this->primaryKeyField => $this->object->get($this->primaryKeyField),
  684. $this->objectType => &$this->object,
  685. 'object' => &$this->object,
  686. ));
  687. }
  688. }
  689. /**
  690. * @param array $criteria
  691. * @return int
  692. */
  693. public function doesAlreadyExist(array $criteria) {
  694. return $this->modx->getCount($this->classKey,$criteria);
  695. }
  696. /**
  697. * Log the removal manager action
  698. * @return void
  699. */
  700. public function logManagerAction() {
  701. $this->modx->logManagerAction($this->objectType.'_create',$this->classKey,$this->object->get($this->primaryKeyField));
  702. }
  703. }
  704. /**
  705. * A utility abstract class for defining update-based processors
  706. * @abstract
  707. */
  708. abstract class modObjectUpdateProcessor extends modObjectProcessor {
  709. public $checkSavePermission = true;
  710. /** @var string $beforeSaveEvent The name of the event to fire before saving */
  711. public $beforeSaveEvent = '';
  712. /** @var string $afterSaveEvent The name of the event to fire after saving */
  713. public $afterSaveEvent = '';
  714. public function initialize() {
  715. $primaryKey = $this->getProperty($this->primaryKeyField,false);
  716. if (empty($primaryKey)) return $this->modx->lexicon($this->objectType.'_err_ns');
  717. $this->object = $this->modx->getObject($this->classKey,$primaryKey);
  718. if (empty($this->object)) return $this->modx->lexicon($this->objectType.'_err_nfs',array($this->primaryKeyField => $primaryKey));
  719. if ($this->checkSavePermission && $this->object instanceof modAccessibleObject && !$this->object->checkPolicy('save')) {
  720. return $this->modx->lexicon('access_denied');
  721. }
  722. return parent::initialize();
  723. }
  724. /**
  725. * {@inheritDoc}
  726. * @return mixed
  727. */
  728. public function process() {
  729. /* Run the beforeSet method before setting the fields, and allow stoppage */
  730. $canSave = $this->beforeSet();
  731. if ($canSave !== true) {
  732. return $this->failure($canSave);
  733. }
  734. $this->object->fromArray($this->getProperties());
  735. /* Run the beforeSave method and allow stoppage */
  736. $canSave = $this->beforeSave();
  737. if ($canSave !== true) {
  738. return $this->failure($canSave);
  739. }
  740. /* run object validation */
  741. if (!$this->object->validate()) {
  742. /** @var modValidator $validator */
  743. $validator = $this->object->getValidator();
  744. if ($validator->hasMessages()) {
  745. foreach ($validator->getMessages() as $message) {
  746. $this->addFieldError($message['field'],$this->modx->lexicon($message['message']));
  747. }
  748. }
  749. }
  750. /* run the before save event and allow stoppage */
  751. $preventSave = $this->fireBeforeSaveEvent();
  752. if (!empty($preventSave)) {
  753. return $this->failure($preventSave);
  754. }
  755. if ($this->saveObject() == false) {
  756. return $this->failure($this->modx->lexicon($this->objectType.'_err_save'));
  757. }
  758. $this->afterSave();
  759. $this->fireAfterSaveEvent();
  760. $this->logManagerAction();
  761. return $this->cleanup();
  762. }
  763. /**
  764. * Abstract the saving of the object out to allow for transient and non-persistent object updating in derivative
  765. * classes
  766. * @return boolean
  767. */
  768. public function saveObject() {
  769. return $this->object->save();
  770. }
  771. /**
  772. * Override in your derivative class to do functionality before the fields are set on the object
  773. * @return boolean
  774. */
  775. public function beforeSet() { return !$this->hasErrors(); }
  776. /**
  777. * Override in your derivative class to do functionality before save() is run
  778. * @return boolean
  779. */
  780. public function beforeSave() { return !$this->hasErrors(); }
  781. /**
  782. * Override in your derivative class to do functionality after save() is run
  783. * @return boolean
  784. */
  785. public function afterSave() { return true; }
  786. /**
  787. * Return the success message
  788. * @return array
  789. */
  790. public function cleanup() {
  791. return $this->success('',$this->object);
  792. }
  793. /**
  794. * Fire before save event. Return true to prevent saving.
  795. * @return boolean
  796. */
  797. public function fireBeforeSaveEvent() {
  798. $preventSave = false;
  799. if (!empty($this->beforeSaveEvent)) {
  800. /** @var boolean|array $OnBeforeFormSave */
  801. $OnBeforeFormSave = $this->modx->invokeEvent($this->beforeSaveEvent,array(
  802. 'mode' => modSystemEvent::MODE_UPD,
  803. 'data' => $this->object->toArray(),
  804. $this->primaryKeyField => $this->object->get($this->primaryKeyField),
  805. $this->objectType => &$this->object,
  806. 'object' => &$this->object,
  807. ));
  808. if (is_array($OnBeforeFormSave)) {
  809. $preventSave = false;
  810. foreach ($OnBeforeFormSave as $msg) {
  811. if (!empty($msg)) {
  812. $preventSave .= $msg."\n";
  813. }
  814. }
  815. } else {
  816. $preventSave = $OnBeforeFormSave;
  817. }
  818. }
  819. return $preventSave;
  820. }
  821. /**
  822. * Fire the after save event
  823. * @return void
  824. */
  825. public function fireAfterSaveEvent() {
  826. if (!empty($this->afterSaveEvent)) {
  827. $this->modx->invokeEvent($this->afterSaveEvent,array(
  828. 'mode' => modSystemEvent::MODE_UPD,
  829. $this->primaryKeyField => $this->object->get($this->primaryKeyField),
  830. $this->objectType => &$this->object,
  831. 'object' => &$this->object,
  832. ));
  833. }
  834. }
  835. /**
  836. * Log the removal manager action
  837. * @return void
  838. */
  839. public function logManagerAction() {
  840. $this->modx->logManagerAction($this->objectType.'_update',$this->classKey,$this->object->get($this->primaryKeyField));
  841. }
  842. /**
  843. * @param array $criteria
  844. * @return int
  845. */
  846. public function doesAlreadyExist(array $criteria) {
  847. return $this->modx->getCount($this->classKey,$criteria);
  848. }
  849. }
  850. /**
  851. * A utility abstract class for defining duplicate-based processors
  852. * @abstract
  853. */
  854. class modObjectDuplicateProcessor extends modObjectProcessor {
  855. /** @var boolean $checkSavePermission Whether or not to check the save permission on modAccessibleObjects */
  856. public $checkSavePermission = true;
  857. /** @var xPDOObject $newObject The newly duplicated object */
  858. public $newObject;
  859. public $nameField = 'name';
  860. public $staticfileField = 'static_file';
  861. /** @var string $newNameField The name of field that used for filling new name of object.
  862. * If defined, duplication error will be attached to field with this name
  863. */
  864. public $newNameField;
  865. /**
  866. * {@inheritDoc}
  867. * @return boolean
  868. */
  869. public function initialize() {
  870. $primaryKey = $this->getProperty($this->primaryKeyField,false);
  871. if (empty($primaryKey)) return $this->modx->lexicon($this->objectType.'_err_ns');
  872. $this->object = $this->modx->getObject($this->classKey,$primaryKey);
  873. if (empty($this->object)) return $this->modx->lexicon($this->objectType.'_err_nfs',array($this->primaryKeyField => $primaryKey));
  874. if ($this->checkSavePermission && $this->object instanceof modAccessibleObject && !$this->object->checkPolicy('save')) {
  875. return $this->modx->lexicon('access_denied');
  876. }
  877. $this->newObject = $this->modx->newObject($this->classKey);
  878. return parent::initialize();
  879. }
  880. /**
  881. * {@inheritDoc}
  882. * @return mixed
  883. */
  884. public function process() {
  885. /* Run the beforeSet method before setting the fields, and allow stoppage */
  886. $canSave = $this->beforeSet();
  887. if ($canSave !== true) {
  888. return $this->failure($canSave);
  889. }
  890. $this->newObject->fromArray($this->object->toArray());
  891. $name = $this->getNewName();
  892. $this->setNewName($name);
  893. $staticFilename = $this->getProperty($this->staticfileField);
  894. if (!empty($staticFilename)) {
  895. $this->newObject->set('static_file', $staticFilename);
  896. }
  897. if ($this->alreadyExists($name)) {
  898. $this->addFieldError(
  899. $this->newNameField ? $this->newNameField : $this->nameField,
  900. $this->modx->lexicon($this->objectType.'_err_ae',array('name' => $name))
  901. );
  902. }
  903. /* Check if a static file already exists within specified static file path. */
  904. if ($staticFilename && $this->staticFileAlreadyExists($staticFilename)) {
  905. $this->modx->lexicon->load('core:element');
  906. $this->addFieldError($this->staticfileField, $this->modx->lexicon('element_err_staticfile_exists'));
  907. }
  908. $canSave = $this->beforeSave();
  909. if ($canSave !== true) {
  910. return $this->failure($canSave);
  911. }
  912. /* save new object */
  913. if ($this->saveObject() === false) {
  914. $this->modx->error->checkValidation($this->newObject);
  915. return $this->failure($this->modx->lexicon($this->objectType.'_err_duplicate'));
  916. }
  917. $this->afterSave();
  918. $this->logManagerAction();
  919. return $this->cleanup();
  920. }
  921. /**
  922. * Abstract the saving of the object out to allow for transient and non-persistent object updating in derivative
  923. * classes
  924. * @return boolean
  925. */
  926. public function saveObject() {
  927. return $this->newObject->save();
  928. }
  929. /**
  930. * Cleanup and return a response.
  931. *
  932. * @return array
  933. */
  934. public function cleanup() {
  935. return $this->success('',$this->newObject);
  936. }
  937. /**
  938. * Override in your derivative class to do functionality before the fields are set on the object
  939. * @return boolean
  940. */
  941. public function beforeSet() { return !$this->hasErrors(); }
  942. /**
  943. * Run any logic before the object has been duplicated. May return false to prevent duplication.
  944. * @return boolean
  945. */
  946. public function beforeSave() { return !$this->hasErrors(); }
  947. /**
  948. * Run any logic after the object has been duplicated
  949. * @return boolean
  950. */
  951. public function afterSave() { return true; }
  952. /**
  953. * Get the new name for the duplicate
  954. * @return string
  955. */
  956. public function getNewName() {
  957. $name = $this->getProperty($this->nameField);
  958. $newName = !empty($name) ? $name : $this->modx->lexicon('duplicate_of',array('name' => $this->object->get($this->nameField)));
  959. return $newName;
  960. }
  961. /**
  962. * Set the new name to the new object
  963. * @param string $name
  964. * @return string
  965. */
  966. public function setNewName($name) {
  967. return $this->newObject->set($this->nameField,$name);
  968. }
  969. /**
  970. * Check to see if an object already exists with that name
  971. * @param string $name
  972. * @return boolean
  973. */
  974. public function alreadyExists($name) {
  975. return $this->modx->getCount($this->classKey,array(
  976. $this->nameField => $name,
  977. )) > 0;
  978. }
  979. /**
  980. * Check to see if a static element file already exists.
  981. * @param $filename
  982. * @return bool
  983. */
  984. public function staticFileAlreadyExists($filename) {
  985. $sourceId = $this->getProperty('source');
  986. if ($sourceId > 0) {
  987. $source = $this->modx->getObject('sources.modFileMediaSource', array('id' => $sourceId));
  988. if ($source && $source->get('is_stream')) {
  989. $source->initialize();
  990. $filename = $source->getBasePath() . $filename;
  991. }
  992. }
  993. return file_exists($filename);
  994. }
  995. /**
  996. * Log a manager action
  997. * @return void
  998. */
  999. public function logManagerAction() {
  1000. $this->modx->logManagerAction($this->objectType.'_duplicate',$this->classKey,$this->newObject->get('id'));
  1001. }
  1002. }
  1003. /**
  1004. * A utility abstract class for defining remove-based processors
  1005. * @abstract
  1006. */
  1007. abstract class modObjectRemoveProcessor extends modObjectProcessor {
  1008. /** @var boolean $checkRemovePermission If set to true, will check the remove permission on modAccessibleObjects */
  1009. public $checkRemovePermission = true;
  1010. /** @var string $beforeRemoveEvent The name of the event to fire before removal */
  1011. public $beforeRemoveEvent = '';
  1012. /** @var string $afterRemoveEvent The name of the event to fire after removal */
  1013. public $afterRemoveEvent = '';
  1014. public function initialize() {
  1015. $primaryKey = $this->getProperty($this->primaryKeyField,false);
  1016. if (empty($primaryKey)) return $this->modx->lexicon($this->objectType.'_err_ns');
  1017. $this->object = $this->modx->getObject($this->classKey,$primaryKey);
  1018. if (empty($this->object)) return $this->modx->lexicon($this->objectType.'_err_nfs',array($this->primaryKeyField => $primaryKey));
  1019. if ($this->checkRemovePermission && $this->object instanceof modAccessibleObject && !$this->object->checkPolicy('remove')) {
  1020. return $this->modx->lexicon('access_denied');
  1021. }
  1022. return parent::initialize();
  1023. }
  1024. public function process() {
  1025. $canRemove = $this->beforeRemove();
  1026. if ($canRemove !== true) {
  1027. return $this->failure($canRemove);
  1028. }
  1029. $preventRemoval = $this->fireBeforeRemoveEvent();
  1030. if (!empty($preventRemoval)) {
  1031. return $this->failure($preventRemoval);
  1032. }
  1033. if ($this->removeObject() == false) {
  1034. return $this->failure($this->modx->lexicon($this->objectType.'_err_remove'));
  1035. }
  1036. $this->afterRemove();
  1037. $this->fireAfterRemoveEvent();
  1038. $this->logManagerAction();
  1039. $this->cleanup();
  1040. return $this->success('',array($this->primaryKeyField => $this->object->get($this->primaryKeyField)));
  1041. }
  1042. /**
  1043. * Abstract the removing of the object out to allow for transient and non-persistent object updating in derivative
  1044. * classes
  1045. * @return boolean
  1046. */
  1047. public function removeObject() {
  1048. return $this->object->remove();
  1049. }
  1050. /**
  1051. * Can contain pre-removal logic; return false to prevent remove.
  1052. * @return boolean
  1053. */
  1054. public function beforeRemove() { return !$this->hasErrors(); }
  1055. /**
  1056. * Can contain post-removal logic.
  1057. * @return bool
  1058. */
  1059. public function afterRemove() { return true; }
  1060. /**
  1061. * Log the removal manager action
  1062. * @return void
  1063. */
  1064. public function logManagerAction() {
  1065. $this->modx->logManagerAction($this->objectType.'_delete',$this->classKey,$this->object->get($this->primaryKeyField));
  1066. }
  1067. /**
  1068. * After removal, manager action log, and event firing logic
  1069. * @return void
  1070. */
  1071. public function cleanup() {}
  1072. /**
  1073. * If specified, fire the before remove event
  1074. * @return boolean Return false to allow removal; non-empty to prevent it
  1075. */
  1076. public function fireBeforeRemoveEvent() {
  1077. $preventRemove = false;
  1078. if (!empty($this->beforeRemoveEvent)) {
  1079. $response = $this->modx->invokeEvent($this->beforeRemoveEvent,array(
  1080. $this->primaryKeyField => $this->object->get($this->primaryKeyField),
  1081. $this->objectType => &$this->object,
  1082. 'object' => &$this->object,
  1083. ));
  1084. $preventRemove = $this->processEventResponse($response);
  1085. }
  1086. return $preventRemove;
  1087. }
  1088. /**
  1089. * If specified, fire the after remove event
  1090. * @return void
  1091. */
  1092. public function fireAfterRemoveEvent() {
  1093. if (!empty($this->afterRemoveEvent)) {
  1094. $this->modx->invokeEvent($this->afterRemoveEvent,array(
  1095. $this->primaryKeyField => $this->object->get($this->primaryKeyField),
  1096. $this->objectType => &$this->object,
  1097. 'object' => &$this->object,
  1098. ));
  1099. }
  1100. }
  1101. }
  1102. /**
  1103. * A utility abstract class for defining soft remove-based processors
  1104. * @abstract
  1105. */
  1106. abstract class modObjectSoftRemoveProcessor extends modObjectProcessor {
  1107. /** @var boolean $checkRemovePermission If set to true, will check the remove permission on modAccessibleObjects */
  1108. public $checkRemovePermission = true;
  1109. /** @var string $beforeRemoveEvent The name of the event to fire before removal */
  1110. public $beforeRemoveEvent = '';
  1111. /** @var string $afterRemoveEvent The name of the event to fire after removal */
  1112. public $afterRemoveEvent = '';
  1113. /** @var bool $userDeletedOn To use or not deleted on field */
  1114. public $useDeletedOn = true;
  1115. /** @var string $deletedOnField Name of deleted on field */
  1116. public $deletedOnField = 'deletedon';
  1117. /** @var bool $userDeleted To use or not deleted field */
  1118. public $useDeleted = true;
  1119. /** @var string $deletedField Name of deleted field */
  1120. public $deletedField = 'deleted';
  1121. /** @var bool $userDeletedBy To use or not deleted by field */
  1122. public $useDeletedBy = true;
  1123. /** @var string $deletedByField Name of deleted by field */
  1124. public $deletedByField = 'deletedby';
  1125. public function initialize() {
  1126. $primaryKey = $this->getProperty($this->primaryKeyField, false);
  1127. if (empty($primaryKey)) {
  1128. return $this->modx->lexicon($this->objectType . '_err_ns');
  1129. }
  1130. $this->object = $this->modx->getObject($this->classKey, $primaryKey);
  1131. if (empty($this->object)) {
  1132. return $this->modx->lexicon($this->objectType . '_err_nfs', array($this->primaryKeyField => $primaryKey));
  1133. }
  1134. if ($this->checkRemovePermission && $this->object instanceof modAccessibleObject && !$this->object->checkPolicy('remove')) {
  1135. return $this->modx->lexicon('access_denied');
  1136. }
  1137. if (!$this->useDeleted && !$this->useDeletedOn && !$this->useDeletedBy) {
  1138. return $this->modx->lexicon($this->objectType . '_err_dt_ns');
  1139. }
  1140. if ($this->useDeleted && ($this->deletedField == null)) {
  1141. return $this->modx->lexicon($this->objectType . '_err_df_ns');
  1142. }
  1143. if ($this->useDeletedOn && ($this->deletedOnField == null)) {
  1144. return $this->modx->lexicon($this->objectType . '_err_dof_ns');
  1145. }
  1146. if ($this->useDeletedBy && ($this->deletedByField == null)) {
  1147. return $this->modx->lexicon($this->objectType . '_err_dbf_ns');
  1148. }
  1149. return parent::initialize();
  1150. }
  1151. public function process() {
  1152. $canRemove = $this->beforeRemove();
  1153. if ($canRemove !== true) {
  1154. return $this->failure($canRemove);
  1155. }
  1156. $preventRemoval = $this->fireBeforeRemoveEvent();
  1157. if (!empty($preventRemoval)) {
  1158. return $this->failure($preventRemoval);
  1159. }
  1160. if ($this->useDeleted) {
  1161. $this->object->set($this->deletedField, true);
  1162. }
  1163. if ($this->useDeletedOn) {
  1164. $this->object->set($this->deletedOnField, time());
  1165. }
  1166. if ($this->useDeletedBy) {
  1167. $this->object->set($this->deletedByField, $this->modx->user->id);
  1168. }
  1169. if ($this->saveObject() == false) {
  1170. return $this->failure($this->modx->lexicon($this->objectType . '_err_soft_remove'));
  1171. }
  1172. $this->afterRemove();
  1173. $this->fireAfterRemoveEvent();
  1174. $this->logManagerAction();
  1175. $this->cleanup();
  1176. return $this->success('', array($this->primaryKeyField => $this->object->get($this->primaryKeyField)));
  1177. }
  1178. /**
  1179. * Abstract the saving of the object out to allow for transient and non-persistent object updating in derivative
  1180. * classes
  1181. * @return boolean
  1182. */
  1183. public function saveObject() {
  1184. return $this->object->save();
  1185. }
  1186. /**
  1187. * Can contain pre-removal logic; return false to prevent remove.
  1188. * @return boolean
  1189. */
  1190. public function beforeRemove() {
  1191. return !$this->hasErrors();
  1192. }
  1193. /**
  1194. * Can contain post-removal logic.
  1195. * @return bool
  1196. */
  1197. public function afterRemove() {
  1198. return true;
  1199. }
  1200. /**
  1201. * Log the removal manager action
  1202. * @return void
  1203. */
  1204. public function logManagerAction() {
  1205. $this->modx->logManagerAction($this->objectType . '_soft_delete', $this->classKey, $this->object->get($this->primaryKeyField));
  1206. }
  1207. /**
  1208. * After removal, manager action log, and event firing logic
  1209. * @return void
  1210. */
  1211. public function cleanup() {
  1212. }
  1213. /**
  1214. * If specified, fire the before remove event
  1215. * @return boolean Return false to allow removal; non-empty to prevent it
  1216. */
  1217. public function fireBeforeRemoveEvent() {
  1218. $preventRemove = false;
  1219. if (!empty($this->beforeRemoveEvent)) {
  1220. $response = $this->modx->invokeEvent($this->beforeRemoveEvent, array(
  1221. $this->primaryKeyField => $this->object->get($this->primaryKeyField),
  1222. $this->objectType => &$this->object,
  1223. 'object' => &$this->object,
  1224. ));
  1225. $preventRemove = $this->processEventResponse($response);
  1226. }
  1227. return $preventRemove;
  1228. }
  1229. /**
  1230. * If specified, fire the after remove event
  1231. * @return void
  1232. */
  1233. public function fireAfterRemoveEvent() {
  1234. if (!empty($this->afterRemoveEvent)) {
  1235. $this->modx->invokeEvent($this->afterRemoveEvent, array(
  1236. $this->primaryKeyField => $this->object->get($this->primaryKeyField),
  1237. $this->objectType => &$this->object,
  1238. 'object' => &$this->object,
  1239. ));
  1240. }
  1241. }
  1242. }
  1243. /**
  1244. * Utility class for exporting an object
  1245. * @abstract
  1246. */
  1247. abstract class modObjectExportProcessor extends modObjectGetProcessor {
  1248. /** @var string $downloadProperty */
  1249. public $downloadProperty = 'download';
  1250. /** @var string $nameField */
  1251. public $nameField = 'name';
  1252. /** @var XMLWriter $xml */
  1253. public $xml;
  1254. public function cleanup() {
  1255. if (!extension_loaded('XMLWriter') || !class_exists('XMLWriter')) {
  1256. return $this->failure($this->modx->lexicon('xmlwriter_err_nf'));
  1257. }
  1258. $download = $this->getProperty($this->downloadProperty);
  1259. if (empty($download)) {
  1260. return $this->cache();
  1261. }
  1262. return $this->download();
  1263. }
  1264. /**
  1265. * Cache the data to an export file
  1266. * @return array|string
  1267. */
  1268. public function cache() {
  1269. $this->xml = new XMLWriter();
  1270. $this->xml->openMemory();
  1271. $this->xml->startDocument('1.0','UTF-8');
  1272. $this->xml->setIndent(true);
  1273. $this->xml->setIndentString(' ');
  1274. $this->prepareXml();
  1275. $this->xml->endDocument();
  1276. $data = $this->xml->outputMemory();
  1277. $f = $this->object->get($this->nameField).'.xml';
  1278. $fileName = $this->modx->getOption('core_path',null,MODX_CORE_PATH).'export/'.$this->objectType.'/'.$f;
  1279. /** @var modCacheManager $cacheManager */
  1280. $cacheManager = $this->modx->getCacheManager();
  1281. $cacheManager->writeFile($fileName,$data);
  1282. $this->logManagerAction();
  1283. return $this->success($f);
  1284. }
  1285. /**
  1286. * Must be declared in your derivative class. Used to prepare the data to export.
  1287. * @abstract
  1288. */
  1289. abstract public function prepareXml();
  1290. /**
  1291. * Attempt to download the exported file to the browser
  1292. * @return mixed
  1293. */
  1294. public function download() {
  1295. $fileName = $this->object->get($this->nameField).'.xml';
  1296. $file = $this->modx->getOption('core_path', null, MODX_CORE_PATH) . 'export/' . $this->objectType . '/' . $fileName;
  1297. $this->modx->getService('fileHandler', 'modFileHandler');
  1298. $fileObj = $this->modx->fileHandler->make($file);
  1299. $name = strtolower(str_replace(array(' ','/'),'-',$this->object->get($this->nameField)));
  1300. if (!$fileObj->exists()) return $this->failure($file);
  1301. $o = $fileObj->getContents();
  1302. $fileObj->download(array('filename' => $name . '.' . $this->objectType . '.xml'));
  1303. return $o;
  1304. }
  1305. /**
  1306. * Log the export manager action
  1307. * @return void
  1308. */
  1309. public function logManagerAction() {
  1310. $this->modx->logManagerAction($this->objectType.'_export',$this->classKey,$this->object->get($this->primaryKeyField));
  1311. }
  1312. }
  1313. /**
  1314. * Utility class for importing an object
  1315. * @abstract
  1316. */
  1317. abstract class modObjectImportProcessor extends modObjectProcessor {
  1318. /** @var string $nameField The name, or unique, field for the object */
  1319. public $nameField = 'name';
  1320. /** @var boolean $setName Whether or not to attempt to set the name field */
  1321. public $setName = true;
  1322. /** @var string $fileProperty The property that contains the file data */
  1323. public $fileProperty = 'file';
  1324. /** @var SimpleXMLElement $xml The parsed XML from the file */
  1325. public $xml = '';
  1326. public function initialize() {
  1327. $file = $this->getProperty($this->fileProperty);
  1328. if (empty($file) || !isset($file['tmp_name'])) return $this->modx->lexicon('import_err_upload');
  1329. if ($file['error'] != 0) return $this->modx->lexicon('import_err_upload');
  1330. if (!file_exists($file['tmp_name'])) return $this->modx->lexicon('import_err_upload');
  1331. $this->xml = file_get_contents($file['tmp_name']);
  1332. if (empty($this->xml)) return $this->modx->lexicon('import_err_upload');
  1333. if (!function_exists('simplexml_load_string')) {
  1334. return $this->failure($this->modx->lexicon('simplexml_err_nf'));
  1335. }
  1336. return parent::initialize();
  1337. }
  1338. public function process() {
  1339. /** @var SimpleXmlElement $xml */
  1340. $this->xml = @simplexml_load_string($this->xml);
  1341. if (empty($this->xml)) return $this->failure($this->modx->lexicon('import_err_xml'));
  1342. $this->object = $this->modx->newObject($this->classKey);
  1343. if ($this->setName) {
  1344. $name = (string)$this->xml->name;
  1345. if ($this->alreadyExists($name)) {
  1346. $this->object->set($this->nameField,$this->modx->lexicon('duplicate_of',array('name' => $name)));
  1347. } else {
  1348. $this->object->set($this->nameField,$name);
  1349. }
  1350. }
  1351. $canSave = $this->beforeSave();
  1352. if ($canSave !== true) {
  1353. return $this->failure($canSave);
  1354. }
  1355. if (!$this->object->save()) {
  1356. return $this->failure($this->modx->lexicon($this->objectType.'_err_save'));
  1357. }
  1358. $this->afterSave();
  1359. $this->logManagerAction();
  1360. return $this->success();
  1361. }
  1362. /**
  1363. * Do any before save logic
  1364. * @return boolean
  1365. */
  1366. public function beforeSave() {
  1367. return !$this->hasErrors();
  1368. }
  1369. /**
  1370. * Do any after save logic
  1371. * @return boolean
  1372. */
  1373. public function afterSave() {
  1374. return !$this->hasErrors();
  1375. }
  1376. /**
  1377. * Check to see if the object already exists with this name field
  1378. * @param string $name
  1379. * @return bool
  1380. */
  1381. public function alreadyExists($name) {
  1382. return $this->modx->getCount($this->classKey,array($this->nameField => $name)) > 0;
  1383. }
  1384. /**
  1385. * Log the export manager action
  1386. * @return void
  1387. */
  1388. public function logManagerAction() {
  1389. $this->modx->logManagerAction($this->objectType.'_import',$this->classKey,$this->object->get($this->primaryKeyField));
  1390. }
  1391. }
  1392. /**
  1393. * Response class for Processor executions
  1394. *
  1395. * @package modx
  1396. */
  1397. class modProcessorResponse {
  1398. /**
  1399. * When there is only a general error
  1400. * @const ERROR_GENERAL
  1401. */
  1402. const ERROR_GENERAL = 'error_general';
  1403. /**
  1404. * When there are only field-specific errors
  1405. * @const ERROR_FIELD
  1406. */
  1407. const ERROR_FIELD = 'error_field';
  1408. /**
  1409. * When there is both field-specific and general errors
  1410. * @const ERROR_BOTH
  1411. */
  1412. const ERROR_BOTH = 'error_both';
  1413. /**
  1414. * The field for the error type
  1415. * @const ERROR_TYPE
  1416. */
  1417. const ERROR_TYPE = 'error_type';
  1418. /**
  1419. * @var modX A reference to the modX object
  1420. */
  1421. public $modx = null;
  1422. /**
  1423. * @var array|string A reference to the full response
  1424. */
  1425. public $response = null;
  1426. /**
  1427. * @var array A collection of modProcessorResponseError objects for each field-specific error
  1428. */
  1429. public $errors = array();
  1430. /**
  1431. * @var string The error type for this response
  1432. */
  1433. public $error_type = '';
  1434. /**
  1435. * The constructor for modProcessorResponse
  1436. *
  1437. * @param modX $modx A reference to the modX object.
  1438. * @param array $response The array response from the modX.runProcessor method
  1439. */
  1440. function __construct(modX &$modx,$response = array()) {
  1441. $this->modx =& $modx;
  1442. $this->response = $response;
  1443. if ($this->isError()) {
  1444. if (!empty($response['errors']) && is_array($response['errors'])) {
  1445. foreach ($response['errors'] as $error) {
  1446. $this->errors[] = new modProcessorResponseError($error);
  1447. }
  1448. if (!empty($response['message'])) {
  1449. $this->error_type = modProcessorResponse::ERROR_BOTH;
  1450. } else {
  1451. $this->error_type = modProcessorResponse::ERROR_FIELD;
  1452. }
  1453. } else {
  1454. $this->error_type = modProcessorResponse::ERROR_GENERAL;
  1455. }
  1456. }
  1457. }
  1458. /**
  1459. * Returns the type of error for this response
  1460. * @return string The type of error returned
  1461. */
  1462. public function getErrorType() {
  1463. return $this->error_type;
  1464. }
  1465. /**
  1466. * Checks to see if the response is an error
  1467. * @return boolean True if the response was a success, otherwise false
  1468. */
  1469. public function isError() {
  1470. return empty($this->response) || (is_array($this->response) && (!array_key_exists('success', $this->response) || empty($this->response['success'])));
  1471. }
  1472. /**
  1473. * Returns true if there is a general status message for the response.
  1474. * @return boolean True if there is a general message
  1475. */
  1476. public function hasMessage() {
  1477. return isset($this->response['message']) && !empty($this->response['message']) ? true : false;
  1478. }
  1479. /**
  1480. * Gets the general status message for the response.
  1481. * @return string The status message
  1482. */
  1483. public function getMessage() {
  1484. return isset($this->response['message']) ? $this->response['message'] : '';
  1485. }
  1486. /**
  1487. * Returns the entire response object in array form
  1488. * @return array The array response
  1489. */
  1490. public function getResponse() {
  1491. return $this->response;
  1492. }
  1493. /**
  1494. * Returns true if an object was sent with this response.
  1495. * @return boolean True if an object was sent.
  1496. */
  1497. public function hasObject() {
  1498. return isset($this->response['object']) && !empty($this->response['object']) ? true : false;
  1499. }
  1500. /**
  1501. * Returns the array object, if is sent in the response
  1502. * @return array The object in the response, usually the object being performed on.
  1503. */
  1504. public function getObject() {
  1505. return isset($this->response['object']) ? $this->response['object'] : array();
  1506. }
  1507. /**
  1508. * An array of modProcessorResponseError objects for each field-specific error
  1509. * @return array
  1510. */
  1511. public function getFieldErrors() {
  1512. return $this->errors;
  1513. }
  1514. /**
  1515. * Checks to see if there are any field-specific errors in this response
  1516. * @return boolean True if there were field-specific errors
  1517. */
  1518. public function hasFieldErrors() {
  1519. return !empty($this->errors) ? true : false;
  1520. }
  1521. /**
  1522. * Gets all errors and adds them all into an array.
  1523. *
  1524. * @param string $fieldErrorSeparator The separator to use between fieldkey and message for field-specific errors.
  1525. * @return array An array of all errors.
  1526. */
  1527. public function getAllErrors($fieldErrorSeparator = ': ') {
  1528. $errormsgs = array();
  1529. if ($this->hasMessage()) {
  1530. $errormsgs[] = $this->getMessage();
  1531. }
  1532. if ($this->hasFieldErrors()) {
  1533. $errors = $this->getFieldErrors();
  1534. if (!empty($errors)) {
  1535. foreach ($errors as $error) {
  1536. $errormsgs[] = $error->field.$fieldErrorSeparator.$error->message;
  1537. }
  1538. }
  1539. }
  1540. return $errormsgs;
  1541. }
  1542. }
  1543. /**
  1544. * An abstraction class of field-specific errors for a processor response
  1545. *
  1546. * @package modx
  1547. */
  1548. class modProcessorResponseError {
  1549. /**
  1550. * @var array The error data itself
  1551. */
  1552. public $error = null;
  1553. /**
  1554. * @var string The field key that the error occurred on
  1555. */
  1556. public $field = null;
  1557. /**
  1558. * @var string The message that was sent for the field error
  1559. */
  1560. public $message = '';
  1561. /**
  1562. * The constructor for the modProcessorResponseError class
  1563. *
  1564. * @param array $error An array error response
  1565. */
  1566. function __construct($error = array()) {
  1567. $this->error = $error;
  1568. if (isset($error['id']) && !empty($error['id'])) { $this->field = $error['id']; }
  1569. if (isset($error['msg']) && !empty($error['msg'])) { $this->message = $error['msg']; }
  1570. }
  1571. /**
  1572. * Returns the message for the field-specific error
  1573. * @return string
  1574. */
  1575. public function getMessage() {
  1576. return $this->message;
  1577. }
  1578. /**
  1579. * Returns the field key for the field-specific error
  1580. * @return string
  1581. */
  1582. public function getField() {
  1583. return $this->field;
  1584. }
  1585. /**
  1586. * Returns the array data for the field-specific error
  1587. * @return array
  1588. */
  1589. public function getError() {
  1590. return $this->error;
  1591. }
  1592. }