PageRenderTime 44ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/cms/widgets/TemplateList.php

https://gitlab.com/gideonmarked/yovelife
PHP | 388 lines | 249 code | 78 blank | 61 comment | 28 complexity | 6b16ca9e8e33eafd075a181dbe75c884 MD5 | raw file
  1. <?php namespace Cms\Widgets;
  2. use Str;
  3. use File;
  4. use Input;
  5. use Request;
  6. use Response;
  7. use Cms\Classes\Theme;
  8. use Backend\Classes\WidgetBase;
  9. /**
  10. * Template list widget.
  11. * This widget displays templates of different types.
  12. *
  13. * @package october\cms
  14. * @author Alexey Bobkov, Samuel Georges
  15. */
  16. class TemplateList extends WidgetBase
  17. {
  18. use \Backend\Traits\SelectableWidget;
  19. protected $searchTerm = false;
  20. protected $dataSource;
  21. protected $theme;
  22. protected $groupStatusCache = false;
  23. /**
  24. * @var string object property to use as a title.
  25. */
  26. public $titleProperty;
  27. /**
  28. * @var array a list of object properties to use in the description area.
  29. * The array should include the property names and corresponding titles:
  30. * ['url'=>'URL']
  31. */
  32. public $descriptionProperties = [];
  33. /**
  34. * @var string object property to use as a description.
  35. */
  36. public $descriptionProperty;
  37. /**
  38. * @var string Message to display when there are no records in the list.
  39. */
  40. public $noRecordsMessage = 'No records found';
  41. /**
  42. * @var string Message to display when the Delete button is clicked.
  43. */
  44. public $deleteConfirmation = 'Delete selected templates?';
  45. /**
  46. * @var string Specifies the item type.
  47. */
  48. public $itemType;
  49. /**
  50. * @var string Extra CSS class name to apply to the control.
  51. */
  52. public $controlClass = null;
  53. /**
  54. * @var string A list of file name patterns to suppress / hide.
  55. */
  56. public $ignoreDirectories = [];
  57. /*
  58. * Public methods
  59. */
  60. public function __construct($controller, $alias, callable $dataSource)
  61. {
  62. $this->alias = $alias;
  63. $this->dataSource = $dataSource;
  64. $this->theme = Theme::getEditTheme();
  65. $this->selectionInputName = 'template';
  66. parent::__construct($controller, []);
  67. if (!Request::isXmlHttpRequest()) {
  68. $this->resetSelection();
  69. }
  70. $configFile = 'config_' . snake_case($alias) .'.yaml';
  71. $config = $this->makeConfig($configFile);
  72. foreach ($config as $field => $value) {
  73. if (property_exists($this, $field)) {
  74. $this->$field = $value;
  75. }
  76. }
  77. $this->bindToController();
  78. }
  79. /**
  80. * Renders the widget.
  81. * @return string
  82. */
  83. public function render()
  84. {
  85. $toolbarClass = Str::contains($this->controlClass, 'hero') ? 'separator' : null;
  86. $this->vars['toolbarClass'] = $toolbarClass;
  87. return $this->makePartial('body', [
  88. 'data' => $this->getData()
  89. ]);
  90. }
  91. /*
  92. * Event handlers
  93. */
  94. public function onSearch()
  95. {
  96. $this->setSearchTerm(Input::get('search'));
  97. $this->extendSelection();
  98. return $this->updateList();
  99. }
  100. public function onGroupStatusUpdate()
  101. {
  102. $this->setGroupStatus(Input::get('group'), Input::get('status'));
  103. }
  104. public function onUpdate()
  105. {
  106. $this->extendSelection();
  107. return $this->updateList();
  108. }
  109. //
  110. // Methods for the internal use
  111. //
  112. protected function getData()
  113. {
  114. /*
  115. * Load the data
  116. */
  117. $items = call_user_func($this->dataSource);
  118. if ($items instanceof \October\Rain\Support\Collection) {
  119. $items = $items->all();
  120. }
  121. $items = $this->removeIgnoredDirectories($items);
  122. $items = array_map([$this, 'normalizeItem'], $items);
  123. usort($items, function ($a, $b) {
  124. return strcmp($a->fileName, $b->fileName);
  125. });
  126. /*
  127. * Apply the search
  128. */
  129. $filteredItems = [];
  130. $searchTerm = Str::lower($this->getSearchTerm());
  131. if (strlen($searchTerm)) {
  132. /*
  133. * Exact
  134. */
  135. foreach ($items as $index => $item) {
  136. if ($this->itemContainsWord($searchTerm, $item, true)) {
  137. $filteredItems[] = $item;
  138. unset($items[$index]);
  139. }
  140. }
  141. /*
  142. * Fuzzy
  143. */
  144. $words = explode(' ', $searchTerm);
  145. foreach ($items as $item) {
  146. if ($this->itemMatchesSearch($words, $item)) {
  147. $filteredItems[] = $item;
  148. }
  149. }
  150. }
  151. else {
  152. $filteredItems = $items;
  153. }
  154. /*
  155. * Group the items
  156. */
  157. $result = [];
  158. $foundGroups = [];
  159. foreach ($filteredItems as $itemData) {
  160. $pos = strpos($itemData->fileName, '/');
  161. if ($pos !== false) {
  162. $group = substr($itemData->fileName, 0, $pos);
  163. if (!array_key_exists($group, $foundGroups)) {
  164. $newGroup = (object)[
  165. 'title' => $group,
  166. 'items' => []
  167. ];
  168. $foundGroups[$group] = $newGroup;
  169. }
  170. $foundGroups[$group]->items[] = $itemData;
  171. }
  172. else {
  173. $result[] = $itemData;
  174. }
  175. }
  176. foreach ($foundGroups as $group) {
  177. $result[] = $group;
  178. }
  179. return $result;
  180. }
  181. protected function removeIgnoredDirectories($items)
  182. {
  183. if (!$this->ignoreDirectories) {
  184. return $items;
  185. }
  186. $ignoreCache = [];
  187. $items = array_filter($items, function($item) use (&$ignoreCache) {
  188. $fileName = $item->getBaseFileName();
  189. $dirName = dirname($fileName);
  190. if (isset($ignoreCache[$dirName])) {
  191. return false;
  192. }
  193. foreach ($this->ignoreDirectories as $ignoreDir) {
  194. if (File::fileNameMatch($dirName, $ignoreDir)) {
  195. $ignoreCache[$dirName] = true;
  196. return false;
  197. }
  198. }
  199. return true;
  200. });
  201. return $items;
  202. }
  203. protected function normalizeItem($item)
  204. {
  205. $description = null;
  206. if ($descriptionProperty = $this->descriptionProperty) {
  207. $description = $item->$descriptionProperty;
  208. }
  209. $descriptions = [];
  210. foreach ($this->descriptionProperties as $property => $title) {
  211. if ($item->$property) {
  212. $descriptions[$title] = $item->$property;
  213. }
  214. }
  215. $result = [
  216. 'title' => $this->getItemTitle($item),
  217. 'fileName' => $item->getFileName(),
  218. 'description' => $description,
  219. 'descriptions' => $descriptions
  220. ];
  221. return (object) $result;
  222. }
  223. protected function getItemTitle($item)
  224. {
  225. $titleProperty = $this->titleProperty;
  226. if ($titleProperty) {
  227. return $item->$titleProperty ?: basename($item->getFileName());
  228. }
  229. return basename($item->getFileName());
  230. }
  231. protected function setSearchTerm($term)
  232. {
  233. $this->searchTerm = trim($term);
  234. $this->putSession('search', $this->searchTerm);
  235. }
  236. protected function getSearchTerm()
  237. {
  238. return $this->searchTerm !== false ? $this->searchTerm : $this->getSession('search');
  239. }
  240. protected function updateList()
  241. {
  242. return [
  243. '#'.$this->getId('template-list') => $this->makePartial('items', ['items' => $this->getData()])
  244. ];
  245. }
  246. protected function itemMatchesSearch($words, $item)
  247. {
  248. foreach ($words as $word) {
  249. $word = trim($word);
  250. if (!strlen($word)) {
  251. continue;
  252. }
  253. if (!$this->itemContainsWord($word, $item)) {
  254. return false;
  255. }
  256. }
  257. return true;
  258. }
  259. protected function itemContainsWord($word, $item, $exact = false)
  260. {
  261. $operator = $exact ? 'is' : 'contains';
  262. if (strlen($item->title)) {
  263. if (Str::$operator(Str::lower($item->title), $word)) {
  264. return true;
  265. }
  266. }
  267. if (Str::$operator(Str::lower($item->fileName), $word)) {
  268. return true;
  269. }
  270. if (Str::$operator(Str::lower($item->description), $word) && strlen($item->description)) {
  271. return true;
  272. }
  273. foreach ($item->descriptions as $value) {
  274. if (Str::$operator(Str::lower($value), $word) && strlen($value)) {
  275. return true;
  276. }
  277. }
  278. return false;
  279. }
  280. protected function getGroupStatus($group)
  281. {
  282. $statuses = $this->getGroupStatuses();
  283. if (array_key_exists($group, $statuses)) {
  284. return $statuses[$group];
  285. }
  286. return false;
  287. }
  288. protected function getThemeSessionKey($prefix)
  289. {
  290. return $prefix.$this->theme->getDirName();
  291. }
  292. protected function getGroupStatuses()
  293. {
  294. if ($this->groupStatusCache !== false) {
  295. return $this->groupStatusCache;
  296. }
  297. $groups = $this->getSession($this->getThemeSessionKey('groups'), []);
  298. if (!is_array($groups)) {
  299. return $this->groupStatusCache = [];
  300. }
  301. return $this->groupStatusCache = $groups;
  302. }
  303. protected function setGroupStatus($group, $status)
  304. {
  305. $statuses = $this->getGroupStatuses();
  306. $statuses[$group] = $status;
  307. $this->groupStatusCache = $statuses;
  308. $this->putSession($this->getThemeSessionKey('groups'), $statuses);
  309. }
  310. }