├── .gitignore ├── LICENSE.md ├── README.md ├── composer.json └── src ├── Commands ├── baseCommand.php ├── createPackage.php ├── createTheme.php ├── installPackage.php ├── listThemes.php ├── refreshCache.php └── removeTheme.php ├── Config └── themes.php ├── Exceptions ├── themeAlreadyExists.php ├── themeException.php └── themeNotFound.php ├── Facades └── Theme.php ├── Helpers └── helpers.php ├── Middleware └── setTheme.php ├── Theme.php ├── Themes.php ├── stubs └── cache.stub ├── themeManifest.php ├── themeServiceProvider.php └── themeViewFinder.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /.idea 3 | composer.lock 4 | Thumbs.db 5 | .env 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Giannis Gasteratos 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | [![License](http://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](https://tldrlegal.com/license/mit-license) 3 | [![Downloads](https://img.shields.io/packagist/dt/igaster/laravel-theme.svg?style=flat-square)](https://packagist.org/packages/igaster/laravel-theme) 4 | 5 | This is a Laravel package that adds basic support for managing themes. It allows you to build your views & your assets in seperate folders, and supports for theme extending! Awesome :) 6 | 7 | Features: 8 | 9 | * Views & Asset separation in theme folders 10 | * Theme inheritance: Extend any theme and create Theme hierarchies (WordPress style!) 11 | * Integrates [Orchestra/Asset](http://orchestraplatform.com/docs/3.0/components/asset) to provide Asset dependencies managment 12 | * Your App & Views remain theme-agnostic. Include new themes with (almost) no modifications 13 | * Themes are distributable! Create a single-file theme Package and install it on any Laravel application. 14 | * Ships with console commands to manage themes 15 | 16 | ## Documentation 17 | 18 | Check the [Documentation](https://github.com/igaster/laravel-theme/wiki/1.-Installation) 19 | 20 | If you are upgrading from v1.x please read the [migration guide](https://github.com/igaster/laravel-theme/wiki/Migrating-from-v1.x) 21 | 22 | ## Compability 23 | 24 | v2.x requires Laravel 5.4+ 25 | 26 | - For Laravel 5.0 & 5.1, please use the [v1.0.x branch](https://github.com/igaster/laravel-theme/tree/v1.0) 27 | - For Laravel 5.2 & 5.3, please use the [v1.1.x branch](https://github.com/igaster/laravel-theme/tree/v1.1) 28 | 29 | ## Tests 30 | 31 | This package is fully tested. Check the [testing repository](https://github.com/igaster/laravel-theme-tests) 32 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "igaster/laravel-theme", 3 | "description": "Laravel Themes: Asset & Views folder per theme. Theme inheritance. Blade integration and more...", 4 | "keywords": ["themes", "views", "assets", "blade", "laravel", "package"], 5 | "license": "MIT", 6 | "homepage": "https://github.com/Igaster/laravel-theme.git", 7 | "authors": [ 8 | { 9 | "name": "Giannis Gasteratos", 10 | "email": "igasteratos@gmail.com" 11 | } 12 | ], 13 | "require": { 14 | "illuminate/contracts": ">=5.4" 15 | }, 16 | "suggest": { 17 | "orchestra/asset": "Use '@css' and '@js' in Blade files" 18 | }, 19 | "autoload": { 20 | "psr-4" : { 21 | "Igaster\\LaravelTheme\\" : "src/", 22 | "Igaster\\LaravelTheme\\Tests\\" : "tests/" 23 | } 24 | }, 25 | "extra": { 26 | "laravel": { 27 | "providers": [ 28 | "Igaster\\LaravelTheme\\themeServiceProvider" 29 | ], 30 | "aliases": { 31 | "Theme": "Igaster\\LaravelTheme\\Facades\\Theme" 32 | } 33 | } 34 | }, 35 | "minimum-stability": "stable" 36 | } 37 | -------------------------------------------------------------------------------- /src/Commands/baseCommand.php: -------------------------------------------------------------------------------- 1 | tempPath = $this->packages_path('tmp'); 34 | 35 | $this->files = $files; 36 | 37 | } 38 | 39 | protected function createTempFolder() 40 | { 41 | $this->clearTempFolder(); 42 | 43 | $this->files->makeDirectory($this->tempPath); 44 | } 45 | 46 | protected function clearTempFolder() 47 | { 48 | if ($this->files->exists($this->tempPath)) { 49 | 50 | $this->files->deleteDirectory($this->tempPath); 51 | 52 | } 53 | } 54 | 55 | protected function packages_path($path = '') 56 | { 57 | return storage_path('themes'.DIRECTORY_SEPARATOR.$path); 58 | } 59 | 60 | protected function theme_installed($themeName) 61 | { 62 | if (!\Theme::exists($themeName)) { 63 | 64 | return false; 65 | 66 | } 67 | 68 | $viewsPath = \Theme::find($themeName)->viewsPath; 69 | 70 | return $this->files->exists(themes_path($viewsPath.DIRECTORY_SEPARATOR.'theme.json')); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Commands/createPackage.php: -------------------------------------------------------------------------------- 1 | info('Error: tar executable could not be found. Please install tar utility before you can continue'); 20 | 21 | return; 22 | 23 | } 24 | 25 | $themeName = $this->argument('themeName'); 26 | 27 | if ($themeName == "") { 28 | 29 | $themes = array_map(function ($theme) { 30 | 31 | return $theme->name; 32 | 33 | }, Theme::all()); 34 | 35 | if(empty($themes)){ 36 | 37 | $this->info("No themes found"); 38 | 39 | return; 40 | 41 | } 42 | 43 | $themeName = $this->choice('Select a theme to create a distributable package:', $themes); 44 | } 45 | 46 | $theme = Theme::find($themeName); 47 | 48 | $viewsPath = themes_path($theme->viewsPath); 49 | 50 | $assetPath = public_path($theme->assetPath); 51 | 52 | // Packages storage path 53 | $packagesPath = $this->packages_path(); 54 | 55 | if (!$this->files->exists($packagesPath)){ 56 | mkdir($packagesPath); 57 | } 58 | 59 | // Sanitize target filename 60 | $packageFileName = $theme->name; 61 | 62 | $packageFileName = mb_ereg_replace("([^\w\s\d\-_~,;\[\]\(\).])", '', $packageFileName); 63 | 64 | $packageFileName = mb_ereg_replace("([\.]{2,})", '', $packageFileName); 65 | 66 | $packageFileName = $this->packages_path("{$packageFileName}.theme.tar.gz"); 67 | 68 | // Create Temp Folder 69 | $this->createTempFolder(); 70 | 71 | // Copy Views+Assets to Temp Folder 72 | File::copyDirectory($viewsPath, "{$this->tempPath}/views"); 73 | 74 | File::copyDirectory($assetPath, "{$this->tempPath}/asset"); 75 | 76 | // Add viewsPath into theme.json file 77 | $themeJson = new \Igaster\LaravelTheme\themeManifest(); 78 | 79 | $themeJson->loadFromFile("{$this->tempPath}/views/theme.json"); 80 | 81 | $themeJson->set('views-path', $theme->viewsPath); 82 | 83 | $themeJson->saveToFile("{$this->tempPath}/views/theme.json"); 84 | 85 | // Tar Temp Folder contents 86 | system("cd {$this->tempPath} && tar -cvzf $packageFileName ."); 87 | 88 | // Del Temp Folder 89 | $this->clearTempFolder(); 90 | 91 | $this->info("Package created at [$packageFileName]"); 92 | } 93 | 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/Commands/createTheme.php: -------------------------------------------------------------------------------- 1 | output->write("$text", $newline); 13 | } 14 | 15 | public function handle() 16 | { 17 | // Get theme name 18 | $themeName = $this->argument('themeName'); 19 | if (!$themeName) { 20 | $themeName = $this->ask('Give theme name:'); 21 | } 22 | 23 | // Check that theme doesn't exist 24 | if ($this->theme_installed($themeName)) { 25 | $this->error("Error: Theme $themeName already exists"); 26 | return; 27 | } 28 | 29 | // Read theme paths 30 | $viewsPath = $this->anticipate("Where will views be located?", [$themeName], $themeName); 31 | $assetPath = $this->anticipate("Where will assets be located?", [$themeName], $themeName); 32 | 33 | // Calculate Absolute paths & check if they exist 34 | $viewsPathFull = themes_path($viewsPath, 0755, true); 35 | $assetPathFull = public_path($assetPath, 0755, true); 36 | 37 | if ($this->files->exists($viewsPathFull)) { 38 | $this->error("Error: Folder already exists: $viewsPathFull"); 39 | return; 40 | } 41 | 42 | if ($this->files->exists($assetPathFull)) { 43 | $this->error("Error: Folder already exists: $assetPathFull"); 44 | return; 45 | } 46 | 47 | // Ask for parent theme 48 | $parentTheme = ""; 49 | if ($this->confirm('Extends an other theme?')) { 50 | $themes = array_map(function ($theme) { 51 | return $theme->name; 52 | }, Theme::all()); 53 | $parentTheme = $this->choice('Which one', $themes); 54 | } 55 | 56 | $customConfiguration = $this->askCustomConfiguration(); 57 | 58 | // Display a summary 59 | $this->info("Summary:"); 60 | $this->info("- Theme name: " . $themeName); 61 | $this->info("- Views Path: " . $viewsPathFull); 62 | $this->info("- Asset Path: " . $assetPathFull); 63 | $this->info("- Extends Theme: " . ($parentTheme ?: "No")); 64 | 65 | if (!empty($customConfiguration)) { 66 | $this->info("Custom Theme Configuration:"); 67 | foreach ($customConfiguration as $key => $value) { 68 | $this->info("- $key: " . print_r($value, true)); 69 | } 70 | } 71 | 72 | if ($this->confirm('Create Theme?', true)) { 73 | 74 | $themeJson = new \Igaster\LaravelTheme\themeManifest(array_merge([ 75 | "name" => $themeName, 76 | "extends" => $parentTheme, 77 | "asset-path" => $assetPath, 78 | // "views-path" => $viewsPath, 79 | ], $customConfiguration)); 80 | 81 | // Create Paths + copy theme.json 82 | $this->files->makeDirectory($viewsPathFull); 83 | $this->files->makeDirectory($assetPathFull); 84 | 85 | $themeJson->saveToFile(themes_path("$viewsPath/theme.json")); 86 | 87 | // Rebuild Themes Cache 88 | Theme::rebuildCache(); 89 | } 90 | } 91 | 92 | // You request more information during theme setup. Just override this class and implement 93 | // the following method. It should return an associative array which will be appended 94 | // into the 'theme.json' configuration file. You can retreive this values 95 | // with Theme::getSetting('key') at runtime. You may optionaly want to redifine the 96 | // command signature too. 97 | public function askCustomConfiguration() 98 | { 99 | return [ 100 | // 'key' => 'value', 101 | ]; 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/Commands/installPackage.php: -------------------------------------------------------------------------------- 1 | info('Error: tar executable could not be found. Please install tar utility before you can continue'); 20 | 21 | return; 22 | 23 | } 24 | 25 | $package = $this->argument('package'); 26 | 27 | if (!$package) { 28 | 29 | $filenames = $this->files->glob($this->packages_path('*.theme.tar.gz')); 30 | 31 | $packages = array_map(function ($filename) { 32 | 33 | return basename($filename, '.theme.tar.gz'); 34 | 35 | }, $filenames); 36 | 37 | if(empty($packages)){ 38 | 39 | $this->info("No theme packages found to install at ".$this->packages_path()); 40 | 41 | return; 42 | 43 | } 44 | 45 | $package = $this->choice('Select a theme to install:', $packages); 46 | } 47 | 48 | $package = $this->packages_path($package . '.theme.tar.gz'); 49 | 50 | // Create Temp Folder 51 | $this->createTempFolder(); 52 | 53 | // Untar to temp folder 54 | exec("tar xzf $package -C {$this->tempPath}"); 55 | 56 | // Read theme.json 57 | $themeJson = new themeManifest(); 58 | 59 | $themeJson->loadFromFile("{$this->tempPath}/views/theme.json"); 60 | 61 | // Check if theme is already installed 62 | $themeName = $themeJson->get('name'); 63 | if ($this->theme_installed($themeName)) { 64 | 65 | $this->error('Error: Theme ' . $themeName . ' already exist. You must remove it first with "artisan theme:remove ' . $themeName . '"'); 66 | 67 | $this->clearTempFolder(); 68 | 69 | return; 70 | } 71 | 72 | // Target Paths 73 | $viewsPath = themes_path($themeJson->get('views-path')); 74 | $assetPath = public_path($themeJson->get('asset-path')); 75 | 76 | // If Views+Asset paths don't exist, move theme from temp to target paths 77 | if (file_exists($viewsPath)) { 78 | $this->info("Warning: Views path [$viewsPath] already exists. Will not be installed."); 79 | } else { 80 | File::moveDirectory("{$this->tempPath}/views", $viewsPath); 81 | 82 | // Remove 'theme-views' from theme.json 83 | $themeJson->remove('views-path'); 84 | 85 | $themeJson->saveToFile("$viewsPath/theme.json"); 86 | 87 | $this->info("Theme views installed to path [$viewsPath]"); 88 | } 89 | 90 | if (file_exists($assetPath)) { 91 | $this->error("Error: Asset path [$assetPath] already exists. Will not be installed."); 92 | } else { 93 | File::moveDirectory("{$this->tempPath}/asset", $assetPath); 94 | $this->info("Theme assets installed to path [$assetPath]"); 95 | } 96 | 97 | // Rebuild Themes Cache 98 | Theme::rebuildCache(); 99 | 100 | // Del Temp Folder 101 | $this->clearTempFolder(); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/Commands/listThemes.php: -------------------------------------------------------------------------------- 1 | output->write("$text", $newline); 14 | } 15 | 16 | public function handle() 17 | { 18 | $themes = Theme::all(); 19 | $this->info('+----------------------+----------------------+----------------------+----------------------+'); 20 | $this->info('| Theme Name | Extends | Views Path | Asset Path |'); 21 | $this->info('+----------------------+----------------------+----------------------+----------------------+'); 22 | foreach ($themes as $theme) { 23 | $this->info(sprintf("| %-20s | %-20s | %-20s | %-20s |", 24 | $theme->name, 25 | $theme->getParent() ? $theme->getParent()->name : "", 26 | $theme->viewsPath, 27 | $theme->assetPath 28 | )); 29 | } 30 | $this->info('+----------------------+----------------------+----------------------+----------------------+'); 31 | $this->info('Views Path is relative to: ' . themes_path()); 32 | $this->info('Asset Path is relative to: ' . public_path()); 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Commands/refreshCache.php: -------------------------------------------------------------------------------- 1 | info("Themes cache was refreshed. Currently theme caching is: " . (Theme::cacheEnabled() ? "ENABLED" : "DISABLED")); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/Commands/removeTheme.php: -------------------------------------------------------------------------------- 1 | argument('themeName'); 14 | if ($themeName == "") { 15 | $themes = array_map(function ($theme) { 16 | return $theme->name; 17 | }, Theme::all()); 18 | $themeName = $this->choice('Select a theme to create a distributable package:', $themes); 19 | } 20 | 21 | // Remove without confirmation? 22 | $force = $this->option('force'); 23 | 24 | // Check that theme exists 25 | if (!Theme::exists($themeName)) { 26 | $this->error("Error: Theme $themeName doesn't exist"); 27 | return; 28 | } 29 | 30 | // Get the theme 31 | $theme = Theme::find($themeName); 32 | 33 | // Diaplay Warning 34 | if (!$force) { 35 | $viewsPath = themes_path($theme->viewsPath); 36 | $assetPath = public_path($theme->assetPath); 37 | 38 | $this->info("Warning: These folders will be deleted:"); 39 | $this->info("- views: $viewsPath"); 40 | $this->info("- asset: $assetPath"); 41 | 42 | if (!$this->confirm("Continue?")) { 43 | return; 44 | } 45 | 46 | } 47 | 48 | // Delete folders 49 | $theme->uninstall(); 50 | $this->info("Theme $themeName was removed"); 51 | 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/Config/themes.php: -------------------------------------------------------------------------------- 1 | null, // eg: base_path('resources/themes') 15 | 16 | /* 17 | |-------------------------------------------------------------------------- 18 | | Set behavior if an asset is not found in a Theme hierarchy. 19 | | Available options: THROW_EXCEPTION | LOG_ERROR | IGNORE 20 | |-------------------------------------------------------------------------- 21 | */ 22 | 23 | 'asset_not_found' => 'LOG_ERROR', 24 | 25 | /* 26 | |-------------------------------------------------------------------------- 27 | | Do we want a theme activated by default? Can be set at runtime with: 28 | | Theme::set('theme-name'); 29 | |-------------------------------------------------------------------------- 30 | */ 31 | 32 | 'default' => null, 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Cache theme.json configuration files that are located in each theme's folder 37 | | in order to avoid searching theme settings in the filesystem for each request 38 | |-------------------------------------------------------------------------- 39 | */ 40 | 41 | 'cache' => false, 42 | 43 | /* 44 | |-------------------------------------------------------------------------- 45 | | Laravel introduced the @js directive in Laravel 9, released on February 8, 2022. 46 | | This directive was added to simplify safely passing PHP data to JavaScript 47 | | within Blade templates, eliminating the need for manual JSON encoding and escaping, 48 | | which was commonly done with json_encode() in older versions. 49 | | 50 | | which may conflict with you if you're (or package ex: filament) using it. 51 | | so you might want to disabled registering this package @js @jsIn @css blade directives. 52 | |-------------------------------------------------------------------------- 53 | */ 54 | 55 | 'register_blade_directives' => true, 56 | 57 | /* 58 | |-------------------------------------------------------------------------- 59 | | Define available themes. Format: 60 | | 61 | | 'theme-name' => [ 62 | | 'extends' => 'theme-to-extend', // optional 63 | | 'views-path' => 'path-to-views', // defaults to: resources/views/theme-name 64 | | 'asset-path' => 'path-to-assets', // defaults to: public/theme-name 65 | | 66 | | // You can add your own custom keys 67 | | // Use Theme::getSetting('key') & Theme::setSetting('key', 'value') to access them 68 | | 'key' => 'value', 69 | | ], 70 | | 71 | |-------------------------------------------------------------------------- 72 | */ 73 | 74 | 'themes' => [ 75 | 76 | // Add your themes here. These settings will override theme.json settings defined for each theme 77 | 78 | /* 79 | |---------------------------[ Example Structure ]-------------------------- 80 | | 81 | | // Full theme Syntax: 82 | | 83 | | 'example1' => [ 84 | | 'extends' => null, // doesn't extend any theme 85 | | 'views-path' => example, // = resources/views/example_theme 86 | | 'asset-path' => example, // = public/example_theme 87 | | ], 88 | | 89 | | // Use all Defaults: 90 | | 91 | | 'example2', // Assets =\public\example2, Views =\resources\views\example2 92 | | // Note that if you use all default values, you can omit declaration completely. 93 | | // i.e. defaults will be used when you call Theme::set('undefined-theme') 94 | | 95 | | 96 | | // This theme shares the views with example2 but defines its own assets in \public\example3 97 | | 98 | | 'example3' => [ 99 | | 'views-path' => 'example', 100 | | ], 101 | | 102 | | // This theme extends example1 and may override SOME views\assets in its own paths 103 | | 104 | | 'example4' => [ 105 | | 'extends' => 'example1', 106 | | ], 107 | | 108 | |-------------------------------------------------------------------------- 109 | */ 110 | ], 111 | 112 | ]; 113 | -------------------------------------------------------------------------------- /src/Exceptions/themeAlreadyExists.php: -------------------------------------------------------------------------------- 1 | name} already exists", 1); 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /src/Exceptions/themeException.php: -------------------------------------------------------------------------------- 1 | make('igaster.themes')->themes_path($filename); 7 | } 8 | } 9 | 10 | if (!function_exists('theme_url')) { 11 | 12 | function theme_url($url) 13 | { 14 | return app()->make('igaster.themes')->url($url); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/Middleware/setTheme.php: -------------------------------------------------------------------------------- 1 | themes = resolve('igaster.themes'); 24 | 25 | $this->name = $themeName; 26 | $this->assetPath = $assetPath === null ? $themeName : $assetPath; 27 | $this->viewsPath = $viewsPath === null ? $themeName : $viewsPath; 28 | $this->parent = $parent; 29 | 30 | $this->themes->add($this); 31 | } 32 | 33 | public function getViewPaths() 34 | { 35 | // Build Paths array. 36 | // All paths are relative to Config::get('theme.theme_path') 37 | $paths = []; 38 | $theme = $this; 39 | do { 40 | if (substr($theme->viewsPath, 0, 1) === DIRECTORY_SEPARATOR) { 41 | $path = base_path(substr($theme->viewsPath, 1)); 42 | } else { 43 | $path = themes_path($theme->viewsPath); 44 | } 45 | if (!in_array($path, $paths)) { 46 | $paths[] = $path; 47 | } 48 | 49 | } while ($theme = $theme->parent); 50 | return $paths; 51 | } 52 | 53 | public function url($url) 54 | { 55 | // return external URLs unmodified 56 | if (preg_match('/^((http(s?):)?\/\/)/i', $url)) { 57 | return $url; 58 | } 59 | 60 | $url = ltrim($url, '/'); 61 | 62 | // Is theme folder located on the web (ie AWS)? Dont lookup parent themes... 63 | if (preg_match('/^((http(s?):)?\/\/)/i', $this->assetPath)) { 64 | return $this->assetPath . '/' . $url; 65 | } 66 | 67 | // Check for valid {xxx} keys and replace them with the Theme's configuration value (in themes.php) 68 | preg_match_all('/\{(.*?)\}/', $url, $matches); 69 | foreach ($matches[1] as $param) { 70 | if (($value = $this->getSetting($param)) !== null) { 71 | $url = str_replace('{' . $param . '}', $value, $url); 72 | } 73 | } 74 | 75 | // Seperate url from url queries 76 | if (($position = strpos($url, '?')) !== false) { 77 | $baseUrl = substr($url, 0, $position); 78 | $params = substr($url, $position); 79 | } else { 80 | $baseUrl = $url; 81 | $params = ''; 82 | } 83 | 84 | // Lookup asset in current's theme asset path 85 | $fullUrl = (empty($this->assetPath) ? '' : '/') . $this->assetPath . '/' . $baseUrl; 86 | 87 | if (file_exists($fullPath = public_path($fullUrl))) { 88 | return $fullUrl . $params; 89 | } 90 | 91 | // If not found then lookup in parent's theme asset path 92 | if ($parentTheme = $this->getParent()) { 93 | return $parentTheme->url($url); 94 | } 95 | // No parent theme? Lookup in the public folder. 96 | else { 97 | if (file_exists(public_path($baseUrl))) { 98 | return "/" . $baseUrl . $params; 99 | } 100 | } 101 | 102 | // Asset not found at all. Error handling 103 | $action = Config::get('themes.asset_not_found', 'LOG_ERROR'); 104 | 105 | if ($action == 'THROW_EXCEPTION') { 106 | throw new Exceptions\themeException("Asset not found [$url]"); 107 | } elseif ($action == 'LOG_ERROR') { 108 | Log::warning("Asset not found [$url] in Theme [" . $this->themes->current()->name . "]"); 109 | } else { 110 | // themes.asset_not_found = 'IGNORE' 111 | return '/' . $url; 112 | } 113 | } 114 | 115 | public function getParent() 116 | { 117 | return $this->parent; 118 | } 119 | 120 | public function setParent(Theme $parent) 121 | { 122 | $this->parent = $parent; 123 | } 124 | 125 | public function install($clearPaths = false) 126 | { 127 | $viewsPath = themes_path($this->viewsPath); 128 | $assetPath = public_path($this->assetPath); 129 | 130 | if ($clearPaths) { 131 | if (File::exists($viewsPath)) { 132 | File::deleteDirectory($viewsPath); 133 | } 134 | if (File::exists($assetPath)) { 135 | File::deleteDirectory($assetPath); 136 | } 137 | } 138 | 139 | File::makeDirectory($viewsPath); 140 | File::makeDirectory($assetPath); 141 | 142 | $themeJson = new \Igaster\LaravelTheme\themeManifest(array_merge($this->settings, [ 143 | 'name' => $this->name, 144 | 'extends' => $this->parent ? $this->parent->name : null, 145 | 'asset-path' => $this->assetPath, 146 | ])); 147 | $themeJson->saveToFile("$viewsPath/theme.json"); 148 | 149 | $this->themes->rebuildCache(); 150 | } 151 | 152 | public function uninstall() 153 | { 154 | // Calculate absolute paths 155 | $viewsPath = themes_path($this->viewsPath); 156 | $assetPath = public_path($this->assetPath); 157 | 158 | // Check that paths exist 159 | $viewsExists = File::exists($viewsPath); 160 | $assetExists = File::exists($assetPath); 161 | 162 | // Check that no other theme uses to the same paths (ie a child theme) 163 | foreach ($this->themes->all() as $t) { 164 | if ($t !== $this && $viewsExists && $t->viewsPath == $this->viewsPath) { 165 | throw new \Exception("Can not delete folder [$viewsPath] of theme [{$this->name}] because it is also used by theme [{$t->name}]", 1); 166 | } 167 | 168 | if ($t !== $this && $assetExists && $t->assetPath == $this->assetPath) { 169 | throw new \Exception("Can not delete folder [$viewsPath] of theme [{$this->name}] because it is also used by theme [{$t->name}]", 1); 170 | } 171 | 172 | } 173 | 174 | File::deleteDirectory($viewsPath); 175 | File::deleteDirectory($assetPath); 176 | 177 | $this->themes->rebuildCache(); 178 | } 179 | 180 | /*-------------------------------------------------------------------------- 181 | | Theme Settings 182 | |--------------------------------------------------------------------------*/ 183 | 184 | public function setSetting($key, $value) 185 | { 186 | $this->settings[$key] = $value; 187 | } 188 | 189 | public function getSetting($key, $default = null) 190 | { 191 | if (Arr::has($this->settings,$key)) { 192 | return Arr::get($this->settings,$key); 193 | } elseif ($parent = $this->getParent()) { 194 | return $parent->getSetting($key, $default); 195 | } else { 196 | return $default; 197 | } 198 | } 199 | 200 | public function loadSettings($settings = []) 201 | { 202 | 203 | // $this->settings = $settings; 204 | 205 | $this->settings = array_diff_key((array) $settings, array_flip([ 206 | 'name', 207 | 'extends', 208 | 'views-path', 209 | 'asset-path', 210 | ])); 211 | 212 | } 213 | 214 | } 215 | -------------------------------------------------------------------------------- /src/Themes.php: -------------------------------------------------------------------------------- 1 | laravelViewsPath = config('view.paths'); 16 | $this->themesPath = config('themes.themes_path', null) ?: config('view.paths')[0]; 17 | $this->cachePath = base_path('bootstrap/cache/themes.php'); 18 | } 19 | 20 | /** 21 | * Return $filename path located in themes folder 22 | * 23 | * @param string $filename 24 | * @return string 25 | */ 26 | public function themes_path($filename = null) 27 | { 28 | return $filename ? $this->themesPath . '/' . $filename : $this->themesPath; 29 | } 30 | 31 | /** 32 | * Return list of registered themes 33 | * 34 | * @return array 35 | */ 36 | public function all() 37 | { 38 | return $this->themes; 39 | } 40 | 41 | /** 42 | * Check if @themeName is registered 43 | * 44 | * @return bool 45 | */ 46 | public function exists($themeName) 47 | { 48 | foreach ($this->themes as $theme) { 49 | if ($theme->name == $themeName) { 50 | return true; 51 | } 52 | 53 | } 54 | return false; 55 | } 56 | 57 | /** 58 | * Enable $themeName & set view paths 59 | * 60 | * @return Theme 61 | */ 62 | public function set($themeName) 63 | { 64 | if ($this->exists($themeName)) { 65 | $theme = $this->find($themeName); 66 | } else { 67 | $theme = new Theme($themeName); 68 | } 69 | 70 | $this->activeTheme = $theme; 71 | 72 | // Get theme view paths 73 | $paths = $theme->getViewPaths(); 74 | 75 | // fall-back to default paths (set in views.php config file) 76 | foreach ($this->laravelViewsPath as $path) { 77 | if (!in_array($path, $paths)) { 78 | $paths[] = $path; 79 | } 80 | } 81 | 82 | config(['view.paths' => $paths]); 83 | 84 | $themeViewFinder = app('view.finder'); 85 | $themeViewFinder->setPaths($paths); 86 | Event::dispatch('igaster.laravel-theme.change', $theme); 87 | return $theme; 88 | } 89 | 90 | /** 91 | * Get current theme 92 | * 93 | * @return Theme 94 | */ 95 | public function current() 96 | { 97 | return $this->activeTheme ? $this->activeTheme : null; 98 | } 99 | 100 | /** 101 | * Get current theme's name 102 | * 103 | * @return string 104 | */ 105 | public function get() 106 | { 107 | return $this->current() ? $this->current()->name : ''; 108 | } 109 | 110 | /** 111 | * Find a theme by it's name 112 | * 113 | * @return Theme 114 | */ 115 | public function find($themeName) 116 | { 117 | // Search for registered themes 118 | foreach ($this->themes as $theme) { 119 | if ($theme->name == $themeName) { 120 | return $theme; 121 | } 122 | 123 | } 124 | 125 | throw new Exceptions\themeNotFound($themeName); 126 | } 127 | 128 | /** 129 | * Register a new theme 130 | * 131 | * @return Theme 132 | */ 133 | public function add(Theme $theme) 134 | { 135 | if ($this->exists($theme->name)) { 136 | throw new Exceptions\themeAlreadyExists($theme); 137 | } 138 | $this->themes[] = $theme; 139 | return $theme; 140 | } 141 | 142 | // Original view paths defined in config.view.php 143 | public function getLaravelViewPaths() 144 | { 145 | return $this->laravelViewsPath; 146 | } 147 | 148 | public function cacheEnabled() 149 | { 150 | return config('themes.cache', false); 151 | } 152 | 153 | // Rebuilds the cache file 154 | public function rebuildCache() 155 | { 156 | $themes = $this->scanJsonFiles(); 157 | // file_put_contents($this->cachePath, json_encode($themes, JSON_PRETTY_PRINT)); 158 | 159 | $stub = file_get_contents(__DIR__ . '/stubs/cache.stub'); 160 | $contents = str_replace('[CACHE]', var_export($themes, true), $stub); 161 | file_put_contents($this->cachePath, $contents); 162 | } 163 | 164 | // Loads themes from the cache 165 | public function loadCache() 166 | { 167 | if (!file_exists($this->cachePath)) { 168 | $this->rebuildCache(); 169 | } 170 | 171 | // $data = json_decode(file_get_contents($this->cachePath), true); 172 | 173 | $data = include $this->cachePath; 174 | 175 | if ($data === null) { 176 | throw new \Exception("Invalid theme cache json file [{$this->cachePath}]"); 177 | } 178 | return $data; 179 | } 180 | 181 | // Scans theme folders for theme.json files and returns an array of themes 182 | public function scanJsonFiles() 183 | { 184 | $themes = []; 185 | foreach (glob($this->themes_path('*'), GLOB_ONLYDIR) as $themeFolder) { 186 | $themeFolder = realpath($themeFolder); 187 | if (file_exists($jsonFilename = $themeFolder . '/' . 'theme.json')) { 188 | 189 | $folders = explode(DIRECTORY_SEPARATOR, $themeFolder); 190 | $themeName = end($folders); 191 | 192 | // default theme settings 193 | $defaults = [ 194 | 'name' => $themeName, 195 | 'asset-path' => $themeName, 196 | 'extends' => null, 197 | ]; 198 | 199 | // If theme.json is not an empty file parse json values 200 | $json = file_get_contents($jsonFilename); 201 | if ($json !== "") { 202 | $data = json_decode($json, true); 203 | if ($data === null) { 204 | throw new \Exception("Invalid theme.json file at [$themeFolder]"); 205 | } 206 | } else { 207 | $data = []; 208 | } 209 | 210 | // We already know views-path since we have scaned folders. 211 | // we will overide this setting if exists 212 | $data['views-path'] = $themeName; 213 | 214 | $themes[] = array_merge($defaults, $data); 215 | } 216 | } 217 | return $themes; 218 | } 219 | 220 | public function loadThemesJson() 221 | { 222 | if ($this->cacheEnabled()) { 223 | return $this->loadCache(); 224 | } else { 225 | return $this->scanJsonFiles(); 226 | } 227 | } 228 | 229 | /** 230 | * Scan all folders inside the themes path & config/themes.php 231 | * If a "theme.json" file is found then load it and setup theme 232 | */ 233 | public function scanThemes() 234 | { 235 | 236 | $parentThemes = []; 237 | $themesConfig = config('themes.themes', []); 238 | 239 | foreach ($this->loadThemesJson() as $data) { 240 | // Are theme settings overriden in config/themes.php? 241 | if (array_key_exists($data['name'], $themesConfig)) { 242 | $data = array_merge($data, $themesConfig[$data['name']]); 243 | } 244 | 245 | // Create theme 246 | $theme = new Theme( 247 | $data['name'], 248 | $data['asset-path'], 249 | $data['views-path'] 250 | ); 251 | 252 | // Has a parent theme? Store parent name to resolve later. 253 | if ($data['extends']) { 254 | $parentThemes[$theme->name] = $data['extends']; 255 | } 256 | 257 | // Load the rest of the values as theme Settings 258 | $theme->loadSettings($data); 259 | } 260 | 261 | // Add themes from config/themes.php 262 | foreach ($themesConfig as $themeName => $themeConfig) { 263 | 264 | // Is it an element with no values? 265 | if (is_string($themeConfig)) { 266 | $themeName = $themeConfig; 267 | $themeConfig = []; 268 | } 269 | 270 | // Create new or Update existing? 271 | if (!$this->exists($themeName)) { 272 | $theme = new Theme($themeName); 273 | } else { 274 | $theme = $this->find($themeName); 275 | } 276 | 277 | // Load Values from config/themes.php 278 | if (isset($themeConfig['asset-path'])) { 279 | $theme->assetPath = $themeConfig['asset-path']; 280 | } 281 | 282 | if (isset($themeConfig['views-path'])) { 283 | $theme->viewsPath = $themeConfig['views-path']; 284 | } 285 | 286 | if (isset($themeConfig['extends'])) { 287 | $parentThemes[$themeName] = $themeConfig['extends']; 288 | } 289 | 290 | $theme->loadSettings(array_merge($theme->settings, $themeConfig)); 291 | } 292 | 293 | // All themes are loaded. Now we can assign the parents to the child-themes 294 | foreach ($parentThemes as $childName => $parentName) { 295 | $child = $this->find($childName); 296 | 297 | if ($this->exists($parentName)) { 298 | $parent = $this->find($parentName); 299 | } else { 300 | $parent = new Theme($parentName); 301 | } 302 | 303 | $child->setParent($parent); 304 | } 305 | } 306 | 307 | /*-------------------------------------------------------------------------- 308 | | Proxy to current theme 309 | |--------------------------------------------------------------------------*/ 310 | 311 | // Return url of current theme 312 | public function url($filename) 313 | { 314 | // If no Theme set, return /$filename 315 | if (!$this->current()) { 316 | return "/" . ltrim($filename, '/'); 317 | } 318 | 319 | return $this->current()->url($filename); 320 | } 321 | 322 | /** 323 | * Act as a proxy to the current theme. Map theme's functions to the Themes class. (Decorator Pattern) 324 | */ 325 | public function __call($method, $args) 326 | { 327 | if (($theme = $this->current())) { 328 | return call_user_func_array([$theme, $method], $args); 329 | } else { 330 | throw new \Exception("No theme is set. Can not execute method [$method] in [" . self::class . "]", 1); 331 | } 332 | } 333 | 334 | /*-------------------------------------------------------------------------- 335 | | Blade Helper Functions 336 | |--------------------------------------------------------------------------*/ 337 | 338 | /** 339 | * Return css link for $href 340 | * 341 | * @param string $href 342 | * @return string 343 | */ 344 | public function css($href) 345 | { 346 | return sprintf('', $this->url($href)); 347 | } 348 | 349 | /** 350 | * Return script link for $href 351 | * 352 | * @param string $href 353 | * @return string 354 | */ 355 | public function js($href) 356 | { 357 | return sprintf('', $this->url($href)); 358 | } 359 | 360 | /** 361 | * Return img tag 362 | * 363 | * @param string $src 364 | * @param string $alt 365 | * @param string $Class 366 | * @param array $attributes 367 | * @return string 368 | */ 369 | public function img($src, $alt = '', $class = '', $attributes = []) 370 | { 371 | return sprintf('%s', 372 | $this->url($src), 373 | $alt, 374 | $class, 375 | $this->HtmlAttributes($attributes) 376 | ); 377 | } 378 | 379 | /** 380 | * Return attributes in html format 381 | * 382 | * @param array $attributes 383 | * @return string 384 | */ 385 | private function HtmlAttributes($attributes) 386 | { 387 | $formatted = join(' ', array_map(function ($key) use ($attributes) { 388 | if (is_bool($attributes[$key])) { 389 | return $attributes[$key] ? $key : ''; 390 | } 391 | return $key . '="' . $attributes[$key] . '"'; 392 | }, array_keys($attributes))); 393 | return $formatted; 394 | } 395 | 396 | } 397 | -------------------------------------------------------------------------------- /src/stubs/cache.stub: -------------------------------------------------------------------------------- 1 | data = $data; 11 | } 12 | 13 | public function get($key, $default = null) 14 | { 15 | return isset($this->data[$key]) ? $this->data[$key] : $default; 16 | } 17 | 18 | public function has($key) 19 | { 20 | return isset($this->data[$key]); 21 | } 22 | 23 | public function set($key, $value) 24 | { 25 | $this->data[$key] = $value; 26 | } 27 | 28 | public function remove($key) 29 | { 30 | if (isset($this->data[$key])) { 31 | unset($this->data[$key]); 32 | } 33 | } 34 | 35 | public function loadData($data = []) 36 | { 37 | $this->data = $data; 38 | } 39 | 40 | public function validate() 41 | { 42 | return true; 43 | // throw new \Exception("Invalid data"); 44 | } 45 | 46 | public function loadFromFile($filename) 47 | { 48 | $json = file_get_contents($filename); 49 | $data = json_decode($json, true); 50 | if ($data === null) { 51 | throw new \Exception("Invalid theme.json file [$filename]"); 52 | } 53 | $this->data = $data; 54 | } 55 | 56 | public function saveToFile($filename) 57 | { 58 | file_put_contents($filename, json_encode($this->data, JSON_PRETTY_PRINT)); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/themeServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton('igaster.themes', function () { 24 | return new Themes(); 25 | }); 26 | 27 | /*-------------------------------------------------------------------------- 28 | | Replace FileViewFinder 29 | |--------------------------------------------------------------------------*/ 30 | 31 | $this->app->singleton('view.finder', function ($app) { 32 | return new \Igaster\LaravelTheme\themeViewFinder( 33 | $app['files'], 34 | $app['config']['view.paths'], 35 | null 36 | ); 37 | }); 38 | 39 | /*-------------------------------------------------------------------------- 40 | | Register helpers.php functions 41 | |--------------------------------------------------------------------------*/ 42 | 43 | require_once 'Helpers/helpers.php'; 44 | 45 | } 46 | 47 | public function boot() 48 | { 49 | 50 | /*-------------------------------------------------------------------------- 51 | | Initialize Themes 52 | |--------------------------------------------------------------------------*/ 53 | 54 | $themes = $this->app->make('igaster.themes'); 55 | $themes->scanThemes(); 56 | 57 | /*-------------------------------------------------------------------------- 58 | | Activate default theme 59 | |--------------------------------------------------------------------------*/ 60 | if (!$themes->current() && \Config::get('themes.default')) { 61 | $themes->set(\Config::get('themes.default')); 62 | } 63 | 64 | /*-------------------------------------------------------------------------- 65 | | Pulish configuration file 66 | |--------------------------------------------------------------------------*/ 67 | 68 | $this->publishes([ 69 | __DIR__ . '/Config/themes.php' => config_path('themes.php'), 70 | ], 'laravel-theme'); 71 | 72 | /*-------------------------------------------------------------------------- 73 | | Register Console Commands 74 | |--------------------------------------------------------------------------*/ 75 | if ($this->app->runningInConsole()) { 76 | $this->commands([ 77 | listThemes::class, 78 | createTheme::class, 79 | removeTheme::class, 80 | createPackage::class, 81 | installPackage::class, 82 | refreshCache::class, 83 | ]); 84 | } 85 | 86 | /*-------------------------------------------------------------------------- 87 | | Register custom Blade Directives 88 | |--------------------------------------------------------------------------*/ 89 | 90 | if(\Config::get('themes.register_blade_directives', true)) { 91 | $this->registerBladeDirectives(); 92 | } 93 | } 94 | 95 | protected function registerBladeDirectives() 96 | { 97 | /*-------------------------------------------------------------------------- 98 | | Extend Blade to support Orcherstra\Asset (Asset Managment) 99 | | 100 | | Syntax: 101 | | 102 | | @css (filename, alias, depends-on-alias) 103 | | @js (filename, alias, depends-on-alias) 104 | |--------------------------------------------------------------------------*/ 105 | 106 | Blade::extend(function ($value) { 107 | return preg_replace_callback('/\@js\s*\(\s*([^),]*)(?:,\s*([^),]*))?(?:,\s*([^),]*))?\)/', function ($match) { 108 | 109 | $p1 = trim($match[1], " \t\n\r\0\x0B\"'"); 110 | $p2 = trim(empty($match[2]) ? $p1 : $match[2], " \t\n\r\0\x0B\"'"); 111 | $p3 = trim(empty($match[3]) ? '' : $match[3], " \t\n\r\0\x0B\"'"); 112 | 113 | if (empty($p3)) { 114 | return ""; 115 | } else { 116 | return ""; 117 | } 118 | 119 | }, $value); 120 | }); 121 | 122 | Blade::extend(function ($value) { 123 | return preg_replace_callback('/\@jsIn\s*\(\s*([^),]*)(?:,\s*([^),]*))?(?:,\s*([^),]*))?(?:,\s*([^),]*))?\)/', 124 | function ($match) { 125 | 126 | $p1 = trim($match[1], " \t\n\r\0\x0B\"'"); 127 | $p2 = trim($match[2], " \t\n\r\0\x0B\"'"); 128 | $p3 = trim(empty($match[3]) ? $p2 : $match[3], " \t\n\r\0\x0B\"'"); 129 | $p4 = trim(empty($match[4]) ? '' : $match[4], " \t\n\r\0\x0B\"'"); 130 | 131 | if (empty($p4)) { 132 | return "script('$p3', theme_url('$p2'));?>"; 133 | } else { 134 | return "script('$p3', theme_url('$p2'), '$p4');?>"; 135 | } 136 | 137 | }, $value); 138 | }); 139 | 140 | Blade::extend(function ($value) { 141 | return preg_replace_callback('/\@css\s*\(\s*([^),]*)(?:,\s*([^),]*))?(?:,\s*([^),]*))?\)/', function ($match) { 142 | 143 | $p1 = trim($match[1], " \t\n\r\0\x0B\"'"); 144 | $p2 = trim(empty($match[2]) ? $p1 : $match[2], " \t\n\r\0\x0B\"'"); 145 | $p3 = trim(empty($match[3]) ? '' : $match[3], " \t\n\r\0\x0B\"'"); 146 | 147 | if (empty($p3)) { 148 | return ""; 149 | } else { 150 | return ""; 151 | } 152 | 153 | }, $value); 154 | }); 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /src/themeViewFinder.php: -------------------------------------------------------------------------------- 1 | themeEngine = \App::make('igaster.themes'); 27 | parent::__construct($files, $paths, $extensions); 28 | } 29 | 30 | /* 31 | * Override findNamespacedView() to add "Theme/vendor/..." paths 32 | * 33 | * @param string $name 34 | * @return string 35 | */ 36 | protected function findNamespacedView($name) 37 | { 38 | // Extract the $view and the $namespace parts 39 | list($namespace, $view) = $this->parseNamespaceSegments($name); 40 | 41 | $paths = $this->addThemeNamespacePaths($namespace); 42 | 43 | // Find and return the view 44 | return $this->findInPaths($view, $paths); 45 | } 46 | 47 | public function addThemeNamespacePaths($namespace) 48 | { 49 | // This rule will remap all paths starting with $key to $value. 50 | // For exapmle paths starting with 'resources/views/vendor' (relative to base_path()) 51 | // will be maped to path 'THEMENAME/vendor' (relative to current theme views-path) 52 | $pathsMap = [ 53 | // 'resources/views/vendor/mail' => 'mail', 54 | 'resources/views/vendor' => 'vendor', 55 | 'resources/views/modules' => 'modules', 56 | ]; 57 | 58 | // Does $namespace exists? 59 | if (!isset($this->hints[$namespace])) { 60 | return []; 61 | } 62 | 63 | // Get the paths registered to the $namespace 64 | $paths = $this->hints[$namespace]; 65 | 66 | // Search $paths array and remap paths that start with a key of $pathsMap array. 67 | // replace with the value of $pathsMap array 68 | $themeSubPaths = []; 69 | foreach ($paths as $path) { 70 | $pathRelativeToApp = substr($path, strlen(base_path()) + 1); 71 | // Ignore paths in composer installed packages (paths inside vendor folder) 72 | if (strpos($pathRelativeToApp, 'vendor') !== 0) { 73 | // Remap paths definded int $pathsMap array 74 | foreach ($pathsMap as $key => $value) { 75 | if (strpos($pathRelativeToApp, $key) === 0) { 76 | $pathRelativeToApp = str_replace($key, $value, $pathRelativeToApp); 77 | break; 78 | } 79 | } 80 | $themeSubPaths[] = $pathRelativeToApp; 81 | } 82 | } 83 | 84 | // Prepend current theme's view path to the remaped paths 85 | $newPaths = []; 86 | $searchPaths = array_diff($this->paths, Theme::getLaravelViewPaths()); 87 | foreach ($searchPaths as $path1) { 88 | foreach ($themeSubPaths as $path2) { 89 | $newPaths[] = $path1 . '/' . $path2; 90 | } 91 | } 92 | 93 | // Add new paths in the beggin of the search paths array 94 | foreach (array_reverse($newPaths) as $path) { 95 | if (!in_array($path, $paths)) { 96 | $paths = Arr::prepend($paths, $path); 97 | } 98 | } 99 | 100 | return $paths; 101 | } 102 | 103 | /** 104 | * Override replaceNamespace() to add path for custom error pages "Theme/errors/..." 105 | * 106 | * @param string $namespace 107 | * @param string|array $hints 108 | * @return void 109 | */ 110 | public function replaceNamespace($namespace, $hints) 111 | { 112 | $this->hints[$namespace] = (array) $hints; 113 | 114 | // Overide Error Pages 115 | if ($namespace == 'errors' || $namespace == 'mails') { 116 | 117 | $searchPaths = array_diff($this->paths, Theme::getLaravelViewPaths()); 118 | 119 | $addPaths = array_map(function ($path) use ($namespace) { 120 | return "$path/$namespace"; 121 | }, $searchPaths); 122 | 123 | $this->prependNamespace($namespace, $addPaths); 124 | } 125 | } 126 | 127 | /** 128 | * Set the array of paths where the views are being searched. 129 | * 130 | * @param array $paths 131 | */ 132 | public function setPaths($paths) 133 | { 134 | $this->paths = $paths; 135 | $this->flush(); 136 | } 137 | 138 | /** 139 | * Get the array of paths wherew the views are being searched. 140 | */ 141 | public function getPaths() 142 | { 143 | return $this->paths; 144 | } 145 | 146 | } 147 | --------------------------------------------------------------------------------