Themes.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. <?php
  2. namespace Webkul\Theme;
  3. use Illuminate\Support\Facades\Config;
  4. use Illuminate\Support\Facades\Vite;
  5. use Illuminate\Support\Str;
  6. use Webkul\Theme\Exceptions\ViterNotFound;
  7. class Themes
  8. {
  9. /**
  10. * Contains current activated theme code.
  11. *
  12. * @var string
  13. */
  14. protected $activeTheme = null;
  15. /**
  16. * Contains all themes.
  17. *
  18. * @var array
  19. */
  20. protected $themes = [];
  21. /**
  22. * Contains laravel default view paths.
  23. *
  24. * @var array
  25. */
  26. protected $laravelViewsPath;
  27. /**
  28. * Create a new themes instance.
  29. *
  30. * @return void
  31. */
  32. public function __construct()
  33. {
  34. $this->laravelViewsPath = Config::get('view.paths');
  35. $this->loadThemes();
  36. }
  37. /**
  38. * Return list of all registered themes.
  39. *
  40. * @return array
  41. */
  42. public function all()
  43. {
  44. return $this->themes;
  45. }
  46. /**
  47. * Return list of registered themes.
  48. *
  49. * @return array
  50. */
  51. public function getChannelThemes()
  52. {
  53. $themes = config('themes.shop', []);
  54. $channelThemes = [];
  55. foreach ($themes as $code => $data) {
  56. $channelThemes[] = new Theme(
  57. $code,
  58. $data['name'] ?? '',
  59. $data['assets_path'] ?? '',
  60. $data['views_path'] ?? '',
  61. isset($data['vite']) ? $data['vite'] : [],
  62. );
  63. if (! empty($data['parent'])) {
  64. $parentThemes[$code] = $data['parent'];
  65. }
  66. }
  67. return $channelThemes;
  68. }
  69. /**
  70. * Check if specified exists.
  71. *
  72. * @return bool
  73. */
  74. public function exists(string $themeName)
  75. {
  76. foreach ($this->themes as $theme) {
  77. if ($theme->code == $themeName) {
  78. return true;
  79. }
  80. }
  81. return false;
  82. }
  83. /**
  84. * Prepare all themes.
  85. *
  86. * @return void
  87. */
  88. public function loadThemes()
  89. {
  90. $parentThemes = [];
  91. /**
  92. * Octane safety: Handle request context more safely.
  93. */
  94. $isAdmin = false;
  95. try {
  96. $currentRequest = request();
  97. if ($currentRequest && $currentRequest->url()) {
  98. $isAdmin = Str::contains($currentRequest->url(), config('app.admin_url').'/');
  99. }
  100. } catch (\Exception $e) {
  101. /**
  102. * Fallback if request context is not available.
  103. */
  104. $isAdmin = false;
  105. }
  106. if ($isAdmin) {
  107. $themes = config('themes.admin', []);
  108. } else {
  109. $themes = config('themes.shop', []);
  110. }
  111. foreach ($themes as $code => $data) {
  112. $this->themes[] = new Theme(
  113. $code,
  114. $data['name'] ?? '',
  115. $data['assets_path'] ?? '',
  116. $data['views_path'] ?? '',
  117. $data['views_namespace'] ?? null,
  118. $data['vite'] ?? [],
  119. );
  120. if (! empty($data['parent'])) {
  121. $parentThemes[$code] = $data['parent'];
  122. }
  123. }
  124. foreach ($parentThemes as $childCode => $parentCode) {
  125. $child = $this->find($childCode);
  126. if ($this->exists($parentCode)) {
  127. $parent = $this->find($parentCode);
  128. } else {
  129. $parent = new Theme($parentCode);
  130. }
  131. $child->setParent($parent);
  132. }
  133. }
  134. /**
  135. * Enable theme.
  136. *
  137. * @return \Webkul\Theme\Theme
  138. */
  139. public function set(string $themeName)
  140. {
  141. if ($this->exists($themeName)) {
  142. $theme = $this->find($themeName);
  143. } else {
  144. $theme = new Theme($themeName);
  145. }
  146. $this->activeTheme = $theme;
  147. $paths = $theme->getViewPaths();
  148. foreach ($this->laravelViewsPath as $path) {
  149. if (! in_array($path, $paths)) {
  150. $paths[] = $path;
  151. }
  152. }
  153. Config::set('view.paths', $paths);
  154. $themeViewFinder = app('view.finder');
  155. $themeViewFinder->setPaths($paths);
  156. return $theme;
  157. }
  158. /**
  159. * Get current theme.
  160. *
  161. * @return \Webkul\Theme\Theme
  162. */
  163. public function current()
  164. {
  165. return $this->activeTheme ?? null;
  166. }
  167. /**
  168. * Get current theme's name.
  169. *
  170. * @return string
  171. */
  172. public function getName()
  173. {
  174. return $this->current()?->name ?? '';
  175. }
  176. /**
  177. * Find a theme by it's name.
  178. *
  179. * @return \Webkul\Theme\Theme
  180. */
  181. public function find(string $themeName)
  182. {
  183. foreach ($this->themes as $theme) {
  184. if ($theme->code == $themeName) {
  185. return $theme;
  186. }
  187. }
  188. throw new Exceptions\ThemeNotFound($themeName);
  189. }
  190. /**
  191. * Original view paths defined in `config.view.php`.
  192. *
  193. * @return array
  194. */
  195. public function getLaravelViewPaths()
  196. {
  197. return $this->laravelViewsPath;
  198. }
  199. /**
  200. * Return the asset URL of the current theme if a theme is found; otherwise, check from the namespace.
  201. *
  202. * @return string
  203. */
  204. public function url(string $filename, ?string $namespace = null)
  205. {
  206. $url = trim($filename, '/');
  207. /**
  208. * If the namespace is null, it means the theming system is activated. We use the request URI to
  209. * detect the theme and provide Vite assets based on the current theme.
  210. */
  211. if (empty($namespace)) {
  212. $currentTheme = $this->current();
  213. /**
  214. * Octane safety: Initialize theme if not set.
  215. */
  216. if (! $currentTheme) {
  217. $this->ensureThemeInitialized();
  218. $currentTheme = $this->current();
  219. }
  220. return $currentTheme->url($url);
  221. }
  222. /**
  223. * If a namespace is provided, it means the developer knows what they are doing and must create the
  224. * registry in the provided configuration. We will analyze based on that.
  225. */
  226. $viters = config('bagisto-vite.viters');
  227. if (empty($viters[$namespace])) {
  228. throw new ViterNotFound($namespace);
  229. }
  230. $viteUrl = trim($viters[$namespace]['package_assets_directory'], '/').'/'.$url;
  231. return Vite::useHotFile($viters[$namespace]['hot_file'])
  232. ->useBuildDirectory($viters[$namespace]['build_directory'])
  233. ->asset($viteUrl);
  234. }
  235. /**
  236. * Set bagisto vite in current theme.
  237. *
  238. * @param mixed $entryPoints
  239. * @return mixed
  240. */
  241. public function setBagistoVite($entryPoints, ?string $namespace = null)
  242. {
  243. /**
  244. * If the namespace is null, it means the theming system is activated. We use the request URI to
  245. * detect the theme and provide Vite assets based on the current theme.
  246. */
  247. if (empty($namespace)) {
  248. $currentTheme = $this->current();
  249. /**
  250. * Octane safety: Initialize theme if not set.
  251. */
  252. if (! $currentTheme) {
  253. $this->ensureThemeInitialized();
  254. $currentTheme = $this->current();
  255. }
  256. return $currentTheme->setBagistoVite($entryPoints);
  257. }
  258. /**
  259. * If a namespace is provided, it means the developer knows what they are doing and must create the
  260. * registry in the provided configuration. We will analyze based on that.
  261. */
  262. $viters = config('bagisto-vite.viters');
  263. if (empty($viters[$namespace])) {
  264. throw new ViterNotFound($namespace);
  265. }
  266. return Vite::useHotFile($viters[$namespace]['hot_file'])
  267. ->useBuildDirectory($viters[$namespace]['build_directory'])
  268. ->withEntryPoints($entryPoints);
  269. }
  270. /**
  271. * Ensure theme is properly initialized (Octane Safety).
  272. *
  273. * @return void
  274. */
  275. protected function ensureThemeInitialized()
  276. {
  277. if (! $this->activeTheme) {
  278. /**
  279. * Reload themes based on current request context.
  280. */
  281. $this->loadThemes();
  282. /**
  283. * Set default theme if no theme is set.
  284. */
  285. if (! $this->activeTheme && ! empty($this->themes)) {
  286. $defaultTheme = $this->themes[0];
  287. $this->set($defaultTheme->code);
  288. }
  289. }
  290. }
  291. }