PageRenderTime 47ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/plugins/Block/src/Model/Table/BlocksTable.php

http://github.com/QuickAppsCMS/QuickApps-CMS
PHP | 374 lines | 218 code | 30 blank | 126 comment | 10 complexity | 444259ed1a943a5f6331006aad364b5f MD5 | raw file
Possible License(s): LGPL-2.1, MPL-2.0-no-copyleft-exception, GPL-3.0
  1. <?php
  2. /**
  3. * Licensed under The GPL-3.0 License
  4. * For full copyright and license information, please see the LICENSE.txt
  5. * Redistributions of files must retain the above copyright notice.
  6. *
  7. * @since 2.0.0
  8. * @author Christopher Castro <chris@quickapps.es>
  9. * @link http://www.quickappscms.org
  10. * @license http://opensource.org/licenses/gpl-3.0.html GPL-3.0 License
  11. */
  12. namespace Block\Model\Table;
  13. use Block\Model\Entity\Block;
  14. use Cake\Cache\Cache;
  15. use Cake\Event\Event;
  16. use Cake\ORM\Table;
  17. use Cake\Validation\Validator;
  18. use CMS\Event\EventDispatcherTrait;
  19. use \ArrayObject;
  20. /**
  21. * Represents "blocks" database table.
  22. *
  23. * @property \User\Model\Table\RolesTable $Roles
  24. * @property \Block\Model\Table\BlocksTable $Blocks
  25. * @property \Block\Model\Table\BlockRegionsTable $BlockRegions
  26. */
  27. class BlocksTable extends Table
  28. {
  29. use EventDispatcherTrait;
  30. /**
  31. * Get the Model callbacks this table is interested in.
  32. *
  33. * @return array
  34. */
  35. public function implementedEvents()
  36. {
  37. $events = parent::implementedEvents();
  38. $events['Blocks.settings.validate'] = 'settingsValidate';
  39. $events['Blocks.settings.defaultValues'] = 'settingsDefaultValues';
  40. return $events;
  41. }
  42. /**
  43. * Initialize method.
  44. *
  45. * @param array $config The configuration for the Table.
  46. * @return void
  47. */
  48. public function initialize(array $config)
  49. {
  50. $this->hasMany('BlockRegions', [
  51. 'className' => 'Block.BlockRegions',
  52. 'foreignKey' => 'block_id',
  53. 'dependent' => true,
  54. 'propertyName' => 'region',
  55. ]);
  56. $this->hasMany('Copies', [
  57. 'className' => 'Block.Blocks',
  58. 'foreignKey' => 'copy_id',
  59. 'dependent' => true,
  60. 'propertyName' => 'copies',
  61. 'cascadeCallbacks' => true,
  62. ]);
  63. $this->belongsToMany('User.Roles', [
  64. 'className' => 'User.Roles',
  65. 'foreignKey' => 'block_id',
  66. 'joinTable' => 'blocks_roles',
  67. 'dependent' => false,
  68. 'propertyName' => 'roles',
  69. ]);
  70. $this->addBehavior('Serializable', [
  71. 'columns' => ['locale', 'settings']
  72. ]);
  73. }
  74. /**
  75. * Gets a list of all blocks renderable in front-end theme.
  76. *
  77. * @return mixed
  78. */
  79. public function inFrontTheme()
  80. {
  81. return $this->_inTheme('front');
  82. }
  83. /**
  84. * Gets a list of all blocks renderable in front-end theme.
  85. *
  86. * @return mixed
  87. */
  88. public function inBackTheme()
  89. {
  90. return $this->_inTheme('back');
  91. }
  92. /**
  93. * Gets a list of all blocks that are NOT renderable.
  94. *
  95. * @return \Cake\Collection\CollectionInterface
  96. */
  97. public function unused()
  98. {
  99. $ids = [];
  100. foreach ([$this->inFrontTheme(), $this->inBackTheme()] as $bundle) {
  101. foreach ($bundle as $region => $blocks) {
  102. foreach ($blocks as $block) {
  103. $ids[] = $block->get('id');
  104. }
  105. }
  106. }
  107. $notIn = array_unique($ids);
  108. $notIn = empty($notIn) ? ['0'] : $notIn;
  109. return $this->find()
  110. ->where([
  111. 'OR' => [
  112. 'Blocks.id NOT IN' => $notIn,
  113. 'Blocks.status' => 0,
  114. ]
  115. ])
  116. ->all()
  117. ->filter(function ($block) {
  118. return $block->renderable();
  119. });
  120. }
  121. /**
  122. * Gets a list of all blocks renderable in the given theme type (frontend or
  123. * backend).
  124. *
  125. * @param string $type Possible values are 'front' or 'back'
  126. * @return array Blocks index by region name
  127. */
  128. protected function _inTheme($type = 'front')
  129. {
  130. $theme = option("{$type}_theme");
  131. $composer = plugin($theme)->composer(true);
  132. $regions = $composer['extra']['regions'];
  133. $out = [];
  134. foreach ($regions as $slug => $name) {
  135. $blocks = $this->find()
  136. ->matching('BlockRegions', function ($q) use ($slug, $theme) {
  137. return $q->where([
  138. 'BlockRegions.theme' => $theme,
  139. 'BlockRegions.region' => $slug,
  140. ]);
  141. })
  142. ->where(['Blocks.status' => 1])
  143. ->all()
  144. ->filter(function ($block) {
  145. return $block->renderable();
  146. })
  147. ->sortBy(function ($block) {
  148. return $block->region->ordering;
  149. }, SORT_ASC);
  150. $out[$name] = $blocks;
  151. }
  152. return $out;
  153. }
  154. /**
  155. * Default validation rules.
  156. *
  157. * @param \Cake\Validation\Validator $validator The validator object
  158. * @return \Cake\Validation\Validator
  159. */
  160. public function validationWidget(Validator $validator)
  161. {
  162. return $validator
  163. ->requirePresence('title')
  164. ->add('title', [
  165. 'notBlank' => [
  166. 'rule' => 'notBlank',
  167. 'message' => __d('block', 'You need to provide a title.'),
  168. ],
  169. 'length' => [
  170. 'rule' => ['minLength', 3],
  171. 'message' => __d('block', 'Title need to be at least 3 characters long.'),
  172. ],
  173. ])
  174. ->requirePresence('description')
  175. ->add('description', [
  176. 'notBlank' => [
  177. 'rule' => 'notBlank',
  178. 'message' => __d('block', 'You need to provide a description.'),
  179. ],
  180. 'length' => [
  181. 'rule' => ['minLength', 3],
  182. 'message' => __d('block', 'Description need to be at least 3 characters long.'),
  183. ],
  184. ])
  185. ->add('visibility', 'validVisibility', [
  186. 'rule' => function ($value, $context) {
  187. return in_array($value, ['except', 'only', 'php']);
  188. },
  189. 'message' => __d('block', 'Invalid visibility.'),
  190. ])
  191. ->allowEmpty('pages')
  192. ->add('pages', 'validPHP', [
  193. 'rule' => function ($value, $context) {
  194. if (!empty($context['data']['visibility']) && $context['data']['visibility'] === 'php') {
  195. return strpos($value, '<?php') !== false && strpos($value, '?>') !== false;
  196. }
  197. return true;
  198. },
  199. 'message' => __d('block', 'Invalid PHP code, make sure that tags "<?php" & "?>" are present.')
  200. ])
  201. ->requirePresence('handler', 'create', __d('block', 'This field is required.'))
  202. ->add('handler', 'validHandler', [
  203. 'rule' => 'notBlank',
  204. 'on' => 'create',
  205. 'message' => __d('block', 'Invalid block handler'),
  206. ]);
  207. }
  208. /**
  209. * Validation rules for custom blocks.
  210. *
  211. * Plugins may define their own blocks, in these cases the "body" value is
  212. * optional. But blocks created by users (on the Blocks administration page)
  213. * are required to have a valid "body".
  214. *
  215. * @param \Cake\Validation\Validator $validator The validator object
  216. * @return \Cake\Validation\Validator
  217. */
  218. public function validationCustom(Validator $validator)
  219. {
  220. return $this->validationWidget($validator)
  221. ->requirePresence('body')
  222. ->add('body', [
  223. 'notBlank' => [
  224. 'rule' => 'notBlank',
  225. 'message' => __d('block', "You need to provide a content for block's body."),
  226. ],
  227. 'length' => [
  228. 'rule' => ['minLength', 3],
  229. 'message' => __d('block', "Block's body need to be at least 3 characters long."),
  230. ],
  231. ]);
  232. }
  233. /**
  234. * Validates block settings before persisted in DB.
  235. *
  236. * @param \Cake\Event\Event $event The event that was triggered
  237. * @param array $data Information to be validated
  238. * @param \ArrayObject $options Options given to pathEntity()
  239. * @return void
  240. */
  241. public function settingsValidate(Event $event, array $data, ArrayObject $options)
  242. {
  243. if (!empty($options['entity']) && $options['entity']->has('handler')) {
  244. $block = $options['entity'];
  245. if (!$block->isCustom()) {
  246. $validator = new Validator();
  247. $block->validateSettings($data, $validator);
  248. $errors = $validator->errors((array)$data);
  249. foreach ($errors as $k => $v) {
  250. $block->errors("settings:{$k}", $v);
  251. }
  252. }
  253. }
  254. }
  255. /**
  256. * Here we set default values for block's settings (used by Widget Blocks).
  257. *
  258. * Triggers the `Block.<handler>.settingsDefaults` event, event listeners
  259. * should catch the event and return an array as `key` => `value` with default
  260. * values.
  261. *
  262. * @param \Cake\Event\Event $event The event that was triggered
  263. * @param \Block\Model\Entity\Block $block The block where to put those values
  264. * @return array
  265. */
  266. public function settingsDefaultValues(Event $event, Block $block)
  267. {
  268. if (!$block->isCustom()) {
  269. return (array)$block->defaultSettings();
  270. }
  271. return [];
  272. }
  273. /**
  274. * Triggered before block is persisted in DB.
  275. *
  276. * @param \Cake\Event\Event $event The event that was triggered
  277. * @param \Block\Model\Entity\Block $block The block entity being saved
  278. * @param \ArrayObject $options Additional options given as an array
  279. * @return bool False if save operation should not continue, true otherwise
  280. */
  281. public function beforeSave(Event $event, Block $block, ArrayObject $options = null)
  282. {
  283. $result = $block->beforeSave();
  284. if ($result === false) {
  285. return false;
  286. }
  287. return true;
  288. }
  289. /**
  290. * Triggered after block was persisted in DB.
  291. *
  292. * All cached blocks are automatically removed.
  293. *
  294. * @param \Cake\Event\Event $event The event that was triggered
  295. * @param \Block\Model\Entity\Block $block The block entity that was saved
  296. * @param \ArrayObject $options Additional options given as an array
  297. * @return void
  298. */
  299. public function afterSave(Event $event, Block $block, ArrayObject $options = null)
  300. {
  301. $block->afterSave();
  302. $this->clearCache();
  303. }
  304. /**
  305. * Triggered before block is removed from DB.
  306. *
  307. * @param \Cake\Event\Event $event The event that was triggered
  308. * @param \Block\Model\Entity\Block $block The block entity being deleted
  309. * @param \ArrayObject $options Additional options given as an array
  310. * @return bool False if delete operation should not continue, true otherwise
  311. */
  312. public function beforeDelete(Event $event, Block $block, ArrayObject $options = null)
  313. {
  314. $result = $block->beforeDelete();
  315. if ($result === false) {
  316. return false;
  317. }
  318. return true;
  319. }
  320. /**
  321. * Triggered after block was removed from DB.
  322. *
  323. * All cached blocks are automatically removed.
  324. *
  325. * @param \Cake\Event\Event $event The event that was triggered
  326. * @param \Block\Model\Entity\Block $block The block entity that was deleted
  327. * @param \ArrayObject $options Additional options given as an array
  328. * @return void
  329. */
  330. public function afterDelete(Event $event, Block $block, ArrayObject $options = null)
  331. {
  332. $block->afterDelete();
  333. $this->clearCache();
  334. }
  335. /**
  336. * Clear blocks cache for all themes and all regions.
  337. *
  338. * @return void
  339. */
  340. public function clearCache()
  341. {
  342. Cache::clearGroup('views', 'blocks');
  343. }
  344. }