├── .gitattributes ├── .gitignore ├── .scrutinizer.yml ├── README.md ├── composer.json └── src ├── Commands └── GenerateSeoManagerData.php ├── Controllers ├── ImportController.php ├── LocalesController.php └── ManagerController.php ├── Facades └── SeoManager.php ├── Middleware ├── ClearViewCache.php └── SeoManager.php ├── Models ├── Locale.php ├── SeoManager.php └── Translate.php ├── SeoManager.php ├── SeoManagerServiceProvider.php ├── Traits ├── Appends.php └── SeoManagerTrait.php ├── assets ├── css │ └── theme.min.css ├── fonts │ ├── cerebrisans │ │ ├── cerebrisans-medium.eot │ │ ├── cerebrisans-medium.ttf │ │ ├── cerebrisans-medium.woff │ │ ├── cerebrisans-mediumd41d.eot │ │ ├── cerebrisans-regular.eot │ │ ├── cerebrisans-regular.ttf │ │ ├── cerebrisans-regular.woff │ │ ├── cerebrisans-regulard41d.eot │ │ ├── cerebrisans-semibold.eot │ │ ├── cerebrisans-semibold.ttf │ │ ├── cerebrisans-semibold.woff │ │ └── cerebrisans-semiboldd41d.eot │ └── feather │ │ ├── feather.min.css │ │ └── fonts │ │ ├── Feather144f.svg │ │ ├── Feather144f.ttf │ │ └── Feather144f.woff ├── img │ ├── logo.png │ ├── logo.svg │ ├── nophoto.png │ └── scale.svg ├── js │ ├── seo-manager.app.js │ └── theme.min.js └── libs │ ├── bootstrap │ └── dist │ │ └── js │ │ └── bootstrap.bundle.min.js │ └── jquery │ └── dist │ └── jquery.min.js ├── config └── seo-manager.php ├── helpers └── helpers.php ├── migrations ├── 2018_10_29_174832_create_lionix_seo_manager_table.php ├── 2019_01_08_132731_create_locales_table.php └── 2019_01_12_174747_create_translates_table.php ├── routes └── seo-manager.php ├── views ├── index.blade.php └── og_data.blade.php └── vue ├── App.vue ├── components ├── Header.vue ├── SideBar.vue └── routes │ ├── Routes.vue │ └── partials │ ├── Actions.vue │ ├── Author.vue │ ├── Description.vue │ ├── Keywords.vue │ ├── Mapping.vue │ ├── OgData.vue │ ├── Params.vue │ ├── Route.vue │ ├── Title.vue │ ├── TitleDynamic.vue │ ├── Url.vue │ ├── mappings │ ├── ParamsMapping.vue │ └── TitleMapping.vue │ └── modals │ ├── AuthorModal.vue │ ├── DescriptionModal.vue │ ├── KeywordsModal.vue │ ├── MappingModal.vue │ ├── OgDataModal.vue │ ├── PreviewModal.vue │ ├── TitleDynamicModal.vue │ ├── TitleModal.vue │ └── UrlModal.vue ├── event_bus └── index.js ├── seo-manager.app.js └── store └── store.js /.gitattributes: -------------------------------------------------------------------------------- 1 | src/assets/* linguist-vendored 2 | src/vue/* linguist-vendored 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lionix-team/seo-manager/23761959505464044b13db04e6e10e5af2f74c7e/.gitignore -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lionix-team/seo-manager/23761959505464044b13db04e6e10e5af2f74c7e/.scrutinizer.yml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Seo Manager Package for Laravel ( with Localization ) 2 | 3 | [![MadeWithLaravel.com shield](https://madewithlaravel.com/storage/repo-shields/1138-shield.svg)](https://madewithlaravel.com/p/seo-manager/shield-link) 4 | [![](https://img.shields.io/scrutinizer/build/g/filp/whoops.svg)](https://scrutinizer-ci.com/g/lionix-team/seo-manager/) 5 | 6 | `lionix/seo-manager` package will provide 7 | you an interface from where you can manage all your pages 8 | metadata separately and get dynamically changing content. 9 | Let's see how. 10 | 11 | # Update: 12 | After updating to version > v1.2.* you have to run: 13 | ```bash 14 | php artisan vendor:publish --provider="Lionix\SeoManager\SeoManagerServiceProvider" --tag=config --force 15 | ``` 16 | ```bash 17 | php artisan vendor:publish --provider="Lionix\SeoManager\SeoManagerServiceProvider" --tag=assets --force 18 | ``` 19 | and 20 | ```bash 21 | php artisan migrate 22 | ``` 23 | to publish latest configs and new migration files. 24 | 25 | ## Localization 26 | In version v1.2.* you will have availability 27 | to localize your metadata and set translates 28 | to your data. 29 | 30 | For that, you just should 31 | add your available locales 32 | with top right button "Add Locales" and 33 | chose locale for which you want to add translations. 34 | 35 | That's it, 36 | Package will automatically detect your locale 37 | from website and will set needed translations, 38 | you don't need to do any other configs. 39 | Easy, isn't it? 40 | 41 | ## Installation 42 | You can install the package via composer: 43 | 44 | ```bash 45 | composer require lionix/seo-manager 46 | ``` 47 | Publishing package files 48 | ```bash 49 | php artisan vendor:publish --provider="Lionix\SeoManager\SeoManagerServiceProvider" 50 | ``` 51 | This command will create `config/seo-manager.php` 52 | file and will copy package assets directory to `public/vendor/lionix`. 53 | 54 | #### Configurations 55 | 56 | In `config/seo-manager.php` file you can do the following configurations: 57 | 58 | ```php 59 | return [ 60 | /** 61 | * Database table name where your manager data will be stored 62 | */ 63 | 'database' => [ 64 | 'table' => 'seo_manager', 65 | 'locales_table' => 'seo_manager_locales', 66 | 'translates_table' => 'seo_manager_translates' 67 | ], 68 | 69 | /** 70 | * Set default locale, 71 | * It will be added as default locale 72 | * when locales table migrated 73 | */ 74 | 'locale' => 'en', 75 | 76 | /** 77 | * Path where your eloquent models are 78 | */ 79 | 'models_path' => '', 80 | 81 | /** 82 | * Route from which your Dashboard will be available 83 | */ 84 | 'route' => 'seo-manager', 85 | 86 | /** 87 | * Middleware array for dashboard 88 | * to prevent unauthorized users visit the manager 89 | */ 90 | 'middleware' => [ 91 | // 'auth', 92 | ], 93 | 94 | /** 95 | * Routes which shouldn't be imported to seo manager 96 | */ 97 | 'except_routes' => [ 98 | 'seo-manager', 99 | 'admin' 100 | // 101 | ], 102 | 103 | /** 104 | * Columns which shouldn't be used ( in mapping ) 105 | */ 106 | 'except_columns' => [ 107 | // "created_at", 108 | // "updated_at", 109 | ], 110 | 111 | /** 112 | * Set this parameter to true 113 | * if you want to have "$metaData" variable 114 | * shared between all views in "web" middleware group 115 | */ 116 | 'shared_meta_data' => false 117 | ]; 118 | ``` 119 | 120 | After finishing with all configurations run `php artisan migrate` to create SEO manager table. 121 | 122 | That's it, now your SEO manager will be available 123 | from /seo-manager URL (or, if you changed route config, 124 | by that config URL) 125 | 126 | ## Usage 127 | 128 | After visiting your dashboard URL you have to import your routes to start to manage them. 129 | 130 | ![routes import](https://lh6.googleusercontent.com/YSbFt8jvV6swodjBE4xi6UCP0h6sNxw01kEhg7YueMnsuIQxmeWoEjBagiY=w2400) 131 | 132 | Route importing will store all your GET routes into the database ( except the ones which specified in "except_routes" configs). 133 | 134 | ![imported routes](https://lh5.googleusercontent.com/Dn-tuphYqMN9bmN_WzcWmTgOCzuzg3m3_TcWlzbb7Nf7zbVHrHTBkXc4O4E=w2400) 135 | 136 | ### Let the fun begin! 137 | 138 | **Mapping** 139 | 140 | ![](https://lh4.googleusercontent.com/fxvoOPQUG9GNiOqAj6C2z7_ZolMMJSV-53M_Q6sPqt3fp1TdYp-9blL3DQ8=w2400) 141 | 142 | To get dynamically changing metadata you should map your route params to the correct Models. 143 | 144 | *Param: Route param* 145 | 146 | *Model: Eloquent Model which you are using to get the record by route param* 147 | 148 | *Find By: Database table column name which you are using to get the record by route param* 149 | 150 | *Use Columns: Database table columns which we should use for further mapping* 151 | 152 | 153 | > **Note**: If you have appended attributes inside your model via `protected $appends` and you want to use them in mapping 154 | you have to use `Lionix\SeoManager\Traits\Appends;` trait inside your model. 155 | 156 | *Mapping available only if your route has params* 157 | 158 | Next steps you can do, is to set Keywords, Description, Title, URL, Author, Title Dynamic, OpenGraph Data. 159 | 160 | **About "Title Dynamic":** 161 | 162 | Here you can drag & drop your mapped params, your title and write custom text to generate the dynamic title for your page. 163 | Every time your "title" will be changed or your mapped params value changed, the dynamic title will be changed automatically. 164 | 165 | ![](https://lh3.googleusercontent.com/VgalM88QnjH8iB9-bEc2iike_14Lb_cF7JEyilBwqBTuuDOfoeJvR-n655M=w2400) 166 | 167 | **About "Open Graph":** 168 | 169 | Here you can write your open graph data or map your fields to mapping based on params. 170 | 171 | ![](https://lh6.googleusercontent.com/Z93-NUUKLFOleZbj4oYwfE59MMyGxhu9SHxE-0iAKNwatWHm9w5LfZ_h5rg=w2400) 172 | 173 | ## Example Usage 174 | 175 | ## Via `SeoManager` Facade 176 | 177 | ```php 178 | use Lionix\SeoManager\Facades\SeoManager; 179 | ``` 180 | ##### This will return an array with all your SEO Manager data 181 | ``` 182 | SeoManager::metaData(); 183 | ``` 184 | *Example:* 185 | ```php 186 | array:13 [▼ 187 | "keywords" => "First Keyword, Second, Third" 188 | "description" => "Test Description" 189 | "title" => "Test Titile" 190 | "url" => "http://example.com/users/1" 191 | "author" => "Sergey Karakhanyan" 192 | "title_dynamic" => "Test Titile - Custom Text - Test User Name " 193 | "og:url" => "http://example.com/users/1" 194 | "og:type" => "website" 195 | "og:image:url" => "https://wallpaperbrowse.com/media/images/3848765-wallpaper-images-download.jpg" 196 | "og:title" => "Test Titile - Custom Open Graph Text" 197 | "og:locale" => "en_GB" 198 | "og:site_name" => "Seo Manager Package" 199 | "og:description" => "Open Graph Description" 200 | ] 201 | ``` 202 | 203 | `SeoManager::metaData();` method can receive property variable 204 | to get the value of some property 205 | 206 | *Example:* 207 | 208 | `SeoManager::metaData('keywords');` will return `"First Keyword, Second, Third"` 209 | 210 | 211 | ##### To get only OpenGraph data array: 212 | ```php 213 | SeoManager::metaData('og_data'); 214 | ``` 215 | *Example* 216 | 217 | ```php 218 | array:7 [▼ 219 | "og:url" => "http://example.com/users/1" 220 | "og:type" => "website" 221 | "og:image:url" => "https://wallpaperbrowse.com/media/images/3848765-wallpaper-images-download.jpg" 222 | "og:title" => "Test Titile - Custom Open Graph Text" 223 | "og:locale" => "en_GB" 224 | "og:site_name" => "Seo Manager Package" 225 | "og:description" => "Open Graph Description" 226 | ] 227 | ``` 228 | ##### Aliases 229 | `SeoManager::metaKeywords()` 230 | 231 | `SeoManager::metaTitle()` 232 | 233 | `SeoManager::metaDescription()` 234 | 235 | `SeoManager::metaUrl()` 236 | 237 | `SeoManager::metaAuthor()` 238 | 239 | `SeoManager::metaTitleDynamic()` 240 | 241 | `SeoManager::metaOpenGraph()` - Can receive property variable to get the value of some OG property 242 | 243 | *Example* 244 | 245 | `SeoManager::metaOpenGraph('og:image:url')` 246 | 247 | Will return 248 | `"https://wallpaperbrowse.com/media/images/3848765-wallpaper-images-download.jpg"` 249 | 250 | 251 | ## Via `helper` functions 252 | 253 | `metaData()` 254 | 255 | `metaKeywords()` 256 | 257 | `metaTitle()` 258 | 259 | `metaDescription()` 260 | 261 | `metaUrl()` 262 | 263 | `metaAuthor()` 264 | 265 | `metaTitleDynamic()` 266 | 267 | `metaOpenGraph()` 268 | 269 | ## Via @Blade directives 270 | 271 | You can use this blade directives in your view files to get metadata. 272 | 273 | `@meta` 274 | 275 | *Output Example* 276 | 277 | ```html 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | ``` 292 | 293 | `@meta` can receive property param 294 | 295 | `@meta('description')` 296 | 297 | *Output Example* 298 | 299 | ```html 300 | 301 | ``` 302 | >**Note:** 303 | You can't add open graph properties to `@meta()` like `@meta('og:url')` 304 | But you can get only OpenGraph meta data by `@meta('og_data')`. 305 | If you want to get concrete OG param meta tag you can use `@openGraph` (*similar to `@meta('og_data')`*) 306 | and pass param there like `@openGraph('og:url)` 307 | 308 | >**Note #2:** If you want to do modifications in your og data and display it manually, you should do that before `@meta` 309 | 310 | *Example:* 311 | ```html 312 | 313 | @meta 314 | ``` 315 | ##### Aliases 316 | 317 | `@keywords` 318 | 319 | `@url` 320 | 321 | `@author` 322 | 323 | `@description` 324 | 325 | `@title` 326 | 327 | `@openGraph` 328 | 329 | `@titleDynamic` - will return dynamically generated title which you can use inside your `` tags. 330 | 331 | ## Contributing 332 | 333 | ### How to start developing on this project 334 | 335 | 1. Install the module in a existing Laravel project and check if the module is working. 336 | 337 | 2. Delete the folder vendor/lionix/seo-manager. 338 | 339 | 3. Copy the fork of the module in the vendor/lionix/seo-manager folder 340 | (don't know how to make a fork? https://kbroman.org/github_tutorial/pages/fork.html) 341 | 342 | 4. Add the following line to your webpack.mix.js 343 | `mix.js('vendor/lionix/seo-manager/src/vue/seo-manager.app.js', 'public/vendor/lionix/js');` 344 | 345 | 5. run the NPM Watch command `npm run watch` 346 | 347 | 6. If everything works fine, you can start developing on the module 348 | 349 | 7. **PLEASE REMIND TO COPY 350 | YOUR `public/vendor/lionix/js/seo-manager.app.js` TO `vendor/lionix/seo-manager/src/assets/seo-manager.app.js`** 351 | 352 | ## Credits 353 | 354 | - [Sergey Karakhanyan](https://github.com/karakhanyans) 355 | - [Lionix Team](https://github.com/lionix-team) 356 | 357 | 358 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lionix/seo-manager", 3 | "description": "SEO Manager for Laravel Framework", 4 | "keywords": ["laravel", "seo", "seo-manager", "lionix", "meta", "meta-data", "open-graph", "og-data"], 5 | "type": "library", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Lionix Team", 10 | "email": "packages@lionix-team.com" 11 | }, 12 | { 13 | "name": "Sergey Karakhanyan", 14 | "email": "karakhanyansa@gmail.com" 15 | } 16 | ], 17 | "extra": { 18 | "laravel": { 19 | "providers": [ 20 | "Lionix\\SeoManager\\SeoManagerServiceProvider" 21 | ], 22 | "aliases": { 23 | "SeoManager": "Lionix\\SeoManager\\Facades\\SeoManager" 24 | } 25 | } 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "Lionix\\SeoManager\\": "src" 30 | }, 31 | "files":[ 32 | "src/helpers/helpers.php" 33 | ] 34 | }, 35 | "minimum-stability": "dev", 36 | "require": { 37 | "php": "^7.0|^8.0", 38 | "illuminate/support": "5.5.* || 5.6.* || 5.7.* || 5.8.* || 6.* || 7.* || 8.*" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Commands/GenerateSeoManagerData.php: -------------------------------------------------------------------------------- 1 | info('Import Started'); 43 | $this->importRoutes(); 44 | $this->info('Import Finished'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Controllers/ImportController.php: -------------------------------------------------------------------------------- 1 | importRoutes(); 22 | return response()->json(['routes' => $routes]); 23 | } catch (\Exception $exception) { 24 | return response()->json(['status' => false, 'message' => $exception->getMessage()]); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Controllers/LocalesController.php: -------------------------------------------------------------------------------- 1 | json(['locales' => $locales]); 19 | } 20 | 21 | /** 22 | * @param Request $request 23 | * @return \Illuminate\Http\JsonResponse 24 | */ 25 | public function addLocale(Request $request) 26 | { 27 | try{ 28 | $locale = Locale::whereName($request->get('name'))->first(); 29 | if(!$locale){ 30 | $locale = new Locale(); 31 | $locale->fill($request->all()); 32 | $locale->save(); 33 | return response()->json(['locale' => $locale->name]); 34 | } 35 | throw new \Exception('Locale is already exist'); 36 | }catch (\Exception $exception){ 37 | return response()->json(['status' => false, 'message' => $exception->getMessage()], 400); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Controllers/ManagerController.php: -------------------------------------------------------------------------------- 1 | get('locale')){ 22 | app()->setLocale($request->get('locale')); 23 | $this->locale = app()->getLocale(); 24 | } 25 | } 26 | 27 | /** 28 | * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View 29 | */ 30 | public function index() 31 | { 32 | return view('seo-manager::index'); 33 | } 34 | 35 | /** 36 | * @return \Illuminate\Http\JsonResponse 37 | */ 38 | public function getRoutes() 39 | { 40 | $routes = SeoManagerModel::all(); 41 | return response()->json(['routes' => $routes]); 42 | } 43 | 44 | /** 45 | * @return \Illuminate\Http\JsonResponse 46 | */ 47 | public function getModels() 48 | { 49 | try { 50 | $models = $this->getAllModels(); 51 | return response()->json(['models' => $models]); 52 | } catch (\Exception $exception) { 53 | return response()->json(['status' => false, 'message' => $exception->getMessage()]); 54 | } 55 | } 56 | 57 | /** 58 | * @param Request $request 59 | * @return \Illuminate\Http\JsonResponse 60 | * @throws \Throwable 61 | */ 62 | public function getModelColumns(Request $request) 63 | { 64 | try { 65 | $model = $request->get('model'); 66 | $columns = $this->getColumns($model); 67 | return response()->json(['columns' => $columns]); 68 | } catch (\Exception $exception) { 69 | return response()->json(['status' => false, 'message' => $exception->getMessage()]); 70 | } 71 | } 72 | 73 | /** 74 | * @param Request $request 75 | * @return \Illuminate\Http\JsonResponse 76 | */ 77 | public function storeData(Request $request) 78 | { 79 | $allowedColumns = Schema::getColumnListing(config('seo-manager.database.table')); 80 | try { 81 | $id = $request->get('id'); 82 | $type = $request->get('type'); 83 | $seoManager = SeoManagerModel::find($id); 84 | if (in_array($type, $allowedColumns)) { 85 | $data = $request->get($type); 86 | if($type != 'mapping' && $this->locale !== config('seo-manager.locale')){ 87 | $translate = $seoManager->translation()->where('locale', $this->locale)->first(); 88 | if(!$translate){ 89 | $newInst = new Translate(); 90 | $newInst->locale = $this->locale; 91 | $translate = $seoManager->translation()->save($newInst); 92 | } 93 | $translate->$type = $data; 94 | $translate->save(); 95 | }else{ 96 | $seoManager->$type = $data; 97 | $seoManager->save(); 98 | } 99 | } 100 | return response()->json([$type => $seoManager->$type]); 101 | } catch (\Exception $exception) { 102 | return response()->json(['status' => false, 'message' => $exception->getMessage()]); 103 | } 104 | } 105 | 106 | /** 107 | * @param Request $request 108 | * @return \Illuminate\Http\JsonResponse 109 | */ 110 | public function getExampleTitle(Request $request) 111 | { 112 | $route = $request->get('route'); 113 | try { 114 | $manager = SeoManagerModel::find($route['id']); 115 | $titles = $route['title_dynamic']; 116 | $exampleTitle = $this->getDynamicTitle($titles, $manager); 117 | return response()->json(['example_title' => $exampleTitle]); 118 | } catch (\Exception $exception) { 119 | return response()->json(['status' => false, 'message' => $exception->getMessage()]); 120 | } 121 | } 122 | 123 | /** 124 | * @param Request $request 125 | * @return \Illuminate\Http\JsonResponse 126 | */ 127 | public function deleteRoute(Request $request) 128 | { 129 | try { 130 | SeoManagerModel::destroy($request->id); 131 | return response()->json(['deleted' => true]); 132 | } catch (\Exception $exception) { 133 | return response()->json(['status' => false, 'message' => $exception->getMessage()]); 134 | } 135 | } 136 | 137 | /** 138 | * @param Request $request 139 | * @return array|null 140 | */ 141 | public function sharingPreview(Request $request) 142 | { 143 | $id = $request->get('id'); 144 | $seoManager = SeoManagerModel::find($id); 145 | if(is_null($seoManager)){ 146 | return null; 147 | } 148 | $ogData = $this->getOgData($seoManager, null); 149 | return response()->json(['og_data' => $ogData]); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/Facades/SeoManager.php: -------------------------------------------------------------------------------- 1 | table = config('seo-manager.database.locales_table'); 19 | 20 | parent::__construct(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Models/SeoManager.php: -------------------------------------------------------------------------------- 1 | 'array', 27 | 'mapping' => 'array', 28 | 'keywords' => 'array', 29 | 'title_dynamic' => 'array', 30 | 'og_data' => 'array', 31 | ]; 32 | 33 | public function __construct() 34 | { 35 | $this->table = config('seo-manager.database.table'); 36 | $this->locale = app()->getLocale(); 37 | 38 | parent::__construct(); 39 | } 40 | 41 | public function translation() 42 | { 43 | return $this->hasOne(Translate::class, 'route_id', 'id')->where('locale', app()->getLocale()); 44 | } 45 | 46 | private function isNotDefaultLocale() 47 | { 48 | return $this->locale !== config('seo-manager.locale') && $this->has('translation'); 49 | } 50 | 51 | public function getKeywordsAttribute($value) 52 | { 53 | if ($this->isNotDefaultLocale() && !empty(optional($this->translation)->keywords)) { 54 | return $this->translation->keywords; 55 | } 56 | return json_decode($value); 57 | } 58 | 59 | public function getDescriptionAttribute($value) 60 | { 61 | if ($this->isNotDefaultLocale() && !is_null(optional($this->translation)->description)) { 62 | return $this->translation->description; 63 | } 64 | return $value; 65 | } 66 | 67 | public function getTitleAttribute($value) 68 | { 69 | if ($this->isNotDefaultLocale() && !is_null(optional($this->translation)->title)) { 70 | return $this->translation->title; 71 | } 72 | return $value; 73 | } 74 | 75 | public function getAuthorAttribute($value) 76 | { 77 | if ($this->isNotDefaultLocale() && !is_null(optional($this->translation)->author)) { 78 | return $this->translation->author; 79 | } 80 | return $value; 81 | } 82 | 83 | public function getUrlAttribute($value) 84 | { 85 | if ($this->isNotDefaultLocale() && !is_null(optional($this->translation)->url)) { 86 | return $this->translation->url; 87 | } 88 | return $value; 89 | } 90 | 91 | public function getTitleDynamicAttribute($value) 92 | { 93 | if ($this->isNotDefaultLocale() && !empty(optional($this->translation)->title_dynamic)) { 94 | return $this->translation->title_dynamic; 95 | } 96 | return json_decode($value); 97 | } 98 | 99 | public function getOgDataAttribute($value) 100 | { 101 | if ($this->isNotDefaultLocale() && !is_null(optional($this->translation)->og_data)) { 102 | return $this->translation->og_data; 103 | } 104 | return json_decode($value, true); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/Models/Translate.php: -------------------------------------------------------------------------------- 1 | 'array', 13 | 'title_dynamic' => 'array', 14 | 'og_data' => 'array', 15 | ]; 16 | 17 | public function __construct() 18 | { 19 | $this->table = config('seo-manager.database.translates_table'); 20 | 21 | parent::__construct(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/SeoManager.php: -------------------------------------------------------------------------------- 1 | getMetaData($property); 25 | } 26 | 27 | /** 28 | * Get Meta Keywords formatted 29 | * @return mixed 30 | */ 31 | public function metaKeywords() 32 | { 33 | return $this->getMetaData('keywords'); 34 | } 35 | 36 | /** 37 | * Get Title 38 | * @return mixed 39 | */ 40 | public function metaTitle() 41 | { 42 | return $this->getMetaData('title'); 43 | 44 | } 45 | 46 | /** 47 | * Get URL 48 | * @return mixed 49 | */ 50 | public function metaUrl() 51 | { 52 | return $this->getMetaData('url'); 53 | 54 | } 55 | 56 | /** 57 | * Get Meta Author 58 | * @return mixed 59 | */ 60 | public function metaAuthor() 61 | { 62 | return $this->getMetaData('author'); 63 | } 64 | 65 | /** 66 | * Get Meta Description 67 | * @return mixed 68 | */ 69 | public function metaDescription() 70 | { 71 | return $this->getMetaData('description'); 72 | } 73 | 74 | /** 75 | * Get dynamically generated title based on users mapping 76 | * @return mixed 77 | */ 78 | public function metaTitleDynamic() 79 | { 80 | return $this->getMetaData('title_dynamic'); 81 | } 82 | 83 | /** 84 | * Get Open Graph Data 85 | * @param $property 86 | * @return mixed 87 | */ 88 | public function metaOpenGraph($property = null) 89 | { 90 | $og_data = $this->getMetaData('og_data'); 91 | if (!is_null($property) && isset($og_data[$property])) { 92 | return $og_data[$property]; 93 | } 94 | return $this->getMetaData('og_data'); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/SeoManagerServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadRoutesFrom(__DIR__ . '/routes/seo-manager.php'); 19 | $this->loadMigrationsFrom(__DIR__ . '/migrations'); 20 | $this->loadViewsFrom(__DIR__ . '/views', 'seo-manager'); 21 | 22 | $this->publishes([ 23 | __DIR__ . '/config/seo-manager.php' => config_path('seo-manager.php'), 24 | ], 'config'); 25 | 26 | $this->publishes([ 27 | __DIR__ . '/assets' => public_path('vendor/lionix'), 28 | ], 'assets'); 29 | 30 | $this->commands([ 31 | GenerateSeoManagerData::class 32 | ]); 33 | 34 | $this->registerHelpers(); 35 | $router = $this->app['router']; 36 | $router->pushMiddlewareToGroup('web', \Lionix\SeoManager\Middleware\ClearViewCache::class); 37 | 38 | if (config('seo-manager.shared_meta_data')) { 39 | $router->pushMiddlewareToGroup('web', \Lionix\SeoManager\Middleware\SeoManager::class); 40 | } 41 | 42 | // Blade Directives 43 | $this->registerBladeDirectives(); 44 | } 45 | 46 | /** 47 | * Register services. 48 | * 49 | * @return void 50 | */ 51 | public function register() 52 | { 53 | $this->mergeConfigFrom( 54 | __DIR__ . '/config/seo-manager.php', 'seo-manager' 55 | ); 56 | $this->app->bind('seomanager', function () { 57 | return new SeoManager(); 58 | }); 59 | $this->app->alias('seomanager', SeoManager::class); 60 | } 61 | 62 | /** 63 | * Register helpers file 64 | */ 65 | public function registerHelpers() 66 | { 67 | // Load the helpers 68 | if (file_exists($file = __DIR__ . '/helpers/helpers.php')) { 69 | require $file; 70 | } 71 | } 72 | 73 | /** 74 | * Register Blade Directives 75 | */ 76 | public function registerBladeDirectives() 77 | { 78 | $names = [ 79 | 'keywords', 80 | 'url', 81 | 'author', 82 | 'description', 83 | 'title', 84 | ]; 85 | 86 | Blade::directive('meta', function ($expression) use ($names) { 87 | $meta = ''; 88 | $expression = trim($expression, '\"\''); 89 | $metaData = metaData($expression); 90 | $type = in_array($expression, $names) ? "name" : "property"; 91 | 92 | if (is_array($metaData)) { 93 | foreach ($metaData as $key => $og) { 94 | $type = in_array($key, $names) ? "name" : "property"; 95 | 96 | $meta .= "\n"; 97 | } 98 | } else { 99 | $meta .= "\n"; 100 | } 101 | return $meta; 102 | }); 103 | Blade::directive('keywords', function () { 104 | return "\n"; 105 | }); 106 | Blade::directive('url', function () { 107 | return "\n"; 108 | }); 109 | Blade::directive('author', function () { 110 | return "\n"; 111 | }); 112 | Blade::directive('description', function () { 113 | return "\n"; 114 | }); 115 | Blade::directive('title', function () { 116 | return "\n"; 117 | }); 118 | Blade::directive('openGraph', function ($expression) { 119 | $expression = trim($expression, '\"\''); 120 | $meta = ''; 121 | $metaOpenGraph = metaOpenGraph($expression); 122 | if (is_array($metaOpenGraph)) { 123 | foreach ($metaOpenGraph as $key => $og) { 124 | $meta .= "\n"; 125 | } 126 | } else { 127 | $meta .= "\n"; 128 | } 129 | return $meta; 130 | }); 131 | Blade::directive('titleDynamic', function () { 132 | return metaTitleDynamic(); 133 | }); 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /src/Traits/Appends.php: -------------------------------------------------------------------------------- 1 | appends; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Traits/SeoManagerTrait.php: -------------------------------------------------------------------------------- 1 | exceptRoutes = array_merge($this->exceptRoutes, config('seo-manager.except_routes')); 31 | $this->exceptColumns = array_merge($this->exceptColumns, config('seo-manager.except_columns')); 32 | } 33 | 34 | /** 35 | * Detect Parameters from the URI 36 | * @param $uri 37 | * @return mixed 38 | */ 39 | private function getParamsFromURI($uri) 40 | { 41 | preg_match_all('/{(.*?)}/', $uri, $output_array); 42 | 43 | return $output_array[1]; 44 | } 45 | 46 | /** 47 | * Check if the given data is URI param 48 | * @param $param 49 | * @return bool 50 | */ 51 | private function isParam($param) 52 | { 53 | $pattern_params = '/{(.*?)}/'; 54 | preg_match_all($pattern_params, $param, $output_params); 55 | if (!empty($output_params[1])) { 56 | return true; 57 | } 58 | return false; 59 | } 60 | 61 | /** 62 | * Check if the given data is Title 63 | * @param $param 64 | * @return bool 65 | */ 66 | private function isTitle($param) 67 | { 68 | $pattern_title = '/~(.*?)~/'; 69 | preg_match_all($pattern_title, $param, $pattern_title); 70 | if (!empty($pattern_title[1])) { 71 | return true; 72 | } 73 | return false; 74 | } 75 | 76 | /** 77 | * Remove unnecessary characters from param 78 | * @param $param 79 | * @return string 80 | */ 81 | private function cleanParam($param) 82 | { 83 | return strtolower(str_replace(['{', '}'], '', $param)); 84 | } 85 | 86 | /** 87 | * Remove routes which shouldn't be imported to Seo Manager 88 | * @return array 89 | */ 90 | private function cleanRoutes() 91 | { 92 | $routes = \Route::getRoutes(); 93 | $getRoutes = array_keys($routes->get('GET')); 94 | foreach ($getRoutes as $key => $route) { 95 | foreach ($this->exceptRoutes as $rule) { 96 | if (strpos($route, $rule) !== FALSE) { 97 | unset($getRoutes[$key]); 98 | } 99 | } 100 | } 101 | return $getRoutes; 102 | } 103 | 104 | /** 105 | * @return array 106 | * @throws \ReflectionException 107 | */ 108 | private function getAllModels() 109 | { 110 | $path = base_path('app') . '/' . config('seo-manager.models_path'); 111 | 112 | $models = File::allFiles($path); 113 | $cleanModelNames = []; 114 | foreach ($models as $model) { 115 | $modelPath = $this->cleanFilePath($model); 116 | $reflectionClass =(new \ReflectionClass($modelPath))->getParentClass(); 117 | if($reflectionClass !== false){ 118 | if($reflectionClass->getName() === "Illuminate\Database\Eloquent\Model" || $reflectionClass->getName() === "Illuminate\Foundation\Auth\User"){ 119 | $cleanModel = [ 120 | 'path' => $modelPath, 121 | 'name' => str_replace('.php', '', $model->getFilename()) 122 | ]; 123 | array_push($cleanModelNames, $cleanModel); 124 | } 125 | } 126 | } 127 | return $cleanModelNames; 128 | } 129 | 130 | /** 131 | * Get Model all Columns 132 | * @param $model 133 | * @return array 134 | */ 135 | public function getColumns($model) 136 | { 137 | $appends = []; 138 | if (method_exists((new $model), 'getAppends')) { 139 | $appends = (new $model)->getAppends(); 140 | } 141 | $table = (new $model)->getTable(); 142 | $columns = $this->getCleanColumns(Schema::getColumnListing($table)); 143 | return array_merge($columns, $appends); 144 | } 145 | 146 | /** 147 | * Clean model file path 148 | * @param $file 149 | * @return string 150 | */ 151 | private function cleanFilePath($file) 152 | { 153 | return '\\' . ucfirst(str_replace('/', '\\', substr($file, strpos($file, 'app'), -4))); 154 | } 155 | 156 | /** 157 | * Remove unnecessary columns from table columns list 158 | * @param $columns 159 | * @return array 160 | */ 161 | private function getCleanColumns($columns) 162 | { 163 | return array_diff($columns, $this->exceptColumns); 164 | } 165 | 166 | /** 167 | * Import Routes to SeoManager database 168 | * @return array|\Illuminate\Database\Eloquent\Collection|static[] 169 | */ 170 | private function importRoutes() 171 | { 172 | $routes = $this->cleanRoutes(); 173 | foreach ($routes as $uri) { 174 | $data = [ 175 | 'uri' => $uri, 176 | 'params' => $this->getParamsFromURI($uri), 177 | 'keywords' => [], 178 | 'title_dynamic' => [] 179 | ]; 180 | if (!SeoManager::where('uri', $uri)->first()) { 181 | $seoManager = new SeoManager(); 182 | $seoManager->fill($data); 183 | $seoManager->save(); 184 | } 185 | } 186 | 187 | $routes = SeoManager::all(); 188 | return $routes; 189 | } 190 | 191 | /** 192 | * Get mapped Seo Data from Database for Current Route 193 | * @param $property 194 | * @return mixed 195 | */ 196 | private function getMetaData($property) 197 | { 198 | $route = \Route::current(); 199 | if (is_null($route)){ 200 | return null; 201 | } 202 | $uri = $route->uri(); 203 | $seoManager = SeoManager::where('uri', $uri)->first(); 204 | if(is_null($seoManager)){ 205 | return null; 206 | } 207 | $metaData = []; 208 | if(count($seoManager->keywords) > 0){ 209 | $metaData['keywords'] = implode(', ', $seoManager->keywords); 210 | } 211 | if($seoManager->description){ 212 | $metaData['description'] = $seoManager->description; 213 | } 214 | if($seoManager->title){ 215 | $metaData['title'] = $seoManager->title; 216 | } 217 | if($seoManager->url){ 218 | $metaData['url'] = $seoManager->url; 219 | }else{ 220 | $metaData['url'] = url()->full(); 221 | } 222 | if($seoManager->author){ 223 | $metaData['author'] = $seoManager->author; 224 | } 225 | if ($seoManager->mapping !== null) { 226 | $metaData['title_dynamic'] = $this->getDynamicTitle($seoManager->title_dynamic, $seoManager, $route->parameters); 227 | } 228 | if ($seoManager->og_data) { 229 | $ogData = $this->getOgData($seoManager, $route->parameters); 230 | if($property === 'og_data'){ 231 | $metaData['og_data'] = $ogData; 232 | }else{ 233 | foreach ($ogData as $key => $og) { 234 | $metaData[$key] = $og; 235 | } 236 | } 237 | } 238 | 239 | if($property !== null && !empty($property)){ 240 | if(isset($metaData[$property])){ 241 | return $metaData[$property]; 242 | }else{ 243 | return null; 244 | } 245 | } 246 | return $metaData; 247 | } 248 | 249 | /** 250 | * Get dynamic title based on user configs for current route 251 | * @param $params 252 | * @param $manager 253 | * @param $routeParams 254 | * @return string 255 | */ 256 | private function getDynamicTitle($params, $manager, $routeParams = null) 257 | { 258 | $dynamicTitle = ''; 259 | if(is_array($params)){ 260 | foreach ($params as $param) { 261 | if ($this->isParam($param)) { 262 | $param = $this->cleanParam($param); 263 | $paramsArray = explode('-', $param); 264 | $mapping = $manager->mapping[$paramsArray[0]]; 265 | $model = $mapping['model']['path']; 266 | $findBy = $mapping['find_by']; 267 | $selectedColumns = $mapping['selectedColumns']; 268 | 269 | if (in_array($paramsArray[1], $selectedColumns)) { 270 | $mappedTitle = (new $model); 271 | 272 | if ($routeParams) { 273 | $value = is_a($routeParams[$paramsArray[0]], $model) ? $routeParams[$paramsArray[0]]->$findBy : $routeParams[$paramsArray[0]]; 274 | $mappedTitle = $mappedTitle->where($findBy, $value)->first(); 275 | } else { 276 | $mappedTitle = $mappedTitle->first(); 277 | } 278 | 279 | if ($mappedTitle) { 280 | $dynamicTitle .= optional($mappedTitle)->{$paramsArray[1]} . ' '; 281 | } 282 | } 283 | } elseif ($this->isTitle($param)) { 284 | $dynamicTitle .= $manager->title . ' '; 285 | } else { 286 | $dynamicTitle .= $param . ' '; 287 | } 288 | } 289 | } 290 | return $dynamicTitle; 291 | } 292 | 293 | /** 294 | * Get Open Graph Dynamic Data 295 | * @param $seoManager 296 | * @param $routeParams 297 | * @return array 298 | */ 299 | private function getOgData($seoManager, $routeParams) 300 | { 301 | $dataArray = []; 302 | $value = ''; 303 | foreach ($seoManager->og_data as $key => $og) { 304 | if (is_array(reset($og['data']))) { 305 | foreach ($og['data'] as $ogKey => $data) { 306 | if ($data['mapped']) { 307 | $value = $this->getMappedValue($data['value'], $seoManager, $routeParams); 308 | } elseif ($data['value']) { 309 | $value = $data['value']; 310 | } 311 | if ($data['value']) { 312 | $dataArray['og:' . $key . ':' . $ogKey] = $value; 313 | } 314 | } 315 | } else { 316 | if ($og['data']['mapped']) { 317 | $value = $this->getMappedValue($og['data']['value'], $seoManager, $routeParams); 318 | } elseif ($og['data']['value']) { 319 | $value = $og['data']['value']; 320 | } elseif ($key === 'url') { 321 | $value = url()->full(); 322 | } 323 | if ($og['data']['value'] || $key === 'url') { 324 | $dataArray['og:' . $key] = $value; 325 | } 326 | } 327 | } 328 | return $dataArray; 329 | } 330 | 331 | /** 332 | * Get Open Graph Data Values based on Mapped Params 333 | * @param $value 334 | * @param $manager 335 | * @param $routeParams 336 | * @return mixed 337 | */ 338 | private function getMappedValue($value, $manager, $routeParams) 339 | { 340 | $paramsArray = explode('-', strtolower($value)); 341 | $mapping = $manager->mapping[$paramsArray[0]]; 342 | $model = $mapping['model']['path']; 343 | $findBy = $mapping['find_by']; 344 | $selectedColumns = $mapping['selectedColumns']; 345 | $mapped = null; 346 | if (in_array($paramsArray[1], $selectedColumns)) { 347 | $mapped = (new $model); 348 | if ($routeParams) { 349 | $mapped = $mapped->where($findBy, $routeParams[$paramsArray[0]])->first(); 350 | }else{ 351 | $mapped = $mapped->first(); 352 | } 353 | } 354 | return optional($mapped)->{$paramsArray[1]}; 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /src/assets/fonts/cerebrisans/cerebrisans-medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lionix-team/seo-manager/23761959505464044b13db04e6e10e5af2f74c7e/src/assets/fonts/cerebrisans/cerebrisans-medium.eot -------------------------------------------------------------------------------- /src/assets/fonts/cerebrisans/cerebrisans-medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lionix-team/seo-manager/23761959505464044b13db04e6e10e5af2f74c7e/src/assets/fonts/cerebrisans/cerebrisans-medium.ttf -------------------------------------------------------------------------------- /src/assets/fonts/cerebrisans/cerebrisans-medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lionix-team/seo-manager/23761959505464044b13db04e6e10e5af2f74c7e/src/assets/fonts/cerebrisans/cerebrisans-medium.woff -------------------------------------------------------------------------------- /src/assets/fonts/cerebrisans/cerebrisans-mediumd41d.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lionix-team/seo-manager/23761959505464044b13db04e6e10e5af2f74c7e/src/assets/fonts/cerebrisans/cerebrisans-mediumd41d.eot -------------------------------------------------------------------------------- /src/assets/fonts/cerebrisans/cerebrisans-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lionix-team/seo-manager/23761959505464044b13db04e6e10e5af2f74c7e/src/assets/fonts/cerebrisans/cerebrisans-regular.eot -------------------------------------------------------------------------------- /src/assets/fonts/cerebrisans/cerebrisans-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lionix-team/seo-manager/23761959505464044b13db04e6e10e5af2f74c7e/src/assets/fonts/cerebrisans/cerebrisans-regular.ttf -------------------------------------------------------------------------------- /src/assets/fonts/cerebrisans/cerebrisans-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lionix-team/seo-manager/23761959505464044b13db04e6e10e5af2f74c7e/src/assets/fonts/cerebrisans/cerebrisans-regular.woff -------------------------------------------------------------------------------- /src/assets/fonts/cerebrisans/cerebrisans-regulard41d.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lionix-team/seo-manager/23761959505464044b13db04e6e10e5af2f74c7e/src/assets/fonts/cerebrisans/cerebrisans-regulard41d.eot -------------------------------------------------------------------------------- /src/assets/fonts/cerebrisans/cerebrisans-semibold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lionix-team/seo-manager/23761959505464044b13db04e6e10e5af2f74c7e/src/assets/fonts/cerebrisans/cerebrisans-semibold.eot -------------------------------------------------------------------------------- /src/assets/fonts/cerebrisans/cerebrisans-semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lionix-team/seo-manager/23761959505464044b13db04e6e10e5af2f74c7e/src/assets/fonts/cerebrisans/cerebrisans-semibold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/cerebrisans/cerebrisans-semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lionix-team/seo-manager/23761959505464044b13db04e6e10e5af2f74c7e/src/assets/fonts/cerebrisans/cerebrisans-semibold.woff -------------------------------------------------------------------------------- /src/assets/fonts/cerebrisans/cerebrisans-semiboldd41d.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lionix-team/seo-manager/23761959505464044b13db04e6e10e5af2f74c7e/src/assets/fonts/cerebrisans/cerebrisans-semiboldd41d.eot -------------------------------------------------------------------------------- /src/assets/fonts/feather/feather.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:Feather;src:url(fonts/Feather144f.ttf?sdxovp) format("truetype"),url(fonts/Feather144f.woff?sdxovp) format("woff"),url(fonts/Feather144f.svg?sdxovp#Feather) format("svg");font-weight:400;font-style:normal}.fe{font-family:Feather!important;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fe-activity:before{content:"\e900"}.fe-airplay:before{content:"\e901"}.fe-alert-circle:before{content:"\e902"}.fe-alert-octagon:before{content:"\e903"}.fe-alert-triangle:before{content:"\e904"}.fe-align-center:before{content:"\e905"}.fe-align-justify:before{content:"\e906"}.fe-align-left:before{content:"\e907"}.fe-align-right:before{content:"\e908"}.fe-anchor:before{content:"\e909"}.fe-aperture:before{content:"\e90a"}.fe-archive:before{content:"\e90b"}.fe-arrow-down:before{content:"\e90c"}.fe-arrow-down-circle:before{content:"\e90d"}.fe-arrow-down-left:before{content:"\e90e"}.fe-arrow-down-right:before{content:"\e90f"}.fe-arrow-left:before{content:"\e910"}.fe-arrow-left-circle:before{content:"\e911"}.fe-arrow-right:before{content:"\e912"}.fe-arrow-right-circle:before{content:"\e913"}.fe-arrow-up:before{content:"\e914"}.fe-arrow-up-circle:before{content:"\e915"}.fe-arrow-up-left:before{content:"\e916"}.fe-arrow-up-right:before{content:"\e917"}.fe-at-sign:before{content:"\e918"}.fe-award:before{content:"\e919"}.fe-bar-chart:before{content:"\e91a"}.fe-bar-chart-2:before{content:"\e91b"}.fe-battery:before{content:"\e91c"}.fe-battery-charging:before{content:"\e91d"}.fe-bell:before{content:"\e91e"}.fe-bell-off:before{content:"\e91f"}.fe-bluetooth:before{content:"\e920"}.fe-bold:before{content:"\e921"}.fe-book:before{content:"\e922"}.fe-book-open:before{content:"\e923"}.fe-bookmark:before{content:"\e924"}.fe-box:before{content:"\e925"}.fe-briefcase:before{content:"\e926"}.fe-calendar:before{content:"\e927"}.fe-camera:before{content:"\e928"}.fe-camera-off:before{content:"\e929"}.fe-cast:before{content:"\e92a"}.fe-check:before{content:"\e92b"}.fe-check-circle:before{content:"\e92c"}.fe-check-square:before{content:"\e92d"}.fe-chevron-down:before{content:"\e92e"}.fe-chevron-left:before{content:"\e92f"}.fe-chevron-right:before{content:"\e930"}.fe-chevron-up:before{content:"\e931"}.fe-chevrons-down:before{content:"\e932"}.fe-chevrons-left:before{content:"\e933"}.fe-chevrons-right:before{content:"\e934"}.fe-chevrons-up:before{content:"\e935"}.fe-chrome:before{content:"\e936"}.fe-circle:before{content:"\e937"}.fe-clipboard:before{content:"\e938"}.fe-clock:before{content:"\e939"}.fe-cloud:before{content:"\e93a"}.fe-cloud-drizzle:before{content:"\e93b"}.fe-cloud-lightning:before{content:"\e93c"}.fe-cloud-off:before{content:"\e93d"}.fe-cloud-rain:before{content:"\e93e"}.fe-cloud-snow:before{content:"\e93f"}.fe-code:before{content:"\e940"}.fe-codepen:before{content:"\e941"}.fe-command:before{content:"\e942"}.fe-compass:before{content:"\e943"}.fe-copy:before{content:"\e944"}.fe-corner-down-left:before{content:"\e945"}.fe-corner-down-right:before{content:"\e946"}.fe-corner-left-down:before{content:"\e947"}.fe-corner-left-up:before{content:"\e948"}.fe-corner-right-down:before{content:"\e949"}.fe-corner-right-up:before{content:"\e94a"}.fe-corner-up-left:before{content:"\e94b"}.fe-corner-up-right:before{content:"\e94c"}.fe-cpu:before{content:"\e94d"}.fe-credit-card:before{content:"\e94e"}.fe-crop:before{content:"\e94f"}.fe-crosshair:before{content:"\e950"}.fe-database:before{content:"\e951"}.fe-delete:before{content:"\e952"}.fe-disc:before{content:"\e953"}.fe-dollar-sign:before{content:"\e954"}.fe-download:before{content:"\e955"}.fe-download-cloud:before{content:"\e956"}.fe-droplet:before{content:"\e957"}.fe-edit:before{content:"\e958"}.fe-edit-2:before{content:"\e959"}.fe-edit-3:before{content:"\e95a"}.fe-external-link:before{content:"\e95b"}.fe-eye:before{content:"\e95c"}.fe-eye-off:before{content:"\e95d"}.fe-facebook:before{content:"\e95e"}.fe-fast-forward:before{content:"\e95f"}.fe-feather:before{content:"\e960"}.fe-file:before{content:"\e961"}.fe-file-minus:before{content:"\e962"}.fe-file-plus:before{content:"\e963"}.fe-file-text:before{content:"\e964"}.fe-film:before{content:"\e965"}.fe-filter:before{content:"\e966"}.fe-flag:before{content:"\e967"}.fe-folder:before{content:"\e968"}.fe-folder-minus:before{content:"\e969"}.fe-folder-plus:before{content:"\e96a"}.fe-gift:before{content:"\e96b"}.fe-git-branch:before{content:"\e96c"}.fe-git-commit:before{content:"\e96d"}.fe-git-merge:before{content:"\e96e"}.fe-git-pull-request:before{content:"\e96f"}.fe-github:before{content:"\e970"}.fe-gitlab:before{content:"\e971"}.fe-globe:before{content:"\e972"}.fe-grid:before{content:"\e973"}.fe-hard-drive:before{content:"\e974"}.fe-hash:before{content:"\e975"}.fe-headphones:before{content:"\e976"}.fe-heart:before{content:"\e977"}.fe-help-circle:before{content:"\e978"}.fe-home:before{content:"\e979"}.fe-image:before{content:"\e97a"}.fe-inbox:before{content:"\e97b"}.fe-info:before{content:"\e97c"}.fe-instagram:before{content:"\e97d"}.fe-italic:before{content:"\e97e"}.fe-layers:before{content:"\e97f"}.fe-layout:before{content:"\e980"}.fe-life-buoy:before{content:"\e981"}.fe-link:before{content:"\e982"}.fe-link-2:before{content:"\e983"}.fe-linkedin:before{content:"\e984"}.fe-list:before{content:"\e985"}.fe-loader:before{content:"\e986"}.fe-lock:before{content:"\e987"}.fe-log-in:before{content:"\e988"}.fe-log-out:before{content:"\e989"}.fe-mail:before{content:"\e98a"}.fe-map:before{content:"\e98b"}.fe-map-pin:before{content:"\e98c"}.fe-maximize:before{content:"\e98d"}.fe-maximize-2:before{content:"\e98e"}.fe-menu:before{content:"\e98f"}.fe-message-circle:before{content:"\e990"}.fe-message-square:before{content:"\e991"}.fe-mic:before{content:"\e992"}.fe-mic-off:before{content:"\e993"}.fe-minimize:before{content:"\e994"}.fe-minimize-2:before{content:"\e995"}.fe-minus:before{content:"\e996"}.fe-minus-circle:before{content:"\e997"}.fe-minus-square:before{content:"\e998"}.fe-monitor:before{content:"\e999"}.fe-moon:before{content:"\e99a"}.fe-more-horizontal:before{content:"\e99b"}.fe-more-vertical:before{content:"\e99c"}.fe-move:before{content:"\e99d"}.fe-music:before{content:"\e99e"}.fe-navigation:before{content:"\e99f"}.fe-navigation-2:before{content:"\e9a0"}.fe-octagon:before{content:"\e9a1"}.fe-package:before{content:"\e9a2"}.fe-paperclip:before{content:"\e9a3"}.fe-pause:before{content:"\e9a4"}.fe-pause-circle:before{content:"\e9a5"}.fe-percent:before{content:"\e9a6"}.fe-phone:before{content:"\e9a7"}.fe-phone-call:before{content:"\e9a8"}.fe-phone-forwarded:before{content:"\e9a9"}.fe-phone-incoming:before{content:"\e9aa"}.fe-phone-missed:before{content:"\e9ab"}.fe-phone-off:before{content:"\e9ac"}.fe-phone-outgoing:before{content:"\e9ad"}.fe-pie-chart:before{content:"\e9ae"}.fe-play:before{content:"\e9af"}.fe-play-circle:before{content:"\e9b0"}.fe-plus:before{content:"\e9b1"}.fe-plus-circle:before{content:"\e9b2"}.fe-plus-square:before{content:"\e9b3"}.fe-pocket:before{content:"\e9b4"}.fe-power:before{content:"\e9b5"}.fe-printer:before{content:"\e9b6"}.fe-radio:before{content:"\e9b7"}.fe-refresh-ccw:before{content:"\e9b8"}.fe-refresh-cw:before{content:"\e9b9"}.fe-repeat:before{content:"\e9ba"}.fe-rewind:before{content:"\e9bb"}.fe-rotate-ccw:before{content:"\e9bc"}.fe-rotate-cw:before{content:"\e9bd"}.fe-rss:before{content:"\e9be"}.fe-save:before{content:"\e9bf"}.fe-scissors:before{content:"\e9c0"}.fe-search:before{content:"\e9c1"}.fe-send:before{content:"\e9c2"}.fe-server:before{content:"\e9c3"}.fe-settings:before{content:"\e9c4"}.fe-share:before{content:"\e9c5"}.fe-share-2:before{content:"\e9c6"}.fe-shield:before{content:"\e9c7"}.fe-shield-off:before{content:"\e9c8"}.fe-shopping-bag:before{content:"\e9c9"}.fe-shopping-cart:before{content:"\e9ca"}.fe-shuffle:before{content:"\e9cb"}.fe-sidebar:before{content:"\e9cc"}.fe-skip-back:before{content:"\e9cd"}.fe-skip-forward:before{content:"\e9ce"}.fe-slack:before{content:"\e9cf"}.fe-slash:before{content:"\e9d0"}.fe-sliders:before{content:"\e9d1"}.fe-smartphone:before{content:"\e9d2"}.fe-speaker:before{content:"\e9d3"}.fe-square:before{content:"\e9d4"}.fe-star:before{content:"\e9d5"}.fe-stop-circle:before{content:"\e9d6"}.fe-sun:before{content:"\e9d7"}.fe-sunrise:before{content:"\e9d8"}.fe-sunset:before{content:"\e9d9"}.fe-tablet:before{content:"\e9da"}.fe-tag:before{content:"\e9db"}.fe-target:before{content:"\e9dc"}.fe-terminal:before{content:"\e9dd"}.fe-thermometer:before{content:"\e9de"}.fe-thumbs-down:before{content:"\e9df"}.fe-thumbs-up:before{content:"\e9e0"}.fe-toggle-left:before{content:"\e9e1"}.fe-toggle-right:before{content:"\e9e2"}.fe-trash:before{content:"\e9e3"}.fe-trash-2:before{content:"\e9e4"}.fe-trending-down:before{content:"\e9e5"}.fe-trending-up:before{content:"\e9e6"}.fe-triangle:before{content:"\e9e7"}.fe-truck:before{content:"\e9e8"}.fe-tv:before{content:"\e9e9"}.fe-twitter:before{content:"\e9ea"}.fe-type:before{content:"\e9eb"}.fe-umbrella:before{content:"\e9ec"}.fe-underline:before{content:"\e9ed"}.fe-unlock:before{content:"\e9ee"}.fe-upload:before{content:"\e9ef"}.fe-upload-cloud:before{content:"\e9f0"}.fe-user:before{content:"\e9f1"}.fe-user-check:before{content:"\e9f2"}.fe-user-minus:before{content:"\e9f3"}.fe-user-plus:before{content:"\e9f4"}.fe-user-x:before{content:"\e9f5"}.fe-users:before{content:"\e9f6"}.fe-video:before{content:"\e9f7"}.fe-video-off:before{content:"\e9f8"}.fe-voicemail:before{content:"\e9f9"}.fe-volume:before{content:"\e9fa"}.fe-volume-1:before{content:"\e9fb"}.fe-volume-2:before{content:"\e9fc"}.fe-volume-x:before{content:"\e9fd"}.fe-watch:before{content:"\e9fe"}.fe-wifi:before{content:"\e9ff"}.fe-wifi-off:before{content:"\ea00"}.fe-wind:before{content:"\ea01"}.fe-x:before{content:"\ea02"}.fe-x-circle:before{content:"\ea03"}.fe-x-square:before{content:"\ea04"}.fe-youtube:before{content:"\ea05"}.fe-zap:before{content:"\ea06"}.fe-zap-off:before{content:"\ea07"}.fe-zoom-in:before{content:"\ea08"}.fe-zoom-out:before{content:"\ea09"} -------------------------------------------------------------------------------- /src/assets/fonts/feather/fonts/Feather144f.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lionix-team/seo-manager/23761959505464044b13db04e6e10e5af2f74c7e/src/assets/fonts/feather/fonts/Feather144f.ttf -------------------------------------------------------------------------------- /src/assets/fonts/feather/fonts/Feather144f.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lionix-team/seo-manager/23761959505464044b13db04e6e10e5af2f74c7e/src/assets/fonts/feather/fonts/Feather144f.woff -------------------------------------------------------------------------------- /src/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lionix-team/seo-manager/23761959505464044b13db04e6e10e5af2f74c7e/src/assets/img/logo.png -------------------------------------------------------------------------------- /src/assets/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/img/nophoto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lionix-team/seo-manager/23761959505464044b13db04e6e10e5af2f74c7e/src/assets/img/nophoto.png -------------------------------------------------------------------------------- /src/assets/js/theme.min.js: -------------------------------------------------------------------------------- 1 | "use strict";var Demo=function(){var n=document.querySelector("#demoForm"),s=document.querySelector("#topnav"),i=document.querySelector("#topbar"),c=document.querySelector("#sidebar"),d=document.querySelector("#sidebarUser"),u=document.querySelectorAll('[class^="container"]'),p=(document.querySelectorAll("#stylesheetLight, #stylesheetDark"),document.querySelector("#stylesheetLight")),h=document.querySelector("#stylesheetDark"),b=localStorage.getItem("dashkitNavPosition")?localStorage.getItem("dashkitNavPosition"):"sidenav",v=localStorage.getItem("dashkitSidebarColor")?localStorage.getItem("dashkitSidebarColor"):"default";document.addEventListener("DOMContentLoaded",function(){var a,e,t,o,r,l;"light"==(a=colorScheme)?(p.disabled=!1,h.disabled=!0):"dark"==a&&(p.disabled=!0,h.disabled=!1),function(a){if(s&&i&&c&&d)if("topnav"==a){t(i),t(c);for(var e=0;e"),r+=''+l+o+n+""}}}(a,t),t.update()}return window.Chart&&r(Chart,(a={defaults:{global:{responsive:!0,maintainAspectRatio:!1,defaultColor:"dark"==colorScheme?o.gray[700]:o.gray[600],defaultFontColor:"dark"==colorScheme?o.gray[700]:o.gray[600],defaultFontFamily:t.base,defaultFontSize:13,layout:{padding:0},legend:{display:!1,position:"bottom",labels:{usePointStyle:!0,padding:16}},elements:{point:{radius:0,backgroundColor:o.primary[700]},line:{tension:.4,borderWidth:3,borderColor:o.primary[700],backgroundColor:o.transparent,borderCapStyle:"rounded"},rectangle:{backgroundColor:o.primary[700]},arc:{backgroundColor:o.primary[700],borderColor:"dark"==colorScheme?o.gray[800]:o.white,borderWidth:4}},tooltips:{enabled:!1,mode:"index",intersect:!1,custom:function(r){var a=$("#chart-tooltip");if(a.length||(a=$(''),$("body").append(a)),0!==r.opacity){if(r.body){var e=r.title||[],l=r.body.map(function(a){return a.lines}),n="";n+='
',e.forEach(function(a){n+='

'+a+"

"}),l.forEach(function(a,e){var t='',o=1'+t+a+""}),a.html(n)}var t=$(this._chart.canvas),o=(t.outerWidth(),t.outerHeight(),t.offset().top),s=t.offset().left,i=a.outerWidth(),c=a.outerHeight(),d=o+r.caretY-c-16,u=s+r.caretX-i/2;a.css({top:d+"px",left:u+"px",display:"block"})}else a.css("display","none")},callbacks:{label:function(a,e){var t=e.datasets[a.datasetIndex].label||"",o=a.yLabel,r="";return 1'+t+""),r+=''+o+""}}}},doughnut:{cutoutPercentage:83,tooltips:{callbacks:{title:function(a,e){return e.labels[a[0].index]},label:function(a,e){var t="";return t+=''+e.datasets[0].data[a.index]+""}}},legendCallback:function(a){var o=a.data,r="";return o.labels.forEach(function(a,e){var t=o.datasets[0].backgroundColor[e];r+='',r+='',r+=a,r+=""}),r}}}},Chart.scaleService.updateScaleDefaults("linear",{gridLines:{borderDash:[2],borderDashOffset:[2],color:"dark"==colorScheme?o.gray[900]:o.gray[300],drawBorder:!1,drawTicks:!1,lineWidth:0,zeroLineWidth:0,zeroLineColor:"dark"==colorScheme?o.gray[900]:o.gray[300],zeroLineBorderDash:[2],zeroLineBorderDashOffset:[2]},ticks:{beginAtZero:!0,padding:10,callback:function(a){if(!(a%10))return a}}}),Chart.scaleService.updateScaleDefaults("category",{gridLines:{drawBorder:!1,drawOnChartArea:!1,drawTicks:!1},ticks:{padding:20},maxBarThickness:10}),a)),e.on({change:function(){var a=$(this);a.is("[data-add]")&&l(a)},click:function(){var a=$(this);a.is("[data-update]")&&n(a)}}),{colors:o,fonts:t,colorScheme:colorScheme}}(),Header=function(){var a,e,t=$("#headerChart");t.length&&(a=t,e=new Chart(a,{type:"line",options:{scales:{yAxes:[{gridLines:{color:ThemeCharts.colors.gray[900],zeroLineColor:ThemeCharts.colors.gray[900]},ticks:{callback:function(a){if(!(a%10))return"$"+a+"k"}}}]},tooltips:{callbacks:{label:function(a,e){var t=e.datasets[a.datasetIndex].label||"",o=a.yLabel,r="";return 1'+t+""),r+='$'+o+"k"}}}},data:{labels:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],datasets:[{label:"Performance",data:[0,10,5,15,10,20,15,25,20,30,25,40]}]}}),a.data("chart",e))}(),Performance=function(){var a,e,t=$("#performanceChart");t.length&&(a=t,e=new Chart(a,{type:"line",options:{scales:{yAxes:[{ticks:{callback:function(a){if(!(a%10))return"$"+a+"k"}}}]},tooltips:{callbacks:{label:function(a,e){var t=e.datasets[a.datasetIndex].label||"",o=a.yLabel,r="";return 1'+t+""),r+='$'+o+"k"}}}},data:{labels:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],datasets:[{label:"Performance",data:[0,10,5,15,10,20,15,25,20,30,25,40]}]}}),a.data("chart",e))}(),PerformanceAlias=function(){var a,e,t=$("#performanceChartAlias");t.length&&(a=t,e=new Chart(a,{type:"line",options:{scales:{yAxes:[{ticks:{callback:function(a){if(!(a%10))return"$"+a+"k"}}}]},tooltips:{callbacks:{label:function(a,e){var t=e.datasets[a.datasetIndex].label||"",o=a.yLabel,r="";return 1'+t+""),r+='$'+o+"k"}}}},data:{labels:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],datasets:[{label:"Performance",data:[0,10,5,15,10,20,15,25,20,30,25,40]}]}}),a.data("chart",e))}(),Orders=function(){var a,e,t=$("#ordersChart"),o=$('[name="ordersSelect"]');t.length&&(a=t,e=new Chart(a,{type:"bar",options:{scales:{yAxes:[{ticks:{callback:function(a){if(!(a%10))return"$"+a+"k"}}}]},tooltips:{callbacks:{label:function(a,e){var t=e.datasets[a.datasetIndex].label||"",o=a.yLabel,r="";return 1'+t+""),r+='$'+o+"k"}}}},data:{labels:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],datasets:[{label:"Sales",data:[25,20,30,22,17,10,18,26,28,26,20,32]}]}}),a.data("chart",e)),o.on("change",function(){var a;"ordersSelectAll"==(a=$(this)).attr("id")&&(a.is(":checked")?o.prop("checked",!0):o.prop("checked",!1))})}(),OrdersAlias=function(){var a,e,t=$("#ordersChartAlias");t.length&&(a=t,e=new Chart(a,{type:"bar",options:{scales:{yAxes:[{ticks:{callback:function(a){if(!(a%10))return"$"+a+"k"}}}]},tooltips:{callbacks:{label:function(a,e){var t=e.datasets[a.datasetIndex].label||"",o=a.yLabel,r="";return 1'+t+""),r+='$'+o+"k"}}}},data:{labels:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],datasets:[{label:"Sales",data:[25,20,30,22,17,10,18,26,28,26,20,32]}]}}),a.data("chart",e))}(),Devices=function(){var a,e,t,o,r,l=$("#devicesChart");l.length&&(o=l,r=new Chart(o,{type:"doughnut",options:{tooltips:{callbacks:{title:function(a,e){return e.labels[a[0].index]},label:function(a,e){var t="";return t+=''+e.datasets[0].data[a.index]+"%"}}}},data:{labels:["Desktop","Tablet","Mobile"],datasets:[{data:[60,25,15],backgroundColor:[ThemeCharts.colors.primary[700],ThemeCharts.colors.primary[300],ThemeCharts.colors.primary[100]],hoverBorderColor:"dark"==ThemeCharts.colorScheme?ThemeCharts.colors.gray[800]:ThemeCharts.colors.white}]}}),o.data("chart",r),e=(a=l).data("chart").generateLegend(),t=a.data("target"),$(t).html(e))}(),WeeklyHours=function(){var a,e,t=$("#weeklyHoursChart");t.length&&(a=t,e=new Chart(a,{type:"bar",options:{scales:{yAxes:[{ticks:{callback:function(a){if(!(a%10))return a+"hrs"}}}]},tooltips:{callbacks:{label:function(a,e){var t=e.datasets[a.datasetIndex].label||"",o=a.yLabel,r="";return 1'+t+""),r+=''+o+"hrs"}}}},data:{labels:["Mon","Tue","Wed","Thu","Fri","Sat","Sun"],datasets:[{data:[21,12,28,15,5,12,17,2]}]}}),a.data("chart",e))}(),Dropdowns=function(){var e=$(".dropup, .dropright, .dropdown, .dropleft"),t=$(".dropdown-menu"),o=$(".dropdown-menu .dropdown-menu");$(".dropdown-menu .dropdown-toggle").on("click",function(){var a;return(a=$(this)).closest(e).siblings(e).find(t).removeClass("show"),a.next(o).toggleClass("show"),!1}),e.on("hide.bs.dropdown",function(){var a,e;a=$(this),(e=a.find(o)).length&&e.removeClass("show")})}(),Dropzones=function(){var a=$('[data-toggle="dropzone"]'),l=$(".dz-preview");a.length&&(Dropzone.autoDiscover=!1,a.each(function(){var a,e,t,o,r;a=$(this),e=void 0!==a.data("dropzone-multiple"),t=a.find(l),o=void 0,r={url:a.data("dropzone-url"),thumbnailWidth:null,thumbnailHeight:null,previewsContainer:t.get(0),previewTemplate:t.html(),maxFiles:e?null:1,acceptedFiles:e?null:"image/*",init:function(){this.on("addedfile",function(a){!e&&o&&this.removeFile(o),o=a})}},t.html(""),a.dropzone(r)}))}(),Flatpickr=function(){var a=$('[data-toggle="flatpickr"]');a.length&&a.each(function(){var a,e;a=$(this),e={mode:void 0!==a.data("flatpickr-mode")?a.data("flatpickr-mode"):"single"},a.flatpickr(e)})}(),Highlight=void $(".highlight").each(function(a,e){var t;t=e,hljs.highlightBlock(t)}),Lists=function(){var a=$('[data-toggle="lists"]'),e=$("[data-sort]");a.length&&a.each(function(){var a,e;a=$(this),new List(a.get(0),{valueNames:(e=a).data("lists-values"),listClass:e.data("lists-class")?e.data("lists-class"):"list"})}),e.on("click",function(){return!1})}(),Navbar=function(){var e=$(".navbar-nav, .navbar-nav .nav"),t=$(".navbar-nav .collapse");t.on({"show.bs.collapse":function(){var a;(a=$(this)).closest(e).find(t).not(a).collapse("hide")}})}(),Popover=function(){var a=$('[data-toggle="popover"]');a.length&&a.popover()}(),Quill=function(){var a=$('[data-toggle="quill"]');a.length&&a.each(function(){var a,e;a=$(this),e=a.data("quill-placeholder"),new Quill(a.get(0),{modules:{toolbar:[["bold","italic"],["link","blockquote","code","image"],[{list:"ordered"},{list:"bullet"}]]},placeholder:e,theme:"snow"})})}(),Select2=function(){var a=$('[data-toggle="select"]');function t(a){if(!a.id)return a.text;var e=$(a.element).data("avatar-src");return e?$(''+a.text+''+a.text+""):a.text}a.length&&a.each(function(){var a,e;a=$(this),e={dropdownParent:a.closest(".modal").length?a.closest(".modal"):$(document.body),minimumResultsForSearch:a.data("minimum-results-for-search"),templateResult:t},a.select2(e)})}(),Tooltip=function(){var a=$('[data-toggle="tooltip"]');a.length&&a.tooltip()}(); -------------------------------------------------------------------------------- /src/config/seo-manager.php: -------------------------------------------------------------------------------- 1 | [ 8 | 'table' => 'seo_manager', 9 | 'locales_table' => 'seo_manager_locales', 10 | 'translates_table' => 'seo_manager_translates' 11 | ], 12 | 13 | /** 14 | * Set default locale, 15 | * It will be added as default locale 16 | * when locales table migrated 17 | */ 18 | 'locale' => 'en', 19 | 20 | /** 21 | * Path where your eloquent models are 22 | * Leave this config empty if you want to look for models in whole project 23 | */ 24 | 'models_path' => '', 25 | 26 | /** 27 | * Route from which your Dashboard will be available 28 | */ 29 | 'route' => 'seo-manager', 30 | 31 | /** 32 | * Middleware array for dashboard 33 | * to prevent unauthorized users visit the manager 34 | */ 35 | 'middleware' => [ 36 | 'web' 37 | // 'auth', 38 | ], 39 | 40 | /** 41 | * Routes which shouldn't be imported to seo manager 42 | */ 43 | 'except_routes' => [ 44 | 'seo-manager', 45 | 'admin' 46 | // 47 | ], 48 | 49 | /** 50 | * Columns which shouldn't be used ( in mapping ) 51 | */ 52 | 'except_columns' => [ 53 | // "created_at", 54 | // "updated_at", 55 | ], 56 | 57 | /** 58 | * Set this parameter to true 59 | * if you want to have "$metaData" variable 60 | * shared between all views in "web" middleware group 61 | */ 62 | 'shared_meta_data' => false 63 | ]; 64 | -------------------------------------------------------------------------------- /src/helpers/helpers.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('uri')->nullable(); 19 | $table->jsonb('params')->nullable(); 20 | $table->jsonb('mapping')->nullable(); 21 | $table->jsonb('keywords'); 22 | $table->string('description')->nullable(); 23 | $table->string('title')->nullable(); 24 | $table->string('author')->nullable(); 25 | $table->string('url')->nullable(); 26 | $table->jsonb('title_dynamic')->nullable(); 27 | $table->jsonb('og_data')->nullable(); 28 | $table->timestamps(); 29 | }); 30 | } 31 | 32 | /** 33 | * Reverse the migrations. 34 | * 35 | * @return void 36 | */ 37 | public function down() 38 | { 39 | Schema::dropIfExists(config('seo-manager.database.table')); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/migrations/2019_01_08_132731_create_locales_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('name'); 19 | $table->timestamps(); 20 | }); 21 | 22 | \Illuminate\Support\Facades\DB::table(config('seo-manager.database.locales_table'))->insert([ 23 | 'name' => config('seo-manager.locale'), 24 | 'created_at' => \Carbon\Carbon::now(), 25 | 'updated_at' => \Carbon\Carbon::now() 26 | ]); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::dropIfExists(config('seo-manager.database.locales_table')); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/migrations/2019_01_12_174747_create_translates_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->integer('route_id'); 19 | $table->string('locale'); 20 | $table->string('url')->nullable(); 21 | $table->jsonb('keywords')->nullable(); 22 | $table->string('description')->nullable(); 23 | $table->string('title')->nullable(); 24 | $table->string('author')->nullable(); 25 | $table->jsonb('title_dynamic')->nullable(); 26 | $table->jsonb('og_data')->nullable(); 27 | $table->timestamps(); 28 | }); 29 | } 30 | 31 | /** 32 | * Reverse the migrations. 33 | * 34 | * @return void 35 | */ 36 | public function down() 37 | { 38 | Schema::dropIfExists(config('seo-manager.database.translates_table')); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/routes/seo-manager.php: -------------------------------------------------------------------------------- 1 | $middleware, 6 | 'prefix' => config('seo-manager.route'), 7 | 'as' => 'seo-manager.', 8 | 'namespace' => 'Lionix\SeoManager\Controllers' 9 | ], function () { 10 | Route::get('/', 'ManagerController@index')->name('home'); 11 | Route::get('get-routes', 'ManagerController@getRoutes')->name('get-routes'); 12 | Route::get('import-routes', 'ImportController')->name('import'); 13 | Route::get('get-models', 'ManagerController@getModels')->name('get-models'); 14 | 15 | Route::group(['prefix' => 'locales', 'as' => 'locales.'], function () { 16 | Route::get('get-locales', 'LocalesController@getLocales')->name('get'); 17 | }); 18 | }); 19 | Route::group([ 20 | 'middleware' => config('seo-manager.middleware'), 21 | 'prefix' => config('seo-manager.route'), 22 | 'as' => 'seo-manager.', 23 | 'namespace' => 'Lionix\SeoManager\Controllers' 24 | ], function () { 25 | Route::post('delete-route', 'ManagerController@deleteRoute')->name('delete-route'); 26 | Route::post('get-model-columns', 'ManagerController@getModelColumns')->name('get-model-columns'); 27 | Route::post('store-data', 'ManagerController@storeData')->name('store-data'); 28 | Route::post('get-example-title', 'ManagerController@getExampleTitle')->name('get-example-title'); 29 | Route::post('sharing-preview', 'ManagerController@sharingPreview')->name('sharing-preview'); 30 | 31 | Route::group(['prefix' => 'locales', 'as' => 'locales.'], function () { 32 | Route::post('add-locale', 'LocalesController@addLocale')->name('add'); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/views/index.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 19 | Seo Manager 20 | 21 | 22 | 23 |
24 | {{--Main Content--}} 25 | 26 |
27 | 28 | 29 | 31 | 32 | 33 | 34 | 35 | 36 | {{----}} 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/views/og_data.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/vue/App.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 30 | 31 | 32 | 35 | -------------------------------------------------------------------------------- /src/vue/components/Header.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 122 | 123 | 128 | -------------------------------------------------------------------------------- /src/vue/components/SideBar.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 25 | 26 | 29 | -------------------------------------------------------------------------------- /src/vue/components/routes/Routes.vue: -------------------------------------------------------------------------------- 1 | 82 | 83 | 259 | 260 | 263 | -------------------------------------------------------------------------------- /src/vue/components/routes/partials/Actions.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 59 | 60 | 63 | -------------------------------------------------------------------------------- /src/vue/components/routes/partials/Author.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 31 | 32 | 35 | -------------------------------------------------------------------------------- /src/vue/components/routes/partials/Description.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 32 | 33 | 36 | -------------------------------------------------------------------------------- /src/vue/components/routes/partials/Keywords.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 32 | 33 | 38 | -------------------------------------------------------------------------------- /src/vue/components/routes/partials/Mapping.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 40 | 41 | 44 | -------------------------------------------------------------------------------- /src/vue/components/routes/partials/OgData.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 34 | 35 | 38 | -------------------------------------------------------------------------------- /src/vue/components/routes/partials/Params.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /src/vue/components/routes/partials/Route.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 71 | 72 | 75 | -------------------------------------------------------------------------------- /src/vue/components/routes/partials/Title.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 31 | 32 | 35 | -------------------------------------------------------------------------------- /src/vue/components/routes/partials/TitleDynamic.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 65 | 66 | 69 | -------------------------------------------------------------------------------- /src/vue/components/routes/partials/Url.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 31 | 32 | 35 | -------------------------------------------------------------------------------- /src/vue/components/routes/partials/mappings/ParamsMapping.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 88 | 89 | 94 | -------------------------------------------------------------------------------- /src/vue/components/routes/partials/mappings/TitleMapping.vue: -------------------------------------------------------------------------------- 1 | 92 | 93 | 166 | 167 | 170 | -------------------------------------------------------------------------------- /src/vue/components/routes/partials/modals/AuthorModal.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 81 | 82 | 98 | -------------------------------------------------------------------------------- /src/vue/components/routes/partials/modals/DescriptionModal.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 81 | 82 | 98 | -------------------------------------------------------------------------------- /src/vue/components/routes/partials/modals/KeywordsModal.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 87 | 88 | 104 | -------------------------------------------------------------------------------- /src/vue/components/routes/partials/modals/MappingModal.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | 148 | 149 | 170 | -------------------------------------------------------------------------------- /src/vue/components/routes/partials/modals/OgDataModal.vue: -------------------------------------------------------------------------------- 1 | 131 | 132 | 445 | 446 | 516 | -------------------------------------------------------------------------------- /src/vue/components/routes/partials/modals/PreviewModal.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 91 | 92 | 189 | -------------------------------------------------------------------------------- /src/vue/components/routes/partials/modals/TitleDynamicModal.vue: -------------------------------------------------------------------------------- 1 | 95 | 96 | 172 | 173 | 193 | -------------------------------------------------------------------------------- /src/vue/components/routes/partials/modals/TitleModal.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 83 | 84 | 100 | -------------------------------------------------------------------------------- /src/vue/components/routes/partials/modals/UrlModal.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 81 | 82 | 98 | -------------------------------------------------------------------------------- /src/vue/event_bus/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | 3 | export const EventBus = new Vue(); 4 | -------------------------------------------------------------------------------- /src/vue/seo-manager.app.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueResource from 'vue-resource' 3 | import App from './App' 4 | import MultiSelect from 'vue-multiselect' 5 | import VueDraggable from 'vue-draggable' 6 | import ClickOutside from 'vue-click-outside' 7 | import VueSweetalert2 from 'vue-sweetalert2' 8 | import {store} from './store/store' 9 | Vue.use(VueDraggable); 10 | Vue.use(VueResource); 11 | Vue.use(VueSweetalert2); 12 | 13 | const API_URL = window.API_URL; 14 | const CSRF_TOKEN = window.CSRF_TOKEN; 15 | 16 | Vue.filter('uppercase', function (value) { 17 | return value.toUpperCase() 18 | }); 19 | Vue.directive('click-outside', ClickOutside); 20 | Vue.component('multi-select', MultiSelect); 21 | 22 | new Vue({ 23 | el: '#lionix-seo-manager-app', 24 | store, 25 | components: { 26 | App 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /src/vue/store/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex); 5 | 6 | export const store = new Vuex.Store({ 7 | state: { 8 | locale: null 9 | }, 10 | mutations:{ 11 | setLocale(state, locale){ 12 | state.locale = locale 13 | } 14 | }, 15 | getters: { 16 | locale: state => { 17 | return state.locale 18 | } 19 | } 20 | }); 21 | 22 | --------------------------------------------------------------------------------