├── .editorconfig ├── .env.docker ├── .env.example ├── .env.testing ├── .env.travis ├── .gitattributes ├── .gitignore ├── .htaccess ├── .jshintignore ├── .travis.yml ├── README.md ├── app ├── Bookmark.php ├── Console │ ├── Commands │ │ └── Inspire.php │ └── Kernel.php ├── Events │ └── Event.php ├── Exceptions │ └── Handler.php ├── Http │ ├── Controllers │ │ ├── Api │ │ │ └── v1 │ │ │ │ ├── BookmarkController.php │ │ │ │ └── UserController.php │ │ ├── Auth │ │ │ ├── AuthController.php │ │ │ └── PasswordController.php │ │ ├── Controller.php │ │ ├── PagesController.php │ │ └── UserAdminController.php │ ├── Kernel.php │ ├── Middleware │ │ ├── Authenticate.php │ │ ├── EncryptCookies.php │ │ ├── RedirectIfAuthenticated.php │ │ ├── SendMailAfterRegistration.php │ │ └── VerifyCsrfToken.php │ ├── Requests │ │ └── Request.php │ └── routes.php ├── Jobs │ └── Job.php ├── Listeners │ └── .gitkeep ├── MailUtility.php ├── Policies │ └── .gitkeep ├── Providers │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ ├── EventServiceProvider.php │ └── RouteServiceProvider.php ├── Tag.php └── User.php ├── artisan ├── bootstrap ├── app.php ├── autoload.php └── cache │ └── .gitignore ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── broadcasting.php ├── cache.php ├── compile.php ├── database.php ├── filesystems.php ├── mail.php ├── queue.php ├── services.php ├── session.php └── view.php ├── database ├── .gitignore ├── database.sqlite ├── factories │ └── ModelFactory.php ├── migrations │ ├── .gitkeep │ ├── 2016_02_16_000001_create_users_table.php │ ├── 2016_02_17_000004_create_bookmarks_table.php │ ├── 2016_02_17_000005_create_tags_table.php │ ├── 2016_10_22_215900_add_users_confirmed_column.php │ └── 2016_11_20_184423_add_user_validation_hash.php └── seeds │ ├── .gitkeep │ ├── DatabaseSeeder.php │ ├── DummyBookmarks.json │ └── DummyBookmarksSeeder.php ├── docker-compose.sample.yml ├── docker ├── create-rc.sh ├── kill-all.sh └── prep-dev.sh ├── elixir-extensions.js ├── gulpfile.js ├── package-lock.json ├── package.json ├── phpunit.xml ├── public ├── .htaccess ├── favicon.ico ├── index.php ├── robots.txt └── web.config ├── resources ├── assets │ ├── images │ │ └── screenshot1.png │ ├── js │ │ ├── bookmarkEditController.js │ │ ├── bookmarkService.js │ │ ├── bookmarkViewController.js │ │ ├── directives │ │ │ ├── angucomplete-alt.js │ │ │ ├── customSelect2.js │ │ │ ├── tagmanagerDirective.js │ │ │ └── ui-ace.js │ │ ├── filters.js │ │ ├── main.js │ │ ├── routes.js │ │ ├── sessionService.js │ │ ├── translation.js │ │ ├── userEditController.js │ │ └── userService.js │ ├── sass │ │ ├── bootstrap-darkly.scss │ │ ├── bootstrap-default.scss │ │ ├── bootstrap-sandstone.scss │ │ ├── bootstrap-united.scss │ │ ├── bootstrap-yeti.scss │ │ ├── main.scss │ │ └── partials │ │ │ ├── angucomplete.scss │ │ │ ├── bs-callout.scss │ │ │ ├── code-pre-tags.scss │ │ │ ├── tag-cloud.scss │ │ │ └── tags.scss │ └── views │ │ ├── bookmark-edit.html │ │ ├── bookmark-view.html │ │ ├── hero.html │ │ ├── tagmanager-directive.html │ │ └── user-edit.html ├── lang │ ├── en.json │ ├── en │ │ ├── auth.php │ │ ├── messages.php │ │ ├── pagination.php │ │ ├── passwords.php │ │ └── validation.php │ ├── fr.json │ └── fr │ │ ├── auth.php │ │ ├── messages.php │ │ ├── pagination.php │ │ ├── passwords.php │ │ └── validation.php └── views │ ├── app.blade.php │ ├── auth │ ├── login.blade.php │ └── register.blade.php │ ├── emails │ └── register.blade.php │ ├── errors │ └── 503.blade.php │ ├── pages │ └── home.blade.php │ ├── partials │ ├── errors.blade.php │ └── hero.blade.php │ ├── useradmin │ ├── create.blade.php │ ├── edit.blade.php │ ├── index.blade.php │ ├── registerform.blade.php │ └── userform.blade.php │ └── vendor │ └── .gitkeep ├── server.php ├── storage ├── app │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore └── tests ├── AppTitleTest.php ├── TestCase.php └── UserRegistrationTest.php /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | # Spaces in coffee 13 | [{package.json,*.yml,*.scss}] 14 | indent_style = space 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.env.docker: -------------------------------------------------------------------------------- 1 | APP_ENV=local 2 | APP_DEBUG=true 3 | APP_KEY= 4 | 5 | ADMIN_ENABLED=true 6 | ALLOW_UNSORTED_CATEGORY=true 7 | 8 | DB_HOST=db 9 | DB_DATABASE=testdb 10 | DB_USERNAME=testuser 11 | DB_PASSWORD=testpassword 12 | 13 | CACHE_DRIVER=file 14 | SESSION_DRIVER=file 15 | QUEUE_DRIVER=sync 16 | LOCALE=en 17 | LOCALE_FALLBACK=en 18 | 19 | ENABLE_REGISTER=false 20 | ENABLE_REGISTER_MAIL=false 21 | 22 | ENABLE_ADMIN_MAIL=false 23 | 24 | MAIL_HOST=PLEASESETME 25 | MAIL_USERNAME=PLEASESETME 26 | MAIL_PASSWORD=PLEASESETME 27 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_ENV=local 2 | APP_DEBUG=false 3 | APP_KEY= 4 | 5 | ADMIN_ENABLED=true 6 | ALLOW_UNSORTED_CATEGORY=true 7 | 8 | DB_HOST=localhost 9 | DB_DATABASE=PLEASESETME 10 | DB_USERNAME=PLEASESETME 11 | DB_PASSWORD=PLEASESETME 12 | 13 | DB_TEST_HOST=localhost 14 | DB_TEST_DATABASE=PLEASESETME 15 | DB_TEST_USERNAME=PLEASESETME 16 | DB_TEST_PASSWORD=PLEASESETME 17 | 18 | CACHE_DRIVER=file 19 | SESSION_DRIVER=file 20 | QUEUE_DRIVER=sync 21 | LOCALE=en 22 | LOCALE_FALLBACK=en 23 | 24 | ENABLE_REGISTER=false 25 | ENABLE_REGISTER_MAIL=false 26 | 27 | ENABLE_ADMIN_MAIL=false 28 | 29 | MAIL_HOST=PLEASESETME 30 | MAIL_USERNAME=PLEASESETME 31 | MAIL_PASSWORD=PLEASESETME 32 | -------------------------------------------------------------------------------- /.env.testing: -------------------------------------------------------------------------------- 1 | APP_ENV=local 2 | APP_DEBUG=true 3 | APP_KEY=ZyGTV6G2ajODKPxqxhUJDd31aBp1JmqY 4 | 5 | ADMIN_ENABLED=true 6 | ALLOW_UNSORTED_CATEGORY=true 7 | 8 | DB_DEFAULT=sqlite 9 | DB_DATABASE=test_db 10 | 11 | CACHE_DRIVER=file 12 | SESSION_DRIVER=file 13 | QUEUE_DRIVER=sync 14 | LOCALE=en 15 | LOCALE_FALLBACK=en 16 | 17 | ENABLE_REGISTER=true 18 | ENABLE_REGISTER_MAIL=false 19 | 20 | ENABLE_ADMIN_MAIL=false 21 | 22 | MAIL_HOST=PLEASESETME 23 | MAIL_USERNAME=PLEASESETME 24 | MAIL_PASSWORD=PLEASESETME 25 | -------------------------------------------------------------------------------- /.env.travis: -------------------------------------------------------------------------------- 1 | APP_ENV=testing 2 | APP_KEY=SomeRandomString 3 | 4 | ADMIN_ENABLED=true 5 | ALLOW_UNSORTED_CATEGORY=true 6 | 7 | DB_CONNECTION=testing 8 | DB_TEST_USERNAME=root 9 | DB_TEST_PASSWORD= 10 | 11 | CACHE_DRIVER=array 12 | SESSION_DRIVER=array 13 | QUEUE_DRIVER=sync 14 | 15 | ENABLE_REGISTER=true 16 | ENABLE_REGISTER_MAIL=false 17 | 18 | ENABLE_ADMIN_MAIL=false 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.less linguist-vendored 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .composer/ 2 | .env 3 | .idea 4 | *.iml 5 | *.tar.gz 6 | docker-compose.yml 7 | node_modules 8 | public/storage 9 | public/**/*.map 10 | public/build 11 | public/css 12 | public/fonts 13 | public/js 14 | public/lang 15 | public/views 16 | vendor 17 | VERSION 18 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine on 3 | RewriteRule (.*) public/$1 [L] 4 | 5 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | resources/assets/js/directives/angucomplete-alt.js 2 | resources/assets/js/directives/ui-ace.js 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | services: 4 | - docker 5 | 6 | language: ruby 7 | 8 | env: 9 | - DOCKER_COMPOSE_VERSION: 1.8.1 10 | 11 | before_install: 12 | - sudo rm /usr/local/bin/docker-compose 13 | - curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose 14 | - chmod +x docker-compose 15 | - sudo mv docker-compose /usr/local/bin 16 | 17 | install: 18 | - cp .env.docker .env 19 | - cp docker-compose.sample.yml docker-compose.yml 20 | - docker-compose stop 21 | - docker-compose rm -f 22 | - docker-compose build 23 | 24 | script: 25 | - docker-compose run --rm composer install 26 | - docker-compose run --rm npm -q install 27 | - docker-compose run --rm gulp --production 28 | - docker-compose run --rm artisan key:generate 29 | - docker-compose run --rm phpunit 30 | - docker-compose up -d 31 | - sleep 10s 32 | - docker-compose run --rm artisan migrate:refresh --seed 33 | - docker-compose run --rm artisan db:seed --class=DummyBookmarksSeeder 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Bookmarks - Easy bookmark manager 2 | 3 | Easy (and fast) self-hosted bookmark manager. 4 | 5 | ![alt text](https://github.com/devimust/easy-bookmark-manager/raw/master/resources/assets/images/screenshot1.png "Screenshot 1") 6 | 7 | ### Demo 8 | 9 | You can test easy-bookmark-manager here : [demo](http://bookmarkdemo.miceli.click/) 10 | 11 | To access use **admin** as login and **nimda** as password. 12 | 13 | You can also download easy-bookmark-manager [chrome plugin](https://chrome.google.com/webstore/detail/easy-bookmark-manager-ext/hhonlfdhoejpaofmmppaogebkfnbgefi) 14 | 15 | And use this url : http://bookmarkdemo.miceli.click/ as target 16 | 17 | ![target](http://img15.hostingpics.net/pics/837402EasyBookmarkManagerOptionsGoogleChrome2.jpg) 18 | 19 | ### Requirements 20 | 21 | * webserver (apache2, nginx) 22 | * php 5.5.9+ 23 | * Laravel 5.2 24 | * mysql (or laravel supported database) 25 | 26 | 27 | ### Installation 28 | 29 | Step 1: 30 | ```bash 31 | $ git clone git@github.com/devimust/easy-bookmark-manager.git 32 | $ cd easy-bookmark-manager 33 | $ composer install 34 | $ npm install 35 | $ gulp --production 36 | ``` 37 | 38 | Step 2: Update database details inside `.env` file. 39 | 40 | Step 3: 41 | ```bash 42 | $ php artisan migrate --seed #this will create the default admin user 43 | ``` 44 | 45 | Step 4: Update `.env` file with `APP_ENV=production`. Ensure webserver user (apache, www-data or nginx) can read and write to the `./storage` and `./bootstrap/cache` folders recursively. 46 | 47 | Step 5: Navigate to http://insert-your-domain.com/ and login with email `admin` and password `nimda`. Go add some bookmarks or navigate to http://insert-your-domain.com/admin/users to create more users and *important* update the admin user's password. 48 | 49 | Step 6: Optional security measure - to disable the `/admin` section set `ADMIN_ENABLED=false` inside .env file. 50 | 51 | Example apache virtual host file 52 | 53 | ```apache 54 | 55 | ServerName 56 | DocumentRoot /var/www/{PROJECT_FOLDER}/public 57 | 58 | Options Indexes FollowSymLinks 59 | AllowOverride All 60 | Options -Indexes 61 | 62 | ErrorLog ${APACHE_LOG_DIR}/error.log 63 | CustomLog ${APACHE_LOG_DIR}/access.log combined 64 | 65 | ``` 66 | 67 | 68 | ### Development 69 | 70 | I welcome any feedback and contributions. 71 | 72 | 73 | #### Local native 74 | 75 | ```bash 76 | # update .env with APP_ENV=local and APP_DEBUG=false 77 | $ composer install 78 | $ npm install 79 | $ gulp 80 | $ vendor/bin/phpunit 81 | $ gulp watch 82 | ``` 83 | 84 | #### Local docker LAMP stack 85 | 86 | ```bash 87 | # run ./docker/prep-dev.sh 88 | # update .env with APP_ENV=local and APP_DEBUG=false 89 | docker-compose build 90 | docker-compose up 91 | docker-compose run --rm composer install 92 | docker-compose run --rm npm install 93 | docker-compose run --rm gulp --production 94 | docker-compose run --rm artisan key:generate 95 | docker-compose run --rm phpunit 96 | docker-compose run --rm artisan migrate:refresh --seed 97 | docker-compose run --rm artisan db:seed --class=DummyBookmarksSeeder 98 | ``` 99 | 100 | #### Local docker LAMP stack using release candidate 101 | 102 | Download latest release candidate and decompress into a folder, then 103 | 104 | ```bash 105 | cp docker-compose.sample.yml docker-compose.yml 106 | cp .env.docker .env 107 | docker-compose build 108 | docker-compose up 109 | docker-compose run --rm artisan key:generate 110 | docker-compose run --rm artisan migrate:refresh --seed 111 | docker-compose run --rm artisan db:seed --class=DummyBookmarksSeeder 112 | ``` 113 | 114 | goto http://localhost:8000/ and login with `admin`:`nimda` 115 | 116 | 117 | ### Chrome Extension 118 | 119 | [Easy Bookmark Manager Extension](https://chrome.google.com/webstore/detail/easy-bookmark-manager-ext/hhonlfdhoejpaofmmppaogebkfnbgefi) 120 | 121 | [Source](https://github.com/devimust/easy-bookmark-manager-chrome-extension) 122 | 123 | 124 | ### Dev Dependencies and Credits 125 | 126 | * jQuery 1.12 127 | * Angular & Angular Route 128 | * [Bootswatch](http://bootswatch.com) 129 | * [Bootstrap 3](http://getbootstrap.com/) 130 | * FontAwesome 131 | * NodeJS 132 | * Composer 133 | * Gulp 134 | * Select2 135 | * Sass 136 | * [Ace](https://ace.c9.io/) 137 | * Normalize.css 138 | 139 | 140 | ### Todos 141 | 142 | * Reset password via email 143 | 144 | 145 | ### License 146 | 147 | MIT 148 | -------------------------------------------------------------------------------- /app/Bookmark.php: -------------------------------------------------------------------------------- 1 | 'boolean' 25 | ]; 26 | 27 | /** 28 | * The attributes that are mass assignable. 29 | * 30 | * @var array 31 | */ 32 | protected $fillable = [ 33 | 'title', 'link', 'favourite', 'snippet', 'icon', 'category' 34 | ]; 35 | 36 | /** 37 | * Get the tags for the bookmark. 38 | */ 39 | public function tags() 40 | { 41 | return $this->belongsToMany('App\Tag', 'bookmark_tag'); 42 | } 43 | 44 | /** 45 | * Get the user linked to the bookmark. 46 | */ 47 | public function user() 48 | { 49 | return $this->belongsTo('App\User'); 50 | } 51 | 52 | /** 53 | * Cast input to boolean type. 54 | * 55 | * @param $value 56 | */ 57 | public function setFavouriteAttribute($value) 58 | { 59 | $this->attributes['favourite'] = filter_var($value, FILTER_VALIDATE_BOOLEAN); 60 | } 61 | 62 | /** 63 | * Cast output to boolean type. 64 | * 65 | * @param $value 66 | * @return bool 67 | */ 68 | public function getFavouriteAttribute($value) 69 | { 70 | return filter_var($value, FILTER_VALIDATE_BOOLEAN); 71 | } 72 | 73 | /** 74 | * Set the bookmark category based on env setting. 75 | * 76 | * @param string $value 77 | * @return string 78 | */ 79 | public function setCategoryAttribute($value) 80 | { 81 | $category = $value; 82 | 83 | // set category to Unsorted if empty string and env is configured 84 | if ($category == '') { 85 | if (env('ALLOW_UNSORTED_CATEGORY', true)) { 86 | $category = 'Unsorted'; 87 | } 88 | } 89 | 90 | $this->attributes['category'] = $category; 91 | } 92 | 93 | /** 94 | * Update bookmark title if title is empty and link exist. 95 | * 96 | * @param array $attributes 97 | * @return $this 98 | */ 99 | public function fill(array $attributes) 100 | { 101 | $title = isset($attributes['title']) ? $attributes['title'] : ''; 102 | $link = isset($attributes['link']) ? $attributes['link'] : ''; 103 | if (isset($attributes['title']) && $title == '' && $link != '') { 104 | $attributes['title'] = BookmarkController::getSiteTitle($link); 105 | } 106 | return parent::fill($attributes); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/Console/Commands/Inspire.php: -------------------------------------------------------------------------------- 1 | comment(PHP_EOL.Inspiring::quote().PHP_EOL); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('inspire') 28 | // ->hourly(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Events/Event.php: -------------------------------------------------------------------------------- 1 | 'error', 25 | 'message' => trans('message.user.noSession') 26 | ]; 27 | } 28 | 29 | return [ 30 | 'result' => 'ok', 31 | 'message' => '', 32 | 'data' => [ 33 | 'username' => Auth::user()->username, 34 | 'canAccessAdmin' => (Auth::user()->administrator && (env('ADMIN_ENABLED') === true) ? 'yes' : 'no') 35 | ] 36 | ]; 37 | } 38 | 39 | /** 40 | * Log the user in via username and password. 41 | * 42 | * @param Request $request 43 | * @return array 44 | */ 45 | public function login(Request $request) { 46 | // User is NOT logged in 47 | if (!Auth::check()) { 48 | $username = $request->input('username'); 49 | $password = $request->input('password'); 50 | 51 | if (!Auth::attempt(['username' => $username, 'password' => $password], true)) 52 | { 53 | return [ 54 | 'result' => 'error', 55 | 'message' => trans('messages.loginCombo') 56 | ]; 57 | } 58 | } 59 | 60 | // Get active tokens 61 | $activeTokenCount = \App\User::find(Auth::id()) 62 | ->tokens() 63 | ->where('expires_at', '>', \Carbon\Carbon::now()) 64 | ->count(); 65 | if ($activeTokenCount > 10) { 66 | // Should we have a bad response now? It appears to be a DOS attack 67 | return [ 68 | 'result' => 'error', 69 | 'message' => trans('messages.user.sessionReached') 70 | ]; 71 | } 72 | 73 | $token = $this->generateToken(); 74 | 75 | $this->associateToken($token); 76 | 77 | return [ 78 | 'result' => 'ok', 79 | 'message' => '', 80 | 'data' => [ 81 | 'token' => $token 82 | ] 83 | ]; 84 | } 85 | 86 | /** 87 | * Return current user details. 88 | * 89 | * @param Request $request 90 | * @return array 91 | */ 92 | public function edit(Request $request) { 93 | $user = Auth::user(); 94 | 95 | if (!$user) { 96 | return [ 97 | 'result' => 'error', 98 | 'message' => trans('message.user.noSession') 99 | ]; 100 | } 101 | 102 | $userData = [ 103 | 'name' => $user->name, 104 | 'email' => $user->username 105 | ]; 106 | 107 | return [ 108 | 'result' => 'ok', 109 | 'message' => '', 110 | 'data' => [ 111 | 'user' => $userData 112 | ] 113 | ]; 114 | } 115 | 116 | /** 117 | * Update current user details. 118 | * 119 | * @param Request $request 120 | * @return array 121 | */ 122 | public function update(Request $request) { 123 | $user = Auth::user(); 124 | 125 | if (!$user) { 126 | return [ 127 | 'result' => 'error', 128 | 'message' => trans('message.user.noSession') 129 | ]; 130 | } 131 | 132 | if ($request->input('name') != '') 133 | $user->name = $request->input('name'); 134 | 135 | if ($request->input('email') != '') 136 | $user->username = $request->input('email'); 137 | 138 | if ($request->input('password1') != '' && $request->input('password2') != '') { 139 | if ($request->input('password1') != $request->input('password2')) { 140 | return [ 141 | 'result' => 'error', 142 | 'message' => trans('messages.password.match') 143 | ]; 144 | } 145 | $user->password = \Hash::make($request->input('password1')); 146 | } 147 | 148 | $user->save(); 149 | 150 | return [ 151 | 'result' => 'ok', 152 | 'message' => '' 153 | ]; 154 | } 155 | 156 | /** 157 | * Log the user in if logged in. 158 | * 159 | * @return array 160 | */ 161 | public function logout() { 162 | Auth::logout(); 163 | 164 | return [ 165 | 'result' => 'ok', 166 | 'message' => '' 167 | ]; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/AuthController.php: -------------------------------------------------------------------------------- 1 | middleware('guest', ['except' => 'logout']); 41 | } 42 | 43 | /** 44 | * Get a validator for an incoming registration request. 45 | * 46 | * @param array $data 47 | * @return \Illuminate\Contracts\Validation\Validator 48 | */ 49 | protected function validator(array $data) 50 | { 51 | return Validator::make($data, [ 52 | 'name' => 'required|unique:users|max:255', 53 | 'email' => 'required|email|max:255|unique:users', 54 | 'password' => 'required|confirmed|min:6', 55 | ]); 56 | } 57 | 58 | /** 59 | * Create a new user instance after a valid registration. 60 | * 61 | * @param array $data 62 | * @return User 63 | */ 64 | protected function create(array $data) 65 | { 66 | return User::create([ 67 | 'name' => $data['name'], 68 | 'email' => $data['email'], 69 | 'password' => bcrypt($data['password']), 70 | ]); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/PasswordController.php: -------------------------------------------------------------------------------- 1 | middleware('guest'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | $request->input('username'), 58 | 'password' => $request->input('password'), 59 | ]; 60 | 61 | $canRegister = env('ENABLE_REGISTER', false); 62 | 63 | if ($canRegister) 64 | { 65 | // check for valid login details 66 | $valid = Auth::validate($credentials); 67 | if (!$valid) { 68 | return \Redirect::back() 69 | ->withErrors(trans('messages.loginCombo')); 70 | } 71 | 72 | // check to see if user is confirmed or not 73 | $user = User::where('username', $credentials['username']) 74 | ->first(); 75 | if ($user && !$user->confirmed) { 76 | return \Redirect::back() 77 | ->withErrors(trans('messages.notConfirmed')); 78 | } 79 | } 80 | 81 | if (!Auth::attempt([ 82 | 'username' => $credentials['username'], 83 | 'password' => $credentials['password'] 84 | ], true)) 85 | { 86 | return \Redirect::back() 87 | ->withErrors(trans('messages.loginCombo')); 88 | } 89 | 90 | return \Redirect::to('/'); 91 | } 92 | 93 | 94 | /** 95 | * Log the user out 96 | * 97 | * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector 98 | */ 99 | public function logout() 100 | { 101 | if (!Auth::check()) { 102 | return redirect('/'); 103 | } 104 | 105 | // @todo clean any existing tokens on logout 106 | // ... 107 | 108 | Auth::logout(); 109 | 110 | return redirect('/'); 111 | } 112 | 113 | public function showRegister() 114 | { 115 | // If user is connected we redirect it to homepage 116 | if (!Auth::check()) { 117 | return view('auth/register'); 118 | } 119 | 120 | return redirect('/'); 121 | } 122 | 123 | 124 | public function register(Request $request) 125 | { 126 | $this->validate($request, [ 127 | 'name' => 'required', 128 | 'username' => 'required|unique:users,username|email|min:3', 129 | 'password' => 'required|confirmed|min:5', 130 | 'password_confirmation' => 'required|min:5' 131 | ], User::getFormMessages()); 132 | 133 | $userData = $request->all(); 134 | $userData['password'] = Hash::make($userData['password']); 135 | $userData['administrator'] = false; 136 | 137 | $user = User::create($userData); 138 | 139 | // check if mail confirmation is disabled 140 | // if mail confirmation is disabled we set user as confirmed 141 | if (env('ENABLE_REGISTER_MAIL', false) === false) { 142 | $user->confirmed = true; 143 | } else { 144 | // use the application key to make hash a little less predictable 145 | $user->hashValidation = hash('sha256', $user->username . env('APP_KEY')); 146 | } 147 | 148 | $user->save(); 149 | 150 | return view('auth/login', array('message' => trans('messages.account.validationMessage'))); 151 | } 152 | 153 | public function validation($hashValidation, Request $request) 154 | { 155 | $user = User::where('hashValidation', $hashValidation)->first(); 156 | if (!$user) { 157 | return Redirect::to('/'); 158 | } 159 | 160 | if ($user->confirmed) { 161 | return Redirect::to('/'); 162 | } 163 | 164 | $user->confirmed = true; 165 | $user->hashValidation = null; 166 | $user->save(); 167 | 168 | return view('auth/login', array('message' => trans('messages.account.validated'))); 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /app/Http/Controllers/UserAdminController.php: -------------------------------------------------------------------------------- 1 | administrator) { 31 | App::abort(403, 'Access denied'); 32 | } 33 | 34 | $users = User::all(); 35 | 36 | return view('useradmin.index', compact('users')); 37 | } 38 | 39 | /** 40 | * Show the form for creating a new user. 41 | * 42 | * @return Response 43 | */ 44 | public function create() 45 | { 46 | return view('useradmin.create'); 47 | } 48 | 49 | /** 50 | * Store a newly created user in storage. 51 | * 52 | * @return Response 53 | */ 54 | public function store(Request $request) 55 | { 56 | $this->validate($request, [ 57 | 'name' => 'required|unique:users', 58 | 'username' => 'required|unique:users,username|email|min:3', 59 | 'password' => 'required|confirmed|min:5' 60 | ], User::getFormMessages()); 61 | 62 | $userData = $request->all(); 63 | $userData['password'] = Hash::make($userData['password']); 64 | 65 | $user = User::create($userData); 66 | 67 | if (env('ENABLE_ADMIN_MAIL') === false) { 68 | $user->confirmed = true; 69 | $user->save(); 70 | } 71 | 72 | return Redirect::to('/admin/users'); 73 | } 74 | 75 | /** 76 | * Show the form for editing the specified user. 77 | * 78 | * @param int $id 79 | * @return Response 80 | */ 81 | public function edit($id) 82 | { 83 | $user = User::findOrFail($id); 84 | 85 | return view('useradmin.edit', compact('user')); 86 | } 87 | 88 | /** 89 | * Update the specified user in storage. 90 | * 91 | * @param int $id 92 | * @return Response 93 | */ 94 | public function update($id, Request $request) 95 | { 96 | $this->validate($request, [ 97 | 'name' => 'required', 98 | 'username' => 'required|unique:users,username,' . $id . '|email|min:3', 99 | 'password' => 'confirmed|min:5' 100 | ], User::getFormMessages()); 101 | 102 | $user = User::findOrFail($id); 103 | 104 | $userData = [ 105 | 'name' => $request->input('name'), 106 | 'username' => $request->input('username'), 107 | 'administrator' => $request->input('administrator') 108 | ]; 109 | 110 | if ($request->input('password') != '') { 111 | $userData['password'] = Hash::make($request->input('password')); 112 | } 113 | 114 | $user->update($userData); 115 | 116 | return Redirect::to('/admin/users'); 117 | } 118 | 119 | /** 120 | * Remove the specified user from storage. 121 | * 122 | * @param int $id 123 | * @return Response 124 | */ 125 | public function destroy($id) 126 | { 127 | User::destroy($id); 128 | 129 | return Redirect::to('/admin/users'); 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /app/Http/Kernel.php: -------------------------------------------------------------------------------- 1 | [ 28 | \App\Http\Middleware\EncryptCookies::class, 29 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 30 | \Illuminate\Session\Middleware\StartSession::class, 31 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 32 | \App\Http\Middleware\VerifyCsrfToken::class, 33 | ], 34 | 35 | 'api' => [ 36 | 'throttle:60,1', 37 | ], 38 | ]; 39 | 40 | /** 41 | * The application's route middleware. 42 | * 43 | * These middleware may be assigned to groups or used individually. 44 | * 45 | * @var array 46 | */ 47 | protected $routeMiddleware = [ 48 | 'auth' => \App\Http\Middleware\Authenticate::class, 49 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 50 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 51 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 52 | 'mail' => SendMailAfterRegistration::class 53 | ]; 54 | } 55 | -------------------------------------------------------------------------------- /app/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | guest()) { 21 | if ($request->ajax() || $request->wantsJson()) { 22 | return response('Unauthorized.', 401); 23 | } else { 24 | return redirect()->guest('login'); 25 | } 26 | } 27 | 28 | return $next($request); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | check()) { 21 | return redirect('/'); 22 | } 23 | 24 | return $next($request); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Http/Middleware/SendMailAfterRegistration.php: -------------------------------------------------------------------------------- 1 | all(); 26 | 27 | $user = User::where('username', $data['username'])->first(); 28 | 29 | MailUtility::sendRegisterMail($user); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Http/Middleware/VerifyCsrfToken.php: -------------------------------------------------------------------------------- 1 | 'api/v1/', 'middleware' => ['web']], function () { 15 | Route::get('user/status', 'Api\v1\UserController@loginStatus'); 16 | }); 17 | 18 | Route::group(['prefix' => 'api/v1/', 'middleware' => ['web', 'auth']], function () { 19 | Route::get('bookmarks', 'Api\v1\BookmarkController@index'); 20 | Route::post('bookmark/create', 'Api\v1\BookmarkController@create'); 21 | Route::get('bookmark/{id}', 'Api\v1\BookmarkController@edit'); 22 | Route::put('bookmark/{id}', 'Api\v1\BookmarkController@update'); 23 | Route::delete('bookmark/{id}', 'Api\v1\BookmarkController@delete'); 24 | Route::post('bookmarks/import', 'Api\v1\BookmarkController@import'); 25 | Route::get('bookmarks/export', 'Api\v1\BookmarkController@export'); 26 | Route::get('bookmarks/duplicates', 'Api\v1\BookmarkController@duplicates'); 27 | 28 | Route::get('bookmarks/categories-and-tags', 'Api\v1\BookmarkController@categoriesAndTags'); 29 | Route::get('categories', 'Api\v1\BookmarkController@categories'); 30 | Route::get('tags', 'Api\v1\BookmarkController@tags'); 31 | 32 | Route::get('user', 'Api\v1\UserController@edit'); 33 | Route::put('user', 'Api\v1\UserController@update'); 34 | }); 35 | 36 | Route::group(['middleware' => ['web']], function () { 37 | Route::get('/', 'PagesController@showIndex'); 38 | Route::get('login', 'PagesController@showLogin'); 39 | if (env('ENABLE_REGISTER') === true) { 40 | Route::get('register', 'PagesController@showRegister')->name('register'); 41 | if (env('ENABLE_REGISTER_MAIL') == true) { 42 | Route::post('auth/register', 'PagesController@register')->middleware('mail'); 43 | Route::get('auth/validation/{hashValidation}', 'PagesController@validation')->name('validation'); 44 | } else { 45 | Route::post('auth/register', 'PagesController@register'); 46 | } 47 | } 48 | Route::post('auth/login', 'PagesController@login'); 49 | Route::get('auth/logout', 'PagesController@logout'); 50 | }); 51 | 52 | /** 53 | * User admin process 54 | */ 55 | if (env('ADMIN_ENABLED') === true) { 56 | Route::group(['prefix' => 'admin/', 'middleware' => ['web', 'auth']], function () { 57 | Route::get('users', 'UserAdminController@index'); 58 | }); 59 | 60 | Route::group(['prefix' => 'admin/', 'middleware' => ['web', 'auth']], function () { 61 | Route::get('user/create', 'UserAdminController@create'); 62 | Route::post('user/store', 'UserAdminController@store'); 63 | Route::get('user/{id}/edit', 'UserAdminController@edit'); 64 | Route::put('user/{id}', 'UserAdminController@update'); 65 | Route::delete('user/{id}', 'UserAdminController@destroy'); 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /app/Jobs/Job.php: -------------------------------------------------------------------------------- 1 | $user], function ($m) use($user) { 12 | $m->from(env('MAIL_USERNAME', 'user@domain.com'), 'Bookmark Manager'); 13 | 14 | $m->to($user->username, $user->name)->subject('Account confirmation'); 15 | }); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/Policies/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devimust/easy-bookmark-manager/e03f619d1da89d32e5f06a33ac335c50710e9823/app/Policies/.gitkeep -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | 'App\Policies\ModelPolicy', 17 | ]; 18 | 19 | /** 20 | * Register any application authentication / authorization services. 21 | * 22 | * @param \Illuminate\Contracts\Auth\Access\Gate $gate 23 | * @return void 24 | */ 25 | public function boot(GateContract $gate) 26 | { 27 | $this->registerPolicies($gate); 28 | 29 | // 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Providers/EventServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'App\Listeners\EventListener', 18 | ], 19 | ]; 20 | 21 | /** 22 | * Register any other events for your application. 23 | * 24 | * @param \Illuminate\Contracts\Events\Dispatcher $events 25 | * @return void 26 | */ 27 | public function boot(DispatcherContract $events) 28 | { 29 | parent::boot($events); 30 | 31 | // 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | group(['namespace' => $this->namespace], function ($router) { 41 | require app_path('Http/routes.php'); 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Tag.php: -------------------------------------------------------------------------------- 1 | belongsToMany('App\Bookmark', 'bookmark_tag'); 32 | } 33 | 34 | /** 35 | * Get the user linked to the tag. 36 | */ 37 | public function user() 38 | { 39 | return $this->belongsTo('App\User'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/User.php: -------------------------------------------------------------------------------- 1 | hasMany('App\Bookmark'); 41 | } 42 | 43 | /** 44 | * Get the bookmarks for the tag. 45 | */ 46 | public function tags() 47 | { 48 | return $this->hasMany('App\Tag'); 49 | } 50 | 51 | public static function getFormMessages() 52 | { 53 | return [ 54 | 'username.required' => trans('messages.userNameRequired'), 55 | 'username.email' => trans('messages.usernameEmail'), 56 | 'username.unique' => trans('messages.usernameUnique') 57 | ]; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make(Illuminate\Contracts\Console\Kernel::class); 32 | 33 | $status = $kernel->handle( 34 | $input = new Symfony\Component\Console\Input\ArgvInput, 35 | new Symfony\Component\Console\Output\ConsoleOutput 36 | ); 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Shutdown The Application 41 | |-------------------------------------------------------------------------- 42 | | 43 | | Once Artisan has finished running. We will fire off the shutdown events 44 | | so that any final work may be done by the application before we shut 45 | | down the process. This is the last thing to happen to the request. 46 | | 47 | */ 48 | 49 | $kernel->terminate($input, $status); 50 | 51 | exit($status); 52 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 30 | Illuminate\Contracts\Http\Kernel::class, 31 | App\Http\Kernel::class 32 | ); 33 | 34 | $app->singleton( 35 | Illuminate\Contracts\Console\Kernel::class, 36 | App\Console\Kernel::class 37 | ); 38 | 39 | $app->singleton( 40 | Illuminate\Contracts\Debug\ExceptionHandler::class, 41 | App\Exceptions\Handler::class 42 | ); 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Return The Application 47 | |-------------------------------------------------------------------------- 48 | | 49 | | This script returns the application instance. The instance is given to 50 | | the calling script so we can separate the building of the instances 51 | | from the actual running of the application and sending responses. 52 | | 53 | */ 54 | 55 | return $app; 56 | -------------------------------------------------------------------------------- /bootstrap/autoload.php: -------------------------------------------------------------------------------- 1 | =5.5.9", 9 | "laravel/framework": "5.2.*", 10 | "laravelcollective/html": "^5.2", 11 | "ramsey/uuid": "^3.1", 12 | "guzzlehttp/guzzle": "^6.2" 13 | }, 14 | "require-dev": { 15 | "fzaninotto/faker": "~1.4", 16 | "mockery/mockery": "0.9.*", 17 | "phpunit/phpunit": "~4.0", 18 | "symfony/css-selector": "2.8.*|3.0.*", 19 | "symfony/dom-crawler": "2.8.*|3.0.*" 20 | }, 21 | "autoload": { 22 | "classmap": [ 23 | "database" 24 | ], 25 | "psr-4": { 26 | "App\\": "app/" 27 | } 28 | }, 29 | "autoload-dev": { 30 | "classmap": [ 31 | "tests/TestCase.php" 32 | ] 33 | }, 34 | "scripts": { 35 | "post-install-cmd": [ 36 | "[ ! -f .env ] && php -r \"copy('.env.example', '.env');\" && php artisan key:generate || echo 'Found .env file!'", 37 | "php artisan clear-compiled", 38 | "php artisan optimize" 39 | ], 40 | "pre-update-cmd": [ 41 | "php artisan clear-compiled" 42 | ], 43 | "post-update-cmd": [ 44 | "php artisan optimize" 45 | ] 46 | }, 47 | "config": { 48 | "preferred-install": "dist" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /config/app.php: -------------------------------------------------------------------------------- 1 | env('APP_ENV', 'production'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Application Debug Mode 21 | |-------------------------------------------------------------------------- 22 | | 23 | | When your application is in debug mode, detailed error messages with 24 | | stack traces will be shown on every error that occurs within your 25 | | application. If disabled, a simple generic error page is shown. 26 | | 27 | */ 28 | 29 | 'debug' => env('APP_DEBUG', false), 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Application URL 34 | |-------------------------------------------------------------------------- 35 | | 36 | | This URL is used by the console to properly generate URLs when using 37 | | the Artisan command line tool. You should set this to the root of 38 | | your application so that it is used when running Artisan tasks. 39 | | 40 | */ 41 | 42 | 'url' => 'http://localhost', 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Application Timezone 47 | |-------------------------------------------------------------------------- 48 | | 49 | | Here you may specify the default timezone for your application, which 50 | | will be used by the PHP date and date-time functions. We have gone 51 | | ahead and set this to a sensible default for you out of the box. 52 | | 53 | */ 54 | 55 | 'timezone' => 'UTC', 56 | 57 | /* 58 | |-------------------------------------------------------------------------- 59 | | Application Locale Configuration 60 | |-------------------------------------------------------------------------- 61 | | 62 | | The application locale determines the default locale that will be used 63 | | by the translation service provider. You are free to set this value 64 | | to any of the locales which will be supported by the application. 65 | | 66 | */ 67 | 68 | 'locale' => env('LOCALE', 'en'), 69 | 70 | /* 71 | |-------------------------------------------------------------------------- 72 | | Application Fallback Locale 73 | |-------------------------------------------------------------------------- 74 | | 75 | | The fallback locale determines the locale to use when the current one 76 | | is not available. You may change the value to correspond to any of 77 | | the language folders that are provided through your application. 78 | | 79 | */ 80 | 81 | 'fallback_locale' => env('LOCALE_FALLBACK', 'en'), 82 | 83 | /* 84 | |-------------------------------------------------------------------------- 85 | | Encryption Key 86 | |-------------------------------------------------------------------------- 87 | | 88 | | This key is used by the Illuminate encrypter service and should be set 89 | | to a random, 32 character string, otherwise these encrypted strings 90 | | will not be safe. Please do this before deploying an application! 91 | | 92 | */ 93 | 94 | 'key' => env('APP_KEY'), 95 | 96 | 'cipher' => 'AES-256-CBC', 97 | 98 | /* 99 | |-------------------------------------------------------------------------- 100 | | Logging Configuration 101 | |-------------------------------------------------------------------------- 102 | | 103 | | Here you may configure the log settings for your application. Out of 104 | | the box, Laravel uses the Monolog PHP logging library. This gives 105 | | you a variety of powerful log handlers / formatters to utilize. 106 | | 107 | | Available Settings: "single", "daily", "syslog", "errorlog" 108 | | 109 | */ 110 | 111 | 'log' => env('APP_LOG', 'single'), 112 | 113 | /* 114 | |-------------------------------------------------------------------------- 115 | | Autoloaded Service Providers 116 | |-------------------------------------------------------------------------- 117 | | 118 | | The service providers listed here will be automatically loaded on the 119 | | request to your application. Feel free to add your own services to 120 | | this array to grant expanded functionality to your applications. 121 | | 122 | */ 123 | 124 | 'providers' => [ 125 | 126 | /* 127 | * Laravel Framework Service Providers... 128 | */ 129 | Illuminate\Auth\AuthServiceProvider::class, 130 | Illuminate\Broadcasting\BroadcastServiceProvider::class, 131 | Illuminate\Bus\BusServiceProvider::class, 132 | Illuminate\Cache\CacheServiceProvider::class, 133 | Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, 134 | Illuminate\Cookie\CookieServiceProvider::class, 135 | Illuminate\Database\DatabaseServiceProvider::class, 136 | Illuminate\Encryption\EncryptionServiceProvider::class, 137 | Illuminate\Filesystem\FilesystemServiceProvider::class, 138 | Illuminate\Foundation\Providers\FoundationServiceProvider::class, 139 | Illuminate\Hashing\HashServiceProvider::class, 140 | Illuminate\Mail\MailServiceProvider::class, 141 | Illuminate\Pagination\PaginationServiceProvider::class, 142 | Illuminate\Pipeline\PipelineServiceProvider::class, 143 | Illuminate\Queue\QueueServiceProvider::class, 144 | Illuminate\Redis\RedisServiceProvider::class, 145 | Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, 146 | Illuminate\Session\SessionServiceProvider::class, 147 | Illuminate\Translation\TranslationServiceProvider::class, 148 | Illuminate\Validation\ValidationServiceProvider::class, 149 | Illuminate\View\ViewServiceProvider::class, 150 | Collective\Html\HtmlServiceProvider::class, 151 | 152 | /* 153 | * Application Service Providers... 154 | */ 155 | App\Providers\AppServiceProvider::class, 156 | App\Providers\AuthServiceProvider::class, 157 | App\Providers\EventServiceProvider::class, 158 | App\Providers\RouteServiceProvider::class, 159 | 160 | ], 161 | 162 | /* 163 | |-------------------------------------------------------------------------- 164 | | Class Aliases 165 | |-------------------------------------------------------------------------- 166 | | 167 | | This array of class aliases will be registered when this application 168 | | is started. However, feel free to register as many as you wish as 169 | | the aliases are "lazy" loaded so they don't hinder performance. 170 | | 171 | */ 172 | 173 | 'aliases' => [ 174 | 175 | 'App' => Illuminate\Support\Facades\App::class, 176 | 'Artisan' => Illuminate\Support\Facades\Artisan::class, 177 | 'Auth' => Illuminate\Support\Facades\Auth::class, 178 | 'Blade' => Illuminate\Support\Facades\Blade::class, 179 | 'Cache' => Illuminate\Support\Facades\Cache::class, 180 | 'Config' => Illuminate\Support\Facades\Config::class, 181 | 'Cookie' => Illuminate\Support\Facades\Cookie::class, 182 | 'Crypt' => Illuminate\Support\Facades\Crypt::class, 183 | 'DB' => Illuminate\Support\Facades\DB::class, 184 | 'Eloquent' => Illuminate\Database\Eloquent\Model::class, 185 | 'Event' => Illuminate\Support\Facades\Event::class, 186 | 'File' => Illuminate\Support\Facades\File::class, 187 | 'Gate' => Illuminate\Support\Facades\Gate::class, 188 | 'Hash' => Illuminate\Support\Facades\Hash::class, 189 | 'Lang' => Illuminate\Support\Facades\Lang::class, 190 | 'Log' => Illuminate\Support\Facades\Log::class, 191 | 'Mail' => Illuminate\Support\Facades\Mail::class, 192 | 'Password' => Illuminate\Support\Facades\Password::class, 193 | 'Queue' => Illuminate\Support\Facades\Queue::class, 194 | 'Redirect' => Illuminate\Support\Facades\Redirect::class, 195 | 'Redis' => Illuminate\Support\Facades\Redis::class, 196 | 'Request' => Illuminate\Support\Facades\Request::class, 197 | 'Response' => Illuminate\Support\Facades\Response::class, 198 | 'Route' => Illuminate\Support\Facades\Route::class, 199 | 'Schema' => Illuminate\Support\Facades\Schema::class, 200 | 'Session' => Illuminate\Support\Facades\Session::class, 201 | 'Storage' => Illuminate\Support\Facades\Storage::class, 202 | 'URL' => Illuminate\Support\Facades\URL::class, 203 | 'Validator' => Illuminate\Support\Facades\Validator::class, 204 | 'View' => Illuminate\Support\Facades\View::class, 205 | 'Form' => Collective\Html\FormFacade::class, 206 | 'Html' => Collective\Html\HtmlFacade::class, 207 | 208 | ], 209 | 210 | ]; 211 | -------------------------------------------------------------------------------- /config/auth.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'guard' => 'web', 18 | 'passwords' => 'users', 19 | ], 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Authentication Guards 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Next, you may define every authentication guard for your application. 27 | | Of course, a great default configuration has been defined for you 28 | | here which uses session storage and the Eloquent user provider. 29 | | 30 | | All authentication drivers have a user provider. This defines how the 31 | | users are actually retrieved out of your database or other storage 32 | | mechanisms used by this application to persist your user's data. 33 | | 34 | | Supported: "session", "token" 35 | | 36 | */ 37 | 38 | 'guards' => [ 39 | 'web' => [ 40 | 'driver' => 'session', 41 | 'provider' => 'users', 42 | ], 43 | 44 | 'api' => [ 45 | 'driver' => 'token', 46 | 'provider' => 'users', 47 | ], 48 | ], 49 | 50 | /* 51 | |-------------------------------------------------------------------------- 52 | | User Providers 53 | |-------------------------------------------------------------------------- 54 | | 55 | | All authentication drivers have a user provider. This defines how the 56 | | users are actually retrieved out of your database or other storage 57 | | mechanisms used by this application to persist your user's data. 58 | | 59 | | If you have multiple user tables or models you may configure multiple 60 | | sources which represent each model / table. These sources may then 61 | | be assigned to any extra authentication guards you have defined. 62 | | 63 | | Supported: "database", "eloquent" 64 | | 65 | */ 66 | 67 | 'providers' => [ 68 | 'users' => [ 69 | 'driver' => 'eloquent', 70 | 'model' => App\User::class, 71 | ], 72 | 73 | // 'users' => [ 74 | // 'driver' => 'database', 75 | // 'table' => 'users', 76 | // ], 77 | ], 78 | 79 | /* 80 | |-------------------------------------------------------------------------- 81 | | Resetting Passwords 82 | |-------------------------------------------------------------------------- 83 | | 84 | | Here you may set the options for resetting passwords including the view 85 | | that is your password reset e-mail. You may also set the name of the 86 | | table that maintains all of the reset tokens for your application. 87 | | 88 | | You may specify multiple password reset configurations if you have more 89 | | than one user table or model in the application and you want to have 90 | | separate password reset settings based on the specific user types. 91 | | 92 | | The expire time is the number of minutes that the reset token should be 93 | | considered valid. This security feature keeps tokens short-lived so 94 | | they have less time to be guessed. You may change this as needed. 95 | | 96 | */ 97 | 98 | 'passwords' => [ 99 | 'users' => [ 100 | 'provider' => 'users', 101 | 'email' => 'auth.emails.password', 102 | 'table' => 'password_resets', 103 | 'expire' => 60, 104 | ], 105 | ], 106 | 107 | ]; 108 | -------------------------------------------------------------------------------- /config/broadcasting.php: -------------------------------------------------------------------------------- 1 | env('BROADCAST_DRIVER', 'pusher'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Broadcast Connections 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may define all of the broadcast connections that will be used 24 | | to broadcast events to other systems or over websockets. Samples of 25 | | each available type of connection are provided inside this array. 26 | | 27 | */ 28 | 29 | 'connections' => [ 30 | 31 | 'pusher' => [ 32 | 'driver' => 'pusher', 33 | 'key' => env('PUSHER_KEY'), 34 | 'secret' => env('PUSHER_SECRET'), 35 | 'app_id' => env('PUSHER_APP_ID'), 36 | 'options' => [ 37 | // 38 | ], 39 | ], 40 | 41 | 'redis' => [ 42 | 'driver' => 'redis', 43 | 'connection' => 'default', 44 | ], 45 | 46 | 'log' => [ 47 | 'driver' => 'log', 48 | ], 49 | 50 | ], 51 | 52 | ]; 53 | -------------------------------------------------------------------------------- /config/cache.php: -------------------------------------------------------------------------------- 1 | env('CACHE_DRIVER', 'file'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Cache Stores 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may define all of the cache "stores" for your application as 24 | | well as their drivers. You may even define multiple stores for the 25 | | same cache driver to group types of items stored in your caches. 26 | | 27 | */ 28 | 29 | 'stores' => [ 30 | 31 | 'apc' => [ 32 | 'driver' => 'apc', 33 | ], 34 | 35 | 'array' => [ 36 | 'driver' => 'array', 37 | ], 38 | 39 | 'database' => [ 40 | 'driver' => 'database', 41 | 'table' => 'cache', 42 | 'connection' => null, 43 | ], 44 | 45 | 'file' => [ 46 | 'driver' => 'file', 47 | 'path' => storage_path('framework/cache'), 48 | ], 49 | 50 | 'memcached' => [ 51 | 'driver' => 'memcached', 52 | 'servers' => [ 53 | [ 54 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 55 | 'port' => env('MEMCACHED_PORT', 11211), 56 | 'weight' => 100, 57 | ], 58 | ], 59 | ], 60 | 61 | 'redis' => [ 62 | 'driver' => 'redis', 63 | 'connection' => 'default', 64 | ], 65 | 66 | ], 67 | 68 | /* 69 | |-------------------------------------------------------------------------- 70 | | Cache Key Prefix 71 | |-------------------------------------------------------------------------- 72 | | 73 | | When utilizing a RAM based store such as APC or Memcached, there might 74 | | be other applications utilizing the same cache. So, we'll specify a 75 | | value to get prefixed to all our keys so we can avoid collisions. 76 | | 77 | */ 78 | 79 | 'prefix' => 'laravel', 80 | 81 | ]; 82 | -------------------------------------------------------------------------------- /config/compile.php: -------------------------------------------------------------------------------- 1 | [ 17 | // 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Compiled File Providers 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may list service providers which define a "compiles" function 26 | | that returns additional files that should be compiled, providing an 27 | | easy way to get common files from any packages you are utilizing. 28 | | 29 | */ 30 | 31 | 'providers' => [ 32 | // 33 | ], 34 | 35 | ]; 36 | -------------------------------------------------------------------------------- /config/database.php: -------------------------------------------------------------------------------- 1 | PDO::FETCH_CLASS, 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Default Database Connection Name 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may specify which of the database connections below you wish 24 | | to use as your default connection for all database work. Of course 25 | | you may use many connections at once using the Database library. 26 | | 27 | */ 28 | 29 | 'default' => env('DB_CONNECTION', 'mysql'), 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Database Connections 34 | |-------------------------------------------------------------------------- 35 | | 36 | | Here are each of the database connections setup for your application. 37 | | Of course, examples of configuring each database platform that is 38 | | supported by Laravel is shown below to make development simple. 39 | | 40 | | 41 | | All database work in Laravel is done through the PHP PDO facilities 42 | | so make sure you have the driver for your particular database of 43 | | choice installed on your machine before you begin development. 44 | | 45 | */ 46 | 47 | 'connections' => [ 48 | 49 | 'sqlite' => [ 50 | 'driver' => 'sqlite', 51 | 'database' => database_path('database.sqlite'), 52 | 'prefix' => '', 53 | ], 54 | 55 | 'mysql' => [ 56 | 'driver' => 'mysql', 57 | 'host' => env('DB_HOST', 'localhost'), 58 | 'database' => env('DB_DATABASE', 'forge'), 59 | 'username' => env('DB_USERNAME', 'forge'), 60 | 'password' => env('DB_PASSWORD', ''), 61 | 'charset' => 'utf8', 62 | 'collation' => 'utf8_unicode_ci', 63 | 'prefix' => '', 64 | 'strict' => false, 65 | 'engine' => null, 66 | ], 67 | 68 | 'pgsql' => [ 69 | 'driver' => 'pgsql', 70 | 'host' => env('DB_HOST', 'localhost'), 71 | 'database' => env('DB_DATABASE', 'forge'), 72 | 'username' => env('DB_USERNAME', 'forge'), 73 | 'password' => env('DB_PASSWORD', ''), 74 | 'charset' => 'utf8', 75 | 'prefix' => '', 76 | 'schema' => 'public', 77 | ], 78 | 79 | 'sqlsrv' => [ 80 | 'driver' => 'sqlsrv', 81 | 'host' => env('DB_HOST', 'localhost'), 82 | 'database' => env('DB_DATABASE', 'forge'), 83 | 'username' => env('DB_USERNAME', 'forge'), 84 | 'password' => env('DB_PASSWORD', ''), 85 | 'charset' => 'utf8', 86 | 'prefix' => '', 87 | ], 88 | 89 | 'testing' => [ 90 | 'driver' => 'mysql', 91 | 'host' => env('DB_TEST_HOST', 'localhost'), 92 | 'database' => env('DB_TEST_DATABASE', 'homestead_test'), 93 | 'username' => env('DB_TEST_USERNAME', 'homestead'), 94 | 'password' => env('DB_TEST_PASSWORD', 'secret'), 95 | 'charset' => 'utf8', 96 | 'collation' => 'utf8_unicode_ci', 97 | 'prefix' => '', 98 | 'strict' => false, 99 | 'engine' => null, 100 | ], 101 | 102 | ], 103 | 104 | /* 105 | |-------------------------------------------------------------------------- 106 | | Migration Repository Table 107 | |-------------------------------------------------------------------------- 108 | | 109 | | This table keeps track of all the migrations that have already run for 110 | | your application. Using this information, we can determine which of 111 | | the migrations on disk haven't actually been run in the database. 112 | | 113 | */ 114 | 115 | 'migrations' => 'migrations', 116 | 117 | /* 118 | |-------------------------------------------------------------------------- 119 | | Redis Databases 120 | |-------------------------------------------------------------------------- 121 | | 122 | | Redis is an open source, fast, and advanced key-value store that also 123 | | provides a richer set of commands than a typical key-value systems 124 | | such as APC or Memcached. Laravel makes it easy to dig right in. 125 | | 126 | */ 127 | 128 | 'redis' => [ 129 | 130 | 'cluster' => false, 131 | 132 | 'default' => [ 133 | 'host' => env('REDIS_HOST', 'localhost'), 134 | 'password' => env('REDIS_PASSWORD', null), 135 | 'port' => env('REDIS_PORT', 6379), 136 | 'database' => 0, 137 | ], 138 | 139 | ], 140 | 141 | ]; 142 | -------------------------------------------------------------------------------- /config/filesystems.php: -------------------------------------------------------------------------------- 1 | 'local', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Default Cloud Filesystem Disk 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Many applications store files both locally and in the cloud. For this 26 | | reason, you may specify a default "cloud" driver here. This driver 27 | | will be bound as the Cloud disk implementation in the container. 28 | | 29 | */ 30 | 31 | 'cloud' => 's3', 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | Filesystem Disks 36 | |-------------------------------------------------------------------------- 37 | | 38 | | Here you may configure as many filesystem "disks" as you wish, and you 39 | | may even configure multiple disks of the same driver. Defaults have 40 | | been setup for each driver as an example of the required options. 41 | | 42 | */ 43 | 44 | 'disks' => [ 45 | 46 | 'local' => [ 47 | 'driver' => 'local', 48 | 'root' => storage_path('app'), 49 | ], 50 | 51 | 'public' => [ 52 | 'driver' => 'local', 53 | 'root' => storage_path('app/public'), 54 | 'visibility' => 'public', 55 | ], 56 | 57 | 's3' => [ 58 | 'driver' => 's3', 59 | 'key' => 'your-key', 60 | 'secret' => 'your-secret', 61 | 'region' => 'your-region', 62 | 'bucket' => 'your-bucket', 63 | ], 64 | 65 | ], 66 | 67 | ]; 68 | -------------------------------------------------------------------------------- /config/mail.php: -------------------------------------------------------------------------------- 1 | env('MAIL_DRIVER', 'smtp'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | SMTP Host Address 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may provide the host address of the SMTP server used by your 26 | | applications. A default option is provided that is compatible with 27 | | the Mailgun mail service which will provide reliable deliveries. 28 | | 29 | */ 30 | 31 | 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | SMTP Host Port 36 | |-------------------------------------------------------------------------- 37 | | 38 | | This is the SMTP port used by your application to deliver e-mails to 39 | | users of the application. Like the host we have set this value to 40 | | stay compatible with the Mailgun e-mail application by default. 41 | | 42 | */ 43 | 44 | 'port' => env('MAIL_PORT', 587), 45 | 46 | /* 47 | |-------------------------------------------------------------------------- 48 | | Global "From" Address 49 | |-------------------------------------------------------------------------- 50 | | 51 | | You may wish for all e-mails sent by your application to be sent from 52 | | the same address. Here, you may specify a name and address that is 53 | | used globally for all e-mails that are sent by your application. 54 | | 55 | */ 56 | 57 | 'from' => ['address' => null, 'name' => null], 58 | 59 | /* 60 | |-------------------------------------------------------------------------- 61 | | E-Mail Encryption Protocol 62 | |-------------------------------------------------------------------------- 63 | | 64 | | Here you may specify the encryption protocol that should be used when 65 | | the application send e-mail messages. A sensible default using the 66 | | transport layer security protocol should provide great security. 67 | | 68 | */ 69 | 70 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'), 71 | 72 | /* 73 | |-------------------------------------------------------------------------- 74 | | SMTP Server Username 75 | |-------------------------------------------------------------------------- 76 | | 77 | | If your SMTP server requires a username for authentication, you should 78 | | set it here. This will get used to authenticate with your server on 79 | | connection. You may also set the "password" value below this one. 80 | | 81 | */ 82 | 83 | 'username' => env('MAIL_USERNAME'), 84 | 85 | /* 86 | |-------------------------------------------------------------------------- 87 | | SMTP Server Password 88 | |-------------------------------------------------------------------------- 89 | | 90 | | Here you may set the password required by your SMTP server to send out 91 | | messages from your application. This will be given to the server on 92 | | connection so that the application will be able to send messages. 93 | | 94 | */ 95 | 96 | 'password' => env('MAIL_PASSWORD'), 97 | 98 | /* 99 | |-------------------------------------------------------------------------- 100 | | Sendmail System Path 101 | |-------------------------------------------------------------------------- 102 | | 103 | | When using the "sendmail" driver to send e-mails, we will need to know 104 | | the path to where Sendmail lives on this server. A default path has 105 | | been provided here, which will work well on most of your systems. 106 | | 107 | */ 108 | 109 | 'sendmail' => '/usr/sbin/sendmail -bs', 110 | 111 | ]; 112 | -------------------------------------------------------------------------------- /config/queue.php: -------------------------------------------------------------------------------- 1 | env('QUEUE_DRIVER', 'sync'), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Queue Connections 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Here you may configure the connection information for each server that 27 | | is used by your application. A default configuration has been added 28 | | for each back-end shipped with Laravel. You are free to add more. 29 | | 30 | */ 31 | 32 | 'connections' => [ 33 | 34 | 'sync' => [ 35 | 'driver' => 'sync', 36 | ], 37 | 38 | 'database' => [ 39 | 'driver' => 'database', 40 | 'table' => 'jobs', 41 | 'queue' => 'default', 42 | 'expire' => 60, 43 | ], 44 | 45 | 'beanstalkd' => [ 46 | 'driver' => 'beanstalkd', 47 | 'host' => 'localhost', 48 | 'queue' => 'default', 49 | 'ttr' => 60, 50 | ], 51 | 52 | 'sqs' => [ 53 | 'driver' => 'sqs', 54 | 'key' => 'your-public-key', 55 | 'secret' => 'your-secret-key', 56 | 'prefix' => 'https://sqs.us-east-1.amazonaws.com/your-account-id', 57 | 'queue' => 'your-queue-name', 58 | 'region' => 'us-east-1', 59 | ], 60 | 61 | 'redis' => [ 62 | 'driver' => 'redis', 63 | 'connection' => 'default', 64 | 'queue' => 'default', 65 | 'expire' => 60, 66 | ], 67 | 68 | ], 69 | 70 | /* 71 | |-------------------------------------------------------------------------- 72 | | Failed Queue Jobs 73 | |-------------------------------------------------------------------------- 74 | | 75 | | These options configure the behavior of failed queue job logging so you 76 | | can control which database and table are used to store the jobs that 77 | | have failed. You may change them to any database / table you wish. 78 | | 79 | */ 80 | 81 | 'failed' => [ 82 | 'database' => env('DB_CONNECTION', 'mysql'), 83 | 'table' => 'failed_jobs', 84 | ], 85 | 86 | ]; 87 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => env('MAILGUN_DOMAIN'), 19 | 'secret' => env('MAILGUN_SECRET'), 20 | ], 21 | 22 | 'mandrill' => [ 23 | 'secret' => env('MANDRILL_SECRET'), 24 | ], 25 | 26 | 'ses' => [ 27 | 'key' => env('SES_KEY'), 28 | 'secret' => env('SES_SECRET'), 29 | 'region' => 'us-east-1', 30 | ], 31 | 32 | 'stripe' => [ 33 | 'model' => App\User::class, 34 | 'key' => env('STRIPE_KEY'), 35 | 'secret' => env('STRIPE_SECRET'), 36 | ], 37 | 38 | ]; 39 | -------------------------------------------------------------------------------- /config/session.php: -------------------------------------------------------------------------------- 1 | env('SESSION_DRIVER', 'file'), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Session Lifetime 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Here you may specify the number of minutes that you wish the session 27 | | to be allowed to remain idle before it expires. If you want them 28 | | to immediately expire on the browser closing, set that option. 29 | | 30 | */ 31 | 32 | 'lifetime' => 120, 33 | 34 | 'expire_on_close' => false, 35 | 36 | /* 37 | |-------------------------------------------------------------------------- 38 | | Session Encryption 39 | |-------------------------------------------------------------------------- 40 | | 41 | | This option allows you to easily specify that all of your session data 42 | | should be encrypted before it is stored. All encryption will be run 43 | | automatically by Laravel and you can use the Session like normal. 44 | | 45 | */ 46 | 47 | 'encrypt' => false, 48 | 49 | /* 50 | |-------------------------------------------------------------------------- 51 | | Session File Location 52 | |-------------------------------------------------------------------------- 53 | | 54 | | When using the native session driver, we need a location where session 55 | | files may be stored. A default has been set for you but a different 56 | | location may be specified. This is only needed for file sessions. 57 | | 58 | */ 59 | 60 | 'files' => storage_path('framework/sessions'), 61 | 62 | /* 63 | |-------------------------------------------------------------------------- 64 | | Session Database Connection 65 | |-------------------------------------------------------------------------- 66 | | 67 | | When using the "database" or "redis" session drivers, you may specify a 68 | | connection that should be used to manage these sessions. This should 69 | | correspond to a connection in your database configuration options. 70 | | 71 | */ 72 | 73 | 'connection' => null, 74 | 75 | /* 76 | |-------------------------------------------------------------------------- 77 | | Session Database Table 78 | |-------------------------------------------------------------------------- 79 | | 80 | | When using the "database" session driver, you may specify the table we 81 | | should use to manage the sessions. Of course, a sensible default is 82 | | provided for you; however, you are free to change this as needed. 83 | | 84 | */ 85 | 86 | 'table' => 'sessions', 87 | 88 | /* 89 | |-------------------------------------------------------------------------- 90 | | Session Sweeping Lottery 91 | |-------------------------------------------------------------------------- 92 | | 93 | | Some session drivers must manually sweep their storage location to get 94 | | rid of old sessions from storage. Here are the chances that it will 95 | | happen on a given request. By default, the odds are 2 out of 100. 96 | | 97 | */ 98 | 99 | 'lottery' => [2, 100], 100 | 101 | /* 102 | |-------------------------------------------------------------------------- 103 | | Session Cookie Name 104 | |-------------------------------------------------------------------------- 105 | | 106 | | Here you may change the name of the cookie used to identify a session 107 | | instance by ID. The name specified here will get used every time a 108 | | new session cookie is created by the framework for every driver. 109 | | 110 | */ 111 | 112 | 'cookie' => 'laravel_session', 113 | 114 | /* 115 | |-------------------------------------------------------------------------- 116 | | Session Cookie Path 117 | |-------------------------------------------------------------------------- 118 | | 119 | | The session cookie path determines the path for which the cookie will 120 | | be regarded as available. Typically, this will be the root path of 121 | | your application but you are free to change this when necessary. 122 | | 123 | */ 124 | 125 | 'path' => '/', 126 | 127 | /* 128 | |-------------------------------------------------------------------------- 129 | | Session Cookie Domain 130 | |-------------------------------------------------------------------------- 131 | | 132 | | Here you may change the domain of the cookie used to identify a session 133 | | in your application. This will determine which domains the cookie is 134 | | available to in your application. A sensible default has been set. 135 | | 136 | */ 137 | 138 | 'domain' => null, 139 | 140 | /* 141 | |-------------------------------------------------------------------------- 142 | | HTTPS Only Cookies 143 | |-------------------------------------------------------------------------- 144 | | 145 | | By setting this option to true, session cookies will only be sent back 146 | | to the server if the browser has a HTTPS connection. This will keep 147 | | the cookie from being sent to you if it can not be done securely. 148 | | 149 | */ 150 | 151 | 'secure' => false, 152 | 153 | ]; 154 | -------------------------------------------------------------------------------- /config/view.php: -------------------------------------------------------------------------------- 1 | [ 17 | realpath(base_path('resources/views')), 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Compiled View Path 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This option determines where all the compiled Blade templates will be 26 | | stored for your application. Typically, this is within the storage 27 | | directory. However, as usual, you are free to change this value. 28 | | 29 | */ 30 | 31 | 'compiled' => realpath(storage_path('framework/views')), 32 | 33 | ]; 34 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | -------------------------------------------------------------------------------- /database/database.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devimust/easy-bookmark-manager/e03f619d1da89d32e5f06a33ac335c50710e9823/database/database.sqlite -------------------------------------------------------------------------------- /database/factories/ModelFactory.php: -------------------------------------------------------------------------------- 1 | define(App\User::class, function (Faker\Generator $faker) { 15 | return [ 16 | 'name' => $faker->name, 17 | 'email' => $faker->email, 18 | 'password' => bcrypt(str_random(10)), 19 | 'remember_token' => str_random(10), 20 | ]; 21 | }); 22 | -------------------------------------------------------------------------------- /database/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devimust/easy-bookmark-manager/e03f619d1da89d32e5f06a33ac335c50710e9823/database/migrations/.gitkeep -------------------------------------------------------------------------------- /database/migrations/2016_02_16_000001_create_users_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('name'); 18 | $table->string('username')->unique(); 19 | $table->string('password', 60); 20 | $table->boolean('administrator'); 21 | $table->rememberToken(); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::drop('users'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /database/migrations/2016_02_17_000004_create_bookmarks_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('title'); 18 | $table->boolean('favourite'); 19 | $table->string('link'); 20 | $table->text('snippet'); 21 | $table->text('icon'); 22 | $table->integer('user_id')->unsigned(); 23 | $table->string('category'); 24 | $table->timestamps(); 25 | 26 | $table 27 | ->foreign('user_id') 28 | ->references('id') 29 | ->on('users') 30 | ->onDelete('cascade'); 31 | }); 32 | } 33 | 34 | /** 35 | * Reverse the migrations. 36 | * 37 | * @return void 38 | */ 39 | public function down() 40 | { 41 | Schema::drop('bookmarks'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /database/migrations/2016_02_17_000005_create_tags_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('name'); 18 | $table->integer('user_id')->unsigned()->index(); 19 | $table->timestamps(); 20 | 21 | $table 22 | ->foreign('user_id') 23 | ->references('id') 24 | ->on('users') 25 | ->onDelete('cascade'); 26 | }); 27 | 28 | Schema::create('bookmark_tag', function(Blueprint $table) 29 | { 30 | $table->integer('bookmark_id')->unsigned()->index(); 31 | $table->foreign('bookmark_id')->references('id')->on('bookmarks'); 32 | //->onDelete('cascade'); 33 | 34 | $table->integer('tag_id')->unsigned()->index(); 35 | $table->foreign('tag_id')->references('id')->on('tags'); 36 | //->onDelete('cascade'); 37 | }); 38 | } 39 | 40 | /** 41 | * Reverse the migrations. 42 | * 43 | * @return void 44 | */ 45 | public function down() 46 | { 47 | Schema::drop('bookmark_tag'); 48 | Schema::drop('tags'); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /database/migrations/2016_10_22_215900_add_users_confirmed_column.php: -------------------------------------------------------------------------------- 1 | boolean('confirmed')->default(false); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | * 23 | * @return void 24 | */ 25 | public function down() 26 | { 27 | Schema::table('users', function ($table) { 28 | $table->dropColumn('confirmed'); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/migrations/2016_11_20_184423_add_user_validation_hash.php: -------------------------------------------------------------------------------- 1 | string('hashValidation')->nullable(); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | * 23 | * @return void 24 | */ 25 | public function down() 26 | { 27 | Schema::table('users', function ($table) { 28 | $table->dropColumn('hashValidation'); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/seeds/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devimust/easy-bookmark-manager/e03f619d1da89d32e5f06a33ac335c50710e9823/database/seeds/.gitkeep -------------------------------------------------------------------------------- /database/seeds/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | 'Admin', 19 | 'username' => 'admin', 20 | 'password' => Hash::make('nimda'), 21 | 'administrator' => true, 22 | 'confirmed' => true 23 | ]); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /database/seeds/DummyBookmarksSeeder.php: -------------------------------------------------------------------------------- 1 | command->getOutput()->writeln("Importing: " . count($importObject->bookmarks) . " rows"); 21 | 22 | foreach ($importObject->bookmarks as $bookmark) { 23 | $tagIds = []; 24 | 25 | foreach ($bookmark->tags as $tagString) { 26 | $tag = Tag::where('name', '=', $tagString)->first(); 27 | if (!$tag) { 28 | $tag = Tag::create([ 29 | 'name' => $tagString, 30 | 'user_id' => 1 31 | ]); 32 | } 33 | $tagIds[] = $tag->id; 34 | } 35 | 36 | Bookmark::create([ 37 | 'title' => $bookmark->title, 38 | 'link' => $bookmark->link, 39 | 'category' => $bookmark->category, 40 | 'favourite' => $bookmark->favourite, 41 | 'snippet' => isset($bookmark->snippet) ? $bookmark->snippet : '', 42 | 'icon' => $bookmark->icon, 43 | 'created_at' => $bookmark->created, 44 | 'updated_at' => $bookmark->modified, 45 | 'user_id' => 1 46 | ])->tags()->attach($tagIds); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /docker-compose.sample.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | db: 4 | image: mysql:latest 5 | environment: 6 | - MYSQL_DATABASE=testdb 7 | - MYSQL_USER=testuser 8 | - MYSQL_PASSWORD=testpassword 9 | - MYSQL_ROOT_PASSWORD=rootp4ssword 10 | app: 11 | image: debian:jessie 12 | volumes: 13 | - .:/app 14 | web: 15 | image: rolckers/web-apache-php 16 | ports: 17 | - "8000:80" 18 | depends_on: 19 | - db 20 | links: 21 | - db 22 | volumes_from: 23 | - app 24 | composer: 25 | image: rolckers/web-apache-php 26 | entrypoint: 27 | - composer 28 | command: --help 29 | volumes_from: 30 | - app 31 | artisan: 32 | image: rolckers/web-apache-php 33 | entrypoint: 34 | - php 35 | - artisan 36 | command: --help 37 | links: 38 | - db 39 | volumes_from: 40 | - app 41 | phpunit: 42 | image: rolckers/web-apache-php 43 | entrypoint: 44 | - vendor/bin/phpunit 45 | links: 46 | - db 47 | volumes_from: 48 | - app 49 | npm: 50 | image: rolckers/web-nodejs:4.6 51 | command: --help 52 | entrypoint: 53 | - npm 54 | volumes_from: 55 | - app 56 | gulp: 57 | image: rolckers/web-nodejs:4.6 58 | command: --help 59 | entrypoint: 60 | - gulp 61 | volumes_from: 62 | - app 63 | -------------------------------------------------------------------------------- /docker/create-rc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Create release candidate..." 4 | echo 5 | 6 | if [ ! -d .git ]; then 7 | echo "ensure this script is run from the project root" 8 | exit 1 9 | fi 10 | 11 | if [ -z "$1" ]; then 12 | echo "no version specified, please run with e.g. 'docker/create-rc.sh v1.0'" 13 | exit 1 14 | fi 15 | 16 | if [ -f "$1.tar.gz" ]; then 17 | echo "the file $1.tar.gz exist, please remove and try again" 18 | exit 1 19 | fi 20 | 21 | docker-compose run --rm gulp --production 22 | 23 | docker-compose run --rm phpunit 24 | 25 | find ./storage -type 'f' | grep -v ".gitignore" | xargs rm -f 26 | 27 | TIMESTAMP=$(date) 28 | 29 | echo "$1 created at $TIMESTAMP" >> ./VERSION 30 | 31 | tar \ 32 | --owner=0 --group=0 \ 33 | -czf ./$1.tar.gz \ 34 | app/ \ 35 | bootstrap/ \ 36 | config/ \ 37 | database/ \ 38 | docker/ \ 39 | public/ \ 40 | resources/ \ 41 | storage/ \ 42 | vendor/ \ 43 | .env.docker \ 44 | .env.example \ 45 | .htaccess \ 46 | artisan \ 47 | docker-compose.sample.yml \ 48 | server.php \ 49 | VERSION 50 | -------------------------------------------------------------------------------- /docker/kill-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Killing everything docker..." 4 | 5 | docker kill $(docker ps -q) 6 | docker rm -f $(docker ps -a -q) 7 | docker rmi -f $(docker images -q) 8 | -------------------------------------------------------------------------------- /docker/prep-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Prep local docker dev environment..." 4 | echo 5 | 6 | if ! [ -x "$(command -v docker)" ]; then 7 | echo "docker is not installed" 8 | exit 1 9 | fi 10 | 11 | if ! [ -x "$(command -v docker-compose)" ]; then 12 | echo "docker-compose is not installed" 13 | exit 1 14 | fi 15 | 16 | if [ ! -f ./.env ]; then 17 | cp ./.env.docker ./.env 18 | echo "creating .env from .env.docker, remember to modify" 19 | fi 20 | 21 | if [ ! -f ./docker-compose.yml ]; then 22 | cp ./docker-compose.sample.yml ./docker-compose.yml 23 | echo "creating docker-compose.yml from docker-compose.sample.yml, remember to modify" 24 | fi 25 | 26 | #chmod -R a+w ./storage 27 | 28 | docker-compose stop 29 | docker-compose rm -f 30 | docker-compose build 31 | docker-compose run --rm composer install 32 | docker-compose run --rm npm install 33 | docker-compose run --rm gulp --production 34 | docker-compose run --rm artisan key:generate 35 | docker-compose run --rm phpunit 36 | docker-compose up -d 37 | sleep 10s 38 | docker-compose run --rm artisan migrate:refresh --seed 39 | docker-compose run --rm artisan db:seed --class=DummyBookmarksSeeder 40 | 41 | echo "done" 42 | -------------------------------------------------------------------------------- /elixir-extensions.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var shell = require('gulp-shell'); 3 | var Elixir = require('laravel-elixir'); 4 | var del = require('del'); 5 | var jshint = require('gulp-jshint'); 6 | 7 | var Task = Elixir.Task; 8 | 9 | Elixir.extend('remove', function(path) { 10 | 11 | new Task('remove', function() { 12 | del(path); 13 | return gulp.src(''); 14 | }); 15 | 16 | }); 17 | 18 | 19 | Elixir.extend('jshint', function(path) { 20 | 21 | new Task('jshint', function() { 22 | return gulp.src(path) 23 | .pipe(jshint()) 24 | .pipe(jshint.reporter('default')); 25 | }); 26 | 27 | }); 28 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var elixir = require('laravel-elixir'); 2 | 3 | require('./elixir-extensions') 4 | 5 | /* 6 | |-------------------------------------------------------------------------- 7 | | Elixir Asset Management 8 | |-------------------------------------------------------------------------- 9 | | 10 | | Elixir provides a clean, fluent API for defining some basic Gulp tasks 11 | | for your Laravel application. By default, we are compiling the Sass 12 | | file for our application, as well as publishing vendor resources. 13 | | 14 | */ 15 | 16 | elixir(function(mix) { 17 | 18 | // delete public files 19 | mix.remove([ 20 | 'public/build', 21 | 'public/css', 22 | 'public/js', 23 | 'public/fonts', 24 | 'public/views', 25 | 'public/lang' 26 | ]); 27 | 28 | // vendor fonts 29 | mix.copy('node_modules/bootstrap/dist/fonts', 'public/fonts'); 30 | mix.copy('node_modules/font-awesome/fonts', 'public/fonts'); 31 | 32 | // vendor styles 33 | mix 34 | .styles([ 35 | 'normalize.css/normalize.css', 36 | 'font-awesome/css/font-awesome.min.css', 37 | 'select2/dist/css/select2.min.css' 38 | ], 'public/css/vendor.css', 'node_modules') 39 | .sass('bootstrap-darkly.scss') 40 | .sass('bootstrap-sandstone.scss') 41 | .sass('bootstrap-united.scss') 42 | .sass('bootstrap-yeti.scss') 43 | .sass('bootstrap-default.scss'); 44 | 45 | // vendor scripts 46 | mix 47 | .scripts([ 48 | 'node_modules/jquery/dist/jquery.min.js', 49 | 'node_modules/bootstrap/dist/js/bootstrap.min.js', 50 | 'node_modules/angular/angular.min.js', 51 | 'node_modules/angular-translate/dist/angular-translate.min.js', 52 | 'node_modules/angular-translate-loader-url/angular-translate-loader-url.min.js', 53 | 'node_modules/angular-route/angular-route.min.js', 54 | 'node_modules/select2/dist/js/select2.full.min.js', 55 | 'node_modules/ace-editor-builds/src-min-noconflict/ace.js', 56 | 'node_modules/ace-editor-builds/src-min-noconflict/mode-snippets.js', 57 | 'node_modules/ace-editor-builds/src-min-noconflict/theme-github.js' 58 | ], 'public/js/vendor.js', 'node_modules'); 59 | 60 | 61 | // app styles 62 | mix.sass('main.scss'); 63 | 64 | // app scripts 65 | mix 66 | .jshint('resources/assets/js/**/*.js') 67 | .scripts([ 68 | 'main.js', 69 | '' // include everything except main.js 70 | ], 'public/js/main.js'); 71 | 72 | // app views 73 | mix.copy('resources/assets/views', 'public/views'); 74 | 75 | // version js/css 76 | mix.version([ 77 | 'public/css/main.css', 78 | 'public/js/main.js' 79 | ]); 80 | 81 | // app language files 82 | mix.copy('resources/lang/*.json', 'public/lang'); 83 | 84 | }); 85 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "del": "^3.0.0", 5 | "gulp": "^3.9.1", 6 | "gulp-jshint": "^2.1.0", 7 | "gulp-shell": "^0.6.5", 8 | "jshint": "^2.9.5" 9 | }, 10 | "dependencies": { 11 | "ace-editor-builds": "^1.2.4", 12 | "angular": "^1.6.10", 13 | "angular-route": "^1.6.10", 14 | "angular-translate": "^2.17.1", 15 | "angular-translate-loader-url": "^2.17.1", 16 | "bootstrap": "^3.3.7", 17 | "font-awesome": "^4.7.0", 18 | "jquery": "^3.3.1", 19 | "laravel-elixir": "^6.0.0-18", 20 | "normalize.css": "^8.0.0", 21 | "select2": "^4.0.6-rc.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | app/ 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Redirect Trailing Slashes If Not A Folder... 9 | RewriteCond %{REQUEST_FILENAME} !-d 10 | RewriteRule ^(.*)/$ /$1 [L,R=301] 11 | 12 | # Handle Front Controller... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_FILENAME} !-f 15 | RewriteRule ^ index.php [L] 16 | 17 | # Handle Authorization Header 18 | RewriteCond %{HTTP:Authorization} . 19 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 20 | 21 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devimust/easy-bookmark-manager/e03f619d1da89d32e5f06a33ac335c50710e9823/public/favicon.ico -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | /* 11 | |-------------------------------------------------------------------------- 12 | | Register The Auto Loader 13 | |-------------------------------------------------------------------------- 14 | | 15 | | Composer provides a convenient, automatically generated class loader for 16 | | our application. We just need to utilize it! We'll simply require it 17 | | into the script here so that we don't have to worry about manual 18 | | loading any of our classes later on. It feels nice to relax. 19 | | 20 | */ 21 | 22 | require __DIR__.'/../bootstrap/autoload.php'; 23 | 24 | /* 25 | |-------------------------------------------------------------------------- 26 | | Turn On The Lights 27 | |-------------------------------------------------------------------------- 28 | | 29 | | We need to illuminate PHP development, so let us turn on the lights. 30 | | This bootstraps the framework and gets it ready for use, then it 31 | | will load up this application so that we can run it and send 32 | | the responses back to the browser and delight our users. 33 | | 34 | */ 35 | 36 | $app = require_once __DIR__.'/../bootstrap/app.php'; 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Run The Application 41 | |-------------------------------------------------------------------------- 42 | | 43 | | Once we have the application, we can handle the incoming request 44 | | through the kernel, and send the associated response back to 45 | | the client's browser allowing them to enjoy the creative 46 | | and wonderful application we have prepared for them. 47 | | 48 | */ 49 | 50 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); 51 | 52 | $response = $kernel->handle( 53 | $request = Illuminate\Http\Request::capture() 54 | ); 55 | 56 | $response->send(); 57 | 58 | $kernel->terminate($request, $response); 59 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /resources/assets/images/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devimust/easy-bookmark-manager/e03f619d1da89d32e5f06a33ac335c50710e9823/resources/assets/images/screenshot1.png -------------------------------------------------------------------------------- /resources/assets/js/bookmarkEditController.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | var bookmarkEditController = function ($scope, $location, $routeParams, $http, $interval, userService, 4 | bookmarkService, $window, $filter) { 5 | 6 | $scope.viewReady = false; 7 | $scope.errorMessage = ''; 8 | $scope.goodMessage = ''; 9 | $scope.globalGoodMessage = ''; 10 | $scope.globalErrorMessage = ''; 11 | $scope.newItem = false; 12 | $scope.fromWindow = false; 13 | $scope.canViewSnippet = false; 14 | $scope.categories = []; 15 | $scope.busyWithAction = false; 16 | $scope.duplicates = []; 17 | 18 | $scope.bookmark = { 19 | favourite: false, 20 | title: $routeParams.title || '', 21 | link: $routeParams.link || '', 22 | snippet: '', 23 | category: '', 24 | tags: [] 25 | }; 26 | 27 | this.run = function () { 28 | 29 | bookmarkService 30 | .fetchCategories() 31 | .then(function (response) { 32 | if (response.result == 'ok') { 33 | $scope.categories = response.data.categories; 34 | } 35 | }); 36 | 37 | if ($scope.newItem) { 38 | bookmarkService 39 | .checkDuplicates($scope.bookmark) 40 | .then(function (response) { 41 | if (response.result == 'ok') { 42 | if (typeof response.data !== 'undefined') { 43 | $scope.duplicates = response.data.bookmarks; 44 | } 45 | } 46 | }); 47 | } else { 48 | bookmarkService 49 | .fetchBookmark($routeParams.bookmarkId || '') 50 | .then(function (response) { 51 | $scope.globalErrorMessage = ''; 52 | if (response.result != 'ok') { 53 | $scope.globalErrorMessage = response.message; 54 | $scope.viewReady = false; 55 | return; 56 | } 57 | 58 | $scope.bookmark = response.data.bookmark; 59 | var snippet = $scope.bookmark.snippet || ''; 60 | if (snippet !== '') { 61 | $scope.canViewSnippet = true; 62 | } 63 | 64 | }); 65 | 66 | } 67 | 68 | $scope.viewReady = true; 69 | 70 | }; 71 | 72 | $scope.checkStatus = function () { 73 | if ( 74 | typeof $routeParams.bookmarkId == 'undefined' || 75 | $routeParams.bookmarkId === '' 76 | ) { 77 | $scope.newItem = true; 78 | } 79 | 80 | // hide header if opened from window 81 | if ($routeParams.window == 1) { 82 | $scope.fromWindow = true; 83 | } 84 | }; 85 | 86 | // @todo find better solution to complete the add bookmark process 87 | $scope.closeWindow = function () { 88 | window.close(); 89 | //open(location, '_self').close(); 90 | }; 91 | 92 | $scope.cancelUpdate = function () { 93 | $scope.busyWithAction = true; 94 | $location.path('/'); 95 | }; 96 | 97 | $scope.enableSnippet = function () { 98 | $scope.canViewSnippet = true; 99 | }; 100 | 101 | $scope.deleteBookmark = function () { 102 | $scope.busyWithAction = true; 103 | $scope.viewReady = false; 104 | bookmarkService 105 | .deleteBookmark($routeParams.bookmarkId || '', $scope.bookmark) 106 | .then(function (response) { 107 | $scope.errorMessage = ''; 108 | if (response.result != 'ok') { 109 | $scope.errorMessage = response.message; 110 | $scope.viewReady = true; 111 | $scope.busyWithAction = false; 112 | return; 113 | } 114 | $scope.globalGoodMessage = $filter('translate')('message.bookmark.delete'); 115 | var interval = $interval(function () { 116 | $interval.cancel(interval); 117 | $location.path('/'); 118 | }, 1000); 119 | }); 120 | }; 121 | 122 | $scope.categorySelect = function (selected) { 123 | if (selected) { 124 | if (typeof selected.originalObject === 'object') { 125 | $scope.bookmark.category = selected.originalObject.name; 126 | } else { 127 | $scope.bookmark.category = selected.originalObject; 128 | } 129 | } else { 130 | $scope.bookmark.category = ''; 131 | } 132 | }; 133 | 134 | $scope.createBookmark = function () { 135 | $scope.busyWithAction = true; 136 | $scope.goodMessage = ''; 137 | $scope.errorMessage = ''; 138 | bookmarkService 139 | .createBookmark($scope.bookmark) 140 | .then(function (response) { 141 | $scope.errorMessage = ''; 142 | if (response.result != 'ok') { 143 | $scope.errorMessage = response.message; 144 | $scope.busyWithAction = false; 145 | return; 146 | } 147 | 148 | var interval; 149 | 150 | if ($scope.newItem && $scope.fromWindow == 1) { 151 | $scope.goodMessage = $filter('translate')('message.bookmark.create'); 152 | interval = $interval(function () { 153 | $interval.cancel(interval); 154 | window.close(); 155 | }, 1000); 156 | return; 157 | } 158 | 159 | $scope.goodMessage = 'Created'; 160 | interval = $interval(function () { 161 | $interval.cancel(interval); 162 | $location.path('/bookmark/edit/' + response.data.bookmark.id); 163 | }, 1000); 164 | }); 165 | }; 166 | 167 | $scope.updateBookmark = function (goBack) { 168 | $scope.busyWithAction = true; 169 | $scope.goodMessage = ''; 170 | $scope.errorMessage = ''; 171 | 172 | bookmarkService 173 | .updateBookmark($routeParams.bookmarkId || '', $scope.bookmark) 174 | .then(function (response) { 175 | $scope.errorMessage = ''; 176 | if (response.result != 'ok') { 177 | $scope.errorMessage = response.message; 178 | $scope.busyWithAction = false; 179 | return; 180 | } 181 | $scope.bookmark = response.data.bookmark; 182 | $scope.goodMessage = 'Updated'; 183 | $scope.busyWithAction = false; 184 | if (goBack) { 185 | $scope.busyWithAction = true; 186 | $location.path('/'); 187 | } 188 | }); 189 | }; 190 | 191 | $scope.checkStatus(); 192 | 193 | var that = this; 194 | 195 | userService 196 | .checkLoginStatus() 197 | .then(function (response) { 198 | $scope.errorMessage = ''; 199 | if (response.result != 'ok') { 200 | if ($scope.newItem && $scope.fromWindow) { 201 | $scope.globalErrorMessage = $filter('translate')('message.session'); 202 | return; 203 | } 204 | 205 | $scope.globalErrorMessage = response.message; 206 | $window.location = '/login'; 207 | 208 | return; 209 | } 210 | that.run(); 211 | }); 212 | 213 | }; 214 | 215 | angular.module('bookmarksApp') 216 | .controller('bookmarkEditController', 217 | ['$scope', '$location', '$routeParams', '$http', '$interval', 'userService', 'bookmarkService', 218 | '$window', '$filter', bookmarkEditController]); 219 | 220 | }()); 221 | -------------------------------------------------------------------------------- /resources/assets/js/bookmarkService.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | var bookmarkService = function ($location, $http) { 4 | 5 | var service = { 6 | 7 | fetchCategoriesAndTags: function () { 8 | return $http 9 | .get(API_URL + 'bookmarks/categories-and-tags') 10 | .then(function (response) { 11 | return response.data; 12 | }); 13 | }, 14 | 15 | fetchCategories: function () { 16 | return $http 17 | .get(API_URL + 'categories') 18 | .then(function (response) { 19 | return response.data; 20 | }); 21 | }, 22 | 23 | fetchTags: function () { 24 | return $http 25 | .get(API_URL + 'tags') 26 | .then(function (response) { 27 | return response.data; 28 | }); 29 | }, 30 | 31 | fetchFilteredBookmarks: function (categories, tags, search, page, limit) { 32 | return $http 33 | .get(API_URL + 34 | 'bookmarks?' + 35 | 'categories=' + categories + 36 | '&tags=' + tags + 37 | '&search=' + search + 38 | '&page=' + page + 39 | '&limit=' + limit) 40 | .then(function (response) { 41 | return response.data; 42 | }); 43 | }, 44 | 45 | fetchBookmark: function (bookmarkId) { 46 | return $http 47 | .get(API_URL + 'bookmark/' + bookmarkId) 48 | .then(function (response) { 49 | return response.data; 50 | }); 51 | }, 52 | 53 | createBookmark: function (data) { 54 | return $http 55 | .post(API_URL + 'bookmark/create?' + $.param(data)) 56 | .then(function (response) { 57 | return response.data; 58 | }); 59 | }, 60 | 61 | checkDuplicates: function (data) { 62 | return $http 63 | .get(API_URL + 'bookmarks/duplicates?' + $.param(data)) 64 | .then(function (response) { 65 | return response.data; 66 | }); 67 | }, 68 | 69 | updateBookmark: function (bookmarkId, data) { 70 | return $http 71 | .put(API_URL + 'bookmark/' + bookmarkId + '?' + $.param(data)) 72 | .then(function (response) { 73 | return response.data; 74 | }); 75 | }, 76 | 77 | deleteBookmark: function (bookmarkId, data) { 78 | return $http 79 | .delete(API_URL + 'bookmark/' + bookmarkId + '?' + $.param(data)) 80 | .then(function (response) { 81 | return response.data; 82 | }); 83 | }, 84 | 85 | importBookmarks: function (data) { 86 | return $http 87 | .post(API_URL + 'bookmarks/import?', data, { //$.param(user), data, { 88 | withCredentials: true, 89 | headers: {'Content-Type': undefined}, 90 | transformRequest: angular.identity 91 | }) 92 | .then(function (response) { 93 | return response.data; 94 | }); 95 | }, 96 | 97 | getExportBookmarksUrl: function () { 98 | return API_URL + 'bookmarks/export'; 99 | }, 100 | }; 101 | 102 | return service; 103 | }; 104 | 105 | angular.module('bookmarksApp') 106 | .service('bookmarkService', ['$location', '$http', bookmarkService]); 107 | 108 | }()); 109 | -------------------------------------------------------------------------------- /resources/assets/js/directives/customSelect2.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | var customSelect2 = function ($timeout, bookmarkService, $filter) { 4 | 5 | return { 6 | 7 | restrict: 'EA', 8 | 9 | scope: { 10 | controltags: '=tagsattribute', 11 | newitem: '=newitemattribute' 12 | }, 13 | 14 | replace: false, 15 | 16 | link: function($scope, el) { 17 | 18 | var loaded = 0; 19 | var data = []; 20 | 21 | var that = this; 22 | 23 | this.initSelect2 = function (el, data) { 24 | $('.loading-tags').hide(); 25 | 26 | $(el).select2({ 27 | placeholder: $filter('translate')('bookmark.tagsPlaceholder' ), 28 | allowClear: true, 29 | tags: true, 30 | data: data 31 | }); 32 | }; 33 | 34 | $scope.$watch("newitem", function (value) { 35 | 36 | if (value && data.length === 0) { 37 | 38 | bookmarkService 39 | .fetchTags() 40 | .then(function (response) { 41 | if (response.result == 'ok') { 42 | for (var i=0; i 1 && data.length === 0) { 69 | 70 | bookmarkService 71 | .fetchTags() 72 | .then(function (response) { 73 | var i; 74 | 75 | // Convert source data structure. 76 | for (i=0; i -1) { 18 | domain = url.split('/')[2]; 19 | } else { 20 | domain = url.split('/')[0]; 21 | } 22 | //find & remove port number 23 | domain = domain.split(':')[0]; 24 | return window.location.protocol + '//' + domain + '/'; 25 | } 26 | 27 | var bookmarkMeText = ''; 28 | bookmarkMeText += '('; 29 | bookmarkMeText += ' function() {'; 30 | bookmarkMeText += ' l="' + extractDomain(window.location.href) + '#/bookmark/add?title="+encodeURIComponent(document.title)+"&link="+encodeURIComponent(window.location.href);'; 31 | bookmarkMeText += ' var e=window.open(l+"&window=1","EasyBookmarkManager","location=0,links=0,scrollbars=0,toolbar=0,width=594,height=600");'; 32 | bookmarkMeText += ' }'; 33 | bookmarkMeText += ')()'; 34 | 35 | $('a.bookmark-me-link').attr('href', 'javascript:' + encodeURIComponent(bookmarkMeText)); 36 | } 37 | 38 | generateBookmarkMe(); 39 | }); 40 | -------------------------------------------------------------------------------- /resources/assets/js/routes.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | var routes = function ($routeProvider, $locationProvider) { 4 | $routeProvider. 5 | when('/', { 6 | templateUrl: '/views/bookmark-view.html', 7 | controller: 'bookmarkViewController' 8 | }). 9 | when('/bookmark/edit/:bookmarkId', { 10 | templateUrl: '/views/bookmark-edit.html', 11 | controller: 'bookmarkEditController' 12 | }). 13 | when('/bookmark/add', { 14 | templateUrl: '/views/bookmark-edit.html', 15 | controller: 'bookmarkEditController' 16 | }). 17 | when('/user/edit', { 18 | templateUrl: '/views/user-edit.html', 19 | controller: 'userEditController' 20 | }). 21 | otherwise({ 22 | redirectTo: '/login' 23 | }); 24 | 25 | // reverting back to no prefix in url (somehow defaulted to using ! e.g. /#!/bookmark/add) 26 | $locationProvider.hashPrefix(''); 27 | 28 | // use the HTML5 History API 29 | $locationProvider.html5Mode(false); 30 | }; 31 | 32 | angular.module('bookmarksApp') 33 | .config(['$routeProvider', '$locationProvider', routes]); 34 | 35 | }()); 36 | -------------------------------------------------------------------------------- /resources/assets/js/sessionService.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | var sessionService = function () { 4 | 5 | var service = { 6 | 7 | model: { 8 | theme: '', 9 | search: '', 10 | pageNo: 1 11 | }, 12 | 13 | setModel: function (data) { 14 | this.model = data; 15 | }, 16 | 17 | getModel: function () { 18 | return this.model; 19 | }, 20 | 21 | clearModel: function () { 22 | this.model = { 23 | theme: '', 24 | search: '', 25 | pageNo: 1 26 | }; 27 | } 28 | 29 | }; 30 | 31 | return service; 32 | 33 | }; 34 | 35 | angular.module('bookmarksApp') 36 | .service('sessionService', [sessionService]); 37 | 38 | }()); 39 | -------------------------------------------------------------------------------- /resources/assets/js/translation.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | angular.module('bookmarksApp') 4 | .config(['$translateProvider', function ($translateProvider) { 5 | 6 | var availableLanguages = [ "en", "fr" ]; 7 | 8 | // we used a native language detector approach as some browsers like Edge and IE 9 | // return en-EN instead of en on Firefox or Chrome 10 | 11 | var userLang = navigator.language || navigator.userLanguage; 12 | userLang = userLang.split('-')[0]; 13 | 14 | // if not in available languages, default to english. 15 | if (availableLanguages.indexOf(userLang) == -1) { 16 | userLang = 'en'; 17 | } 18 | 19 | $translateProvider.useUrlLoader('/lang/' + userLang + '.json'); 20 | $translateProvider.useSanitizeValueStrategy(null); 21 | $translateProvider.preferredLanguage(userLang); 22 | 23 | }]); 24 | 25 | }()); 26 | -------------------------------------------------------------------------------- /resources/assets/js/userEditController.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | var userEditController = function ($scope, $location, $http, $interval, userService, $filter) { 4 | 5 | $scope.errorMessage = ''; 6 | $scope.goodMessage = ''; 7 | $scope.busyWithAction = true; 8 | 9 | $scope.profile = { 10 | name: '', 11 | email: '', 12 | theme: '', 13 | password1: '', 14 | password2: '' 15 | }; 16 | 17 | var theme = localStorage.getItem('theme') || 'bootstrap-yeti'; 18 | $scope.profile.theme = theme; 19 | 20 | userService 21 | .getDetails() 22 | .then(function (response) { 23 | $scope.busyWithAction = false; 24 | if (response.result != 'ok') { 25 | $scope.errorMessage = response.message; 26 | return; 27 | } 28 | $scope.profile.name = response.data.user.name; 29 | $scope.profile.email = response.data.user.email; 30 | }); 31 | 32 | $scope.updateUser = function () { 33 | $scope.errorMessage = ''; 34 | $scope.goodMessage = ''; 35 | 36 | if ($scope.profile.password1.length > 0) { 37 | if ($scope.profile.password1.length < 5) { 38 | $scope.errorMessage = $filter('translate')('message.password.length'); 39 | return; 40 | } 41 | if ($scope.profile.password1.search(/[a-z]/i) < 0) { 42 | $scope.errorMessage = $filter('translate')('message.password.oneLetter'); 43 | return; 44 | } 45 | if ($scope.profile.password1.search(/[0-9]/) < 0) { 46 | $scope.errorMessage = $filter('translate')('message.password.oneDigit'); 47 | return; 48 | } 49 | if ($scope.profile.password1 != $scope.profile.password2) { 50 | $scope.errorMessage = $filter('translate')('message.password.match'); 51 | return; 52 | } 53 | } 54 | 55 | $scope.busyWithAction = true; 56 | 57 | userService 58 | .updateDetails($scope.profile) 59 | .then(function (response) { 60 | $scope.busyWithAction = false; 61 | if (response.result != 'ok') { 62 | $scope.errorMessage = response.message; 63 | return; 64 | } 65 | $scope.goodMessage = $filter('translate')('message.password.updated'); 66 | $scope.profile.password1 = ''; 67 | $scope.profile.password2 = ''; 68 | }); 69 | }; 70 | 71 | $scope.changeTheme = function (item) { 72 | localStorage.setItem('theme', item); 73 | $scope.profile.theme = item; 74 | $('.custom-css').remove(); 75 | loadTheme(); 76 | }; 77 | 78 | $scope.cancelUpdate = function () { 79 | $scope.busyWithAction = true; 80 | $location.path('/'); 81 | }; 82 | }; 83 | 84 | angular.module('bookmarksApp') 85 | .controller('userEditController', 86 | ['$scope', '$location', '$http', '$interval', 'userService', '$filter', userEditController]); 87 | 88 | }()); 89 | -------------------------------------------------------------------------------- /resources/assets/js/userService.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | var userService = function ($location, $http) { 4 | 5 | var service = { 6 | 7 | getDetails: function () { 8 | return $http 9 | .get(API_URL + 'user') 10 | .then(function (response) { 11 | return response.data; 12 | }); 13 | }, 14 | 15 | updateDetails: function (data) { 16 | return $http 17 | .put(API_URL + 'user?' + $.param(data)) 18 | .then(function (response) { 19 | return response.data; 20 | }); 21 | }, 22 | 23 | checkLoginStatus: function () { 24 | return $http 25 | .get(API_URL + 'user/status?') 26 | .then(function (response) { 27 | return response.data; 28 | }); 29 | } 30 | }; 31 | 32 | return service; 33 | 34 | }; 35 | 36 | angular.module('bookmarksApp') 37 | .service('userService', ['$location', '$http', userService]); 38 | 39 | }()); 40 | -------------------------------------------------------------------------------- /resources/assets/sass/main.scss: -------------------------------------------------------------------------------- 1 | @import "partials/bs-callout"; 2 | @import "partials/angucomplete"; 3 | @import "partials/tags"; 4 | @import "partials/tag-cloud"; 5 | @import "partials/code-pre-tags"; 6 | 7 | body { 8 | overflow-x:hidden; 9 | } 10 | 11 | .highlight { 12 | color: #ff0000; 13 | } 14 | 15 | .bookmark-me-link, .bookmark-me-link:hover, .bookmark-me-link:link, .bookmark-me-link:visited, .bookmark-me-link:active { 16 | text-decoration: none; 17 | } 18 | 19 | .li-item-selected { 20 | font-weight: bold; 21 | } 22 | 23 | .click-item { 24 | cursor: pointer; 25 | } 26 | 27 | .edit-bookmark { 28 | padding: 15px 20px; 29 | cursor: pointer; 30 | position: absolute; 31 | top: 0; 32 | right: 0; 33 | height: 100%; 34 | } 35 | 36 | .btn-file { 37 | position: relative; 38 | overflow: hidden; 39 | } 40 | 41 | .btn-file input[type=file] { 42 | position: absolute; 43 | top: 0; 44 | right: 0; 45 | min-width: 100%; 46 | min-height: 100%; 47 | font-size: 100px; 48 | text-align: right; 49 | filter: alpha(opacity=0); 50 | opacity: 0; 51 | outline: none; 52 | background: white; 53 | cursor: inherit; 54 | display: block; 55 | } 56 | 57 | .prev-next { 58 | padding-bottom: 8px; 59 | } 60 | 61 | .prev-next-pages { 62 | margin-right: 18px; 63 | margin-top: 7px; 64 | } 65 | 66 | .ace_editor { 67 | height : 200px; 68 | } 69 | 70 | #search-clear { 71 | position: absolute; 72 | right: 10px; 73 | top: 0; 74 | bottom: 0; 75 | height: 18px; 76 | margin: auto; 77 | font-size: 18px; 78 | cursor: pointer; 79 | color: #bbb; 80 | } 81 | 82 | .li-hidden { 83 | display:none; 84 | } 85 | 86 | .tag-item { 87 | font-weight: normal; 88 | } 89 | 90 | .ace-snippet-container { 91 | padding:0; 92 | 93 | &.list { 94 | padding: 10px 0; 95 | } 96 | } 97 | 98 | .logged-in-user { 99 | float: right; 100 | font-size: 0.37em; 101 | line-height: 1.8em; 102 | margin: 10px 0 0 20px; 103 | /* text-align: right; */ 104 | /* position: relative; */ 105 | /* top: 14px; */ 106 | /* right: 0px; */ 107 | /* width: 300px; */ 108 | /* display: block; */ 109 | /* overflow-x: hidden; */ 110 | /* margin: 0.5em 0 0 30px; */ 111 | } 112 | -------------------------------------------------------------------------------- /resources/assets/sass/partials/angucomplete.scss: -------------------------------------------------------------------------------- 1 | .angucomplete-holder { 2 | position: relative; 3 | } 4 | 5 | .angucomplete-dropdown { 6 | border-color: #ececec; 7 | border-width: 1px; 8 | border-style: solid; 9 | border-radius: 2px; 10 | width: 250px; 11 | padding: 6px; 12 | cursor: pointer; 13 | z-index: 9999; 14 | position: absolute; 15 | /*top: 32px; 16 | left: 0px; 17 | */ 18 | margin-top: -6px; 19 | background-color: #ffffff; 20 | } 21 | 22 | .angucomplete-searching { 23 | color: #acacac; 24 | font-size: 14px; 25 | } 26 | 27 | .angucomplete-description { 28 | font-size: 14px; 29 | } 30 | 31 | .angucomplete-row { 32 | padding: 5px; 33 | color: #000000; 34 | margin-bottom: 4px; 35 | clear: both; 36 | } 37 | 38 | .angucomplete-selected-row { 39 | background-color: lightblue; 40 | color: #ffffff; 41 | } 42 | 43 | .angucomplete-image-holder { 44 | padding-top: 2px; 45 | float: left; 46 | margin-right: 10px; 47 | margin-left: 5px; 48 | } 49 | 50 | .angucomplete-image { 51 | height: 34px; 52 | width: 34px; 53 | border-radius: 50%; 54 | border-color: #ececec; 55 | border-style: solid; 56 | border-width: 1px; 57 | } 58 | 59 | .angucomplete-image-default { 60 | /* Add your own default image here 61 | background-image: url('/assets/default.png'); 62 | */ 63 | background-position: center; 64 | background-size: contain; 65 | height: 34px; 66 | width: 34px; 67 | } 68 | -------------------------------------------------------------------------------- /resources/assets/sass/partials/bs-callout.scss: -------------------------------------------------------------------------------- 1 | .bs-callout { 2 | padding: 20px; 3 | margin: 20px 0; 4 | border: 1px solid #eee; 5 | border-left-width: 5px; 6 | border-radius: 3px; 7 | } 8 | 9 | .bs-callout h4 { 10 | margin-top: 0; 11 | margin-bottom: 5px; 12 | } 13 | 14 | .bs-callout p:last-child { 15 | margin-bottom: 0; 16 | } 17 | 18 | .bs-callout code { 19 | border-radius: 3px; 20 | } 21 | 22 | .bs-callout + .bs-callout { 23 | margin-top: -5px; 24 | } 25 | 26 | .bs-callout-default { 27 | border-left-color: #777; 28 | } 29 | 30 | .bs-callout-default h4 { 31 | color: #777; 32 | } 33 | 34 | .bs-callout-primary { 35 | border-left-color: #428bca; 36 | } 37 | 38 | .bs-callout-primary h4 { 39 | color: #428bca; 40 | } 41 | 42 | .bs-callout-success { 43 | border-left-color: #5cb85c; 44 | } 45 | 46 | .bs-callout-success h4 { 47 | color: #5cb85c; 48 | } 49 | 50 | .bs-callout-danger { 51 | border-left-color: #d9534f; 52 | } 53 | 54 | .bs-callout-danger h4 { 55 | color: #d9534f; 56 | } 57 | 58 | .bs-callout-warning { 59 | border-left-color: #f0ad4e; 60 | } 61 | 62 | .bs-callout-warning h4 { 63 | color: #f0ad4e; 64 | } 65 | 66 | .bs-callout-info { 67 | border-left-color: #5bc0de; 68 | } 69 | 70 | .bs-callout-info h4 { 71 | color: #5bc0de; 72 | } 73 | 74 | .custom-bs-callout { 75 | padding: 10px; 76 | margin: 10px 5px; 77 | position: relative; 78 | } 79 | 80 | .bs-callout h4 { 81 | margin-right: 44px; 82 | } 83 | -------------------------------------------------------------------------------- /resources/assets/sass/partials/code-pre-tags.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Code borrowed from @link https://perishablepress.com/perfect-pre-tags/ 3 | */ 4 | 5 | code, samp, kbd { 6 | font-family: "Courier New", Courier, monospace, sans-serif; 7 | text-align: left; 8 | color: #555; 9 | } 10 | pre code { 11 | line-height: 1.6em; 12 | font-size: 11px; 13 | } 14 | pre { 15 | padding: 0.1em 0.5em 0.3em 0.7em; 16 | border-left: 11px solid #ccc; 17 | margin: 1.7em 0 1.7em 0.3em; 18 | overflow: auto; 19 | width: 93%; 20 | } 21 | /* target IE7 and IE6 */ 22 | *:first-child+html pre { 23 | padding-bottom: 2em; 24 | overflow-y: hidden; 25 | overflow: visible; 26 | overflow-x: auto; 27 | } 28 | * html pre { 29 | padding-bottom: 2em; 30 | overflow: visible; 31 | overflow-x: auto; 32 | } 33 | -------------------------------------------------------------------------------- /resources/assets/sass/partials/tag-cloud.scss: -------------------------------------------------------------------------------- 1 | #tagcloud ul { 2 | margin: 1em 0; 3 | padding: .5em 10px; 4 | text-align: center; 5 | /*background-color:#71b5e9;*/ 6 | } 7 | 8 | #tagcloud li { 9 | margin: 0; 10 | padding: 0; 11 | list-style: none; 12 | display: inline; 13 | } 14 | 15 | #tagcloud li a { 16 | text-decoration: none; 17 | /*color:#fff;*/ 18 | padding: 0 2px; 19 | } 20 | 21 | #tagcloud li a:hover { 22 | color: #cff400; 23 | } 24 | 25 | .tag1 { 26 | font-size: 100%; 27 | } 28 | 29 | .tag2 { 30 | font-size: 120%; 31 | } 32 | 33 | .tag3 { 34 | font-size: 140%; 35 | } 36 | 37 | .tag4 { 38 | font-size: 160%; 39 | } 40 | 41 | .tag5 { 42 | font-size: 180%; 43 | } 44 | 45 | .tag6 { 46 | font-size: 200%; 47 | } 48 | 49 | .tag7 { 50 | font-size: 220%; 51 | } 52 | 53 | .tag8 { 54 | font-size: 240%; 55 | } 56 | 57 | .tag9 { 58 | font-size: 260%; 59 | } 60 | 61 | .tag10 { 62 | font-size: 280%; 63 | } 64 | -------------------------------------------------------------------------------- /resources/assets/sass/partials/tags.scss: -------------------------------------------------------------------------------- 1 | .tags { 2 | padding: 4px 0; 3 | } 4 | 5 | .tags .tag { 6 | border-radius: 10px; 7 | padding: 6px 4px 6px 8px; 8 | background-color: #EFEFEF; 9 | margin-right: 10px; 10 | line-height: 32px; 11 | } 12 | 13 | .tags .tag:hover { 14 | color: red; 15 | text-decoration: none; 16 | } 17 | 18 | .tags input { 19 | width: 80px; 20 | border: none; 21 | } 22 | 23 | .tag-item { 24 | margin-right: 1px; 25 | } 26 | -------------------------------------------------------------------------------- /resources/assets/views/bookmark-edit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | 8 | 9 | 12 | 13 |
14 | 15 |
16 | 19 | 20 |
21 |
22 | 25 |
26 |
27 |
28 | 29 |
30 | 33 | 34 |
35 | 37 |
38 |
39 | 40 |
41 | 44 | 45 |
46 | 47 |
48 |
49 | 50 |
51 | 52 | 53 |
54 |
55 |
57 |
58 |
59 | 60 |
61 | 64 |
65 |
66 | 67 |
68 | 71 | 72 |
73 |
87 |
88 |
89 | 90 |
91 | 94 | 95 |
96 | loading tags... 97 | 98 | 106 |
107 |
108 | 109 | 112 | 113 | 116 | 117 | 124 | 125 |
126 | 129 | 130 |
131 | 134 | 135 | 138 | 139 | 143 | 144 | 147 | 148 | 151 | 152 | 156 |
157 |
158 |
159 | 160 |
161 |
162 |
163 | -------------------------------------------------------------------------------- /resources/assets/views/bookmark-view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | 8 | 9 | 12 |
13 | 14 |
15 | 16 |
17 |
18 | {{ 'bookmark.categories' | translate }} 19 |
20 | 21 |
    22 |
  • 26 | {{category.name}} 27 | {{category.count}} 28 |
  • 29 | 30 |
  • 35 | load more... 36 |
  • 37 |
38 |
39 | 40 |
41 |
42 | {{'bookmark.tags' | translate }} 43 |
44 | 45 |
46 | 56 |
57 | 58 |
59 | 60 | 92 | 93 |
94 | 95 |
96 |
97 |
98 |
99 | 101 | 102 |
103 |
104 | 105 |
106 | 109 | 110 |
113 |
114 |

115 | 116 | 117 | 118 | 119 | {{ bookmark.title || '( No Title )' }} 120 |

121 | 122 | {{ bookmark.link | cut:true:80 }} 123 | 124 | 128 |
129 | 130 |
131 |
133 |
134 |
135 | 136 |
137 | {{ tag }} 138 |
139 |
140 | 141 |
142 | 146 | 147 | 151 | 152 |

{{ 154 | bookmarkResults.page 155 | }} / {{ bookmarkResults.maxPages }}

156 |
157 | 158 | 161 | 162 | 165 |
166 |
167 |
168 |
169 | -------------------------------------------------------------------------------- /resources/assets/views/hero.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 10 |
11 |
12 | -------------------------------------------------------------------------------- /resources/assets/views/tagmanager-directive.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | {{tag}} 4 | 5 | 6 | 10 |
11 | -------------------------------------------------------------------------------- /resources/assets/views/user-edit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 |
7 | 10 | 11 |
12 | 13 |
14 |
15 | 16 |
17 | 20 | 21 |
22 | 23 |
24 |
25 | 26 |
27 | 30 | 31 |
32 | 40 |
41 |
42 | 43 |
44 | 47 | 48 |
49 | 50 |
51 |
52 | 53 |
54 | 57 | 58 |
59 | 60 |
61 |
62 | 63 | 66 | 67 | 70 | 71 |
72 | 75 | 76 |
77 | 80 | 81 | 84 | 85 | 86 |
87 |
88 |
89 | 90 |
91 |
92 |
93 | -------------------------------------------------------------------------------- /resources/lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "update": "Update user", 4 | "logout": "Logout", 5 | "loggedin": "Logged in as {{user}}", 6 | "useradmin": "User admin" 7 | }, 8 | "export.bookmarks": "Export bookmarks", 9 | "import.bookmarks": "Import bookmarks", 10 | "new.bookmark": "New bookmark", 11 | "search.bookmark": "Refine search", 12 | "bookmarks.nodata": "no data found, try clicking on some filters", 13 | "bookmark": { 14 | "title": "Title", 15 | "link": "Link", 16 | "category": "Category", 17 | "categories": "Categories", 18 | "save": "Save", 19 | "create": "Create", 20 | "update": "Update", 21 | "saveandclose": "Save and close", 22 | "delete": "Delete", 23 | "close": "Close", 24 | "back": "Back", 25 | "enable": "Enable", 26 | "cancel": "Cancel", 27 | "search": "Search categories", 28 | "tagsPlaceholder": "Add tags", 29 | "favourite": "Favourite", 30 | "tag": "Tag", 31 | "tags": "Tags" 32 | }, 33 | "message": { 34 | "error": "Oh snap!", 35 | "success": "Success", 36 | "password": { 37 | "length": "Your new password must be at least 5 characters", 38 | "oneLetter": "Your new password must contain at least 1 letter", 39 | "oneDigit": "Your new password must contain at least 1 digit", 40 | "match": "Both passwords must match", 41 | "updated": "Details updated" 42 | }, 43 | "bookmark": { 44 | "delete": "Deleted, redirecting...", 45 | "create": "Created, closing...", 46 | "import": "Imported {{count}} record(s), reloading..." 47 | }, 48 | "session": "No session found, please login via the browser" 49 | }, 50 | "settings": { 51 | "name": "Name", 52 | "login": "Email/Login", 53 | "theme": "Theme", 54 | "password": "New password", 55 | "confirm": "Re-type new password" 56 | }, 57 | "auth": { 58 | "failed": "These credentials do not match our records.", 59 | "throttle": "Too many login attempts. Please try again in :seconds seconds." 60 | }, 61 | "messages": { 62 | "loginCombo": "That username\/password combo does not exist.", 63 | "userNameRequired": "The email field is required.", 64 | "usernameEmail": "The email must be a valid email address.", 65 | "usernameUnique": "The email has already been taken.", 66 | "user.add": "Add user", 67 | "user.edit": "Edit user", 68 | "edit": "Edit", 69 | "create": "Create", 70 | "delete": "Delete", 71 | "logout": "Logout", 72 | "loggedin": "logged in", 73 | "back": "Back", 74 | "save": "Save", 75 | "signin": "Sign in", 76 | "notConfirmed": "Your account has not been confirmed", 77 | "bookmark": { 78 | "notFound": "Could not retrieve the item." 79 | }, 80 | "upload": { 81 | "error": "There was a problem with uploading the file." 82 | }, 83 | "user": { 84 | "noSession": "No user sessions found.", 85 | "sessionReached": "Active session limit reached. Please logout to clean out session tokens.", 86 | "admin": "User Administration", 87 | "name": "Name", 88 | "mail": "Email \/ Login", 89 | "created": "Created", 90 | "adminRole": "Admin", 91 | "password": "Password", 92 | "confirm": "Confirm Password", 93 | "isAdmin": "Administrator", 94 | "register": "Register" 95 | }, 96 | "password": { 97 | "match": "Both passwords must match." 98 | }, 99 | "account": { 100 | "welcome": "Thanks to register on Easy Bookmark Manager.", 101 | "confirm": "You have to confirm your account clicking on this link", 102 | "confirmLink": "Account confirmation", 103 | "validated": "Your account has been validated !", 104 | "validationMessage": "Check your mail box to validate your account" 105 | }, 106 | "yes": "yes" 107 | }, 108 | "pagination": { 109 | "previous": "« Previous", 110 | "next": "Next »" 111 | }, 112 | "passwords": { 113 | "password": "Passwords must be at least six characters and match the confirmation.", 114 | "reset": "Your password has been reset!", 115 | "sent": "We have e-mailed your password reset link!", 116 | "token": "This password reset token is invalid.", 117 | "user": "We can't find a user with that e-mail address." 118 | }, 119 | "validation": { 120 | "accepted": "The :attribute must be accepted.", 121 | "active_url": "The :attribute is not a valid URL.", 122 | "after": "The :attribute must be a date after :date.", 123 | "alpha": "The :attribute may only contain letters.", 124 | "alpha_dash": "The :attribute may only contain letters, numbers, and dashes.", 125 | "alpha_num": "The :attribute may only contain letters and numbers.", 126 | "array": "The :attribute must be an array.", 127 | "before": "The :attribute must be a date before :date.", 128 | "between": { 129 | "numeric": "The :attribute must be between :min and :max.", 130 | "file": "The :attribute must be between :min and :max kilobytes.", 131 | "string": "The :attribute must be between :min and :max characters.", 132 | "array": "The :attribute must have between :min and :max items." 133 | }, 134 | "boolean": "The :attribute field must be true or false.", 135 | "confirmed": "The :attribute confirmation does not match.", 136 | "date": "The :attribute is not a valid date.", 137 | "date_format": "The :attribute does not match the format :format.", 138 | "different": "The :attribute and :other must be different.", 139 | "digits": "The :attribute must be :digits digits.", 140 | "digits_between": "The :attribute must be between :min and :max digits.", 141 | "email": "The :attribute must be a valid email address.", 142 | "exists": "The selected :attribute is invalid.", 143 | "filled": "The :attribute field is required.", 144 | "image": "The :attribute must be an image.", 145 | "in": "The selected :attribute is invalid.", 146 | "integer": "The :attribute must be an integer.", 147 | "ip": "The :attribute must be a valid IP address.", 148 | "json": "The :attribute must be a valid JSON string.", 149 | "max": { 150 | "numeric": "The :attribute may not be greater than :max.", 151 | "file": "The :attribute may not be greater than :max kilobytes.", 152 | "string": "The :attribute may not be greater than :max characters.", 153 | "array": "The :attribute may not have more than :max items." 154 | }, 155 | "mimes": "The :attribute must be a file of type: :values.", 156 | "min": { 157 | "numeric": "The :attribute must be at least :min.", 158 | "file": "The :attribute must be at least :min kilobytes.", 159 | "string": "The :attribute must be at least :min characters.", 160 | "array": "The :attribute must have at least :min items." 161 | }, 162 | "not_in": "The selected :attribute is invalid.", 163 | "numeric": "The :attribute must be a number.", 164 | "regex": "The :attribute format is invalid.", 165 | "required": "The :attribute field is required.", 166 | "required_if": "The :attribute field is required when :other is :value.", 167 | "required_unless": "The :attribute field is required unless :other is in :values.", 168 | "required_with": "The :attribute field is required when :values is present.", 169 | "required_with_all": "The :attribute field is required when :values is present.", 170 | "required_without": "The :attribute field is required when :values is not present.", 171 | "required_without_all": "The :attribute field is required when none of :values are present.", 172 | "same": "The :attribute and :other must match.", 173 | "size": { 174 | "numeric": "The :attribute must be :size.", 175 | "file": "The :attribute must be :size kilobytes.", 176 | "string": "The :attribute must be :size characters.", 177 | "array": "The :attribute must contain :size items." 178 | }, 179 | "string": "The :attribute must be a string.", 180 | "timezone": "The :attribute must be a valid zone.", 181 | "unique": "The :attribute has already been taken.", 182 | "url": "The :attribute format is invalid.", 183 | "custom": { 184 | "attribute-name": { 185 | "rule-name": "custom-message" 186 | } 187 | }, 188 | "attributes": {} 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /resources/lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Easy Bookmark Manager 9 | 10 | 15 | 16 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 53 | 54 | 55 | 56 | 57 | @yield('content') 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /resources/views/auth/login.blade.php: -------------------------------------------------------------------------------- 1 | @extends('app') 2 | 3 | @section('content') 4 | 5 |
6 | 7 | @include('partials/hero') 8 | 9 |
10 |
11 | 12 |
13 | 14 | @if (!empty($message)) 15 |
16 | {{$message}} 17 |
18 | @endif 19 | 20 | {{ Form::open(['role' => 'form', 'url' => '/auth/login']) }} 21 | 22 | {!! csrf_field() !!} 23 | 24 |
25 | {{ Form::text('username', null, ['placeholder' => 'Email', 'class' => 'form-control']) }} 26 |
27 | 28 |
29 | {{ Form::password('password', ['placeholder' => trans('messages.user.password'), 'class' => 'form-control']) }} 30 |
31 | 32 |
33 | @include('partials/errors') 34 |
35 | 36 | {{ Form::submit(trans('messages.signin'), ['class' => 'btn btn-primary btn-small btn-block']) }} 37 | 38 | @if (env('ENABLE_REGISTER')) 39 |
40 | or register 41 |
42 | @endif 43 | 44 | {{ Form::close() }} 45 | 46 |
47 |
48 |
49 | 50 | @stop 51 | 52 | 53 | -------------------------------------------------------------------------------- /resources/views/auth/register.blade.php: -------------------------------------------------------------------------------- 1 | @extends('app') 2 | 3 | @section('content') 4 | 5 |
6 | 7 | @include('partials/hero') 8 | 9 |
10 |
11 | 12 |
13 | 14 |

15 | {{ trans('messages.user.register') }} 16 | 17 | {{ trans('messages.back') }} 18 | 19 |

20 | 21 | {{ Form::open(['role' => 'form', 'url' => 'auth/register', 'name' => 'register']) }} 22 | 23 | @include('useradmin/registerform') 24 | 25 | @include('partials/errors') 26 | 27 |
28 | {{ Form::submit(trans('messages.user.register'), ['class' => 'btn btn-success']) }} 29 |
30 | 31 | {{ Form::close() }} 32 | 33 |
34 |
35 |
36 | @endsection 37 | -------------------------------------------------------------------------------- /resources/views/emails/register.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Easy Bookmark Manager 8 | 9 | 10 | 11 | @include('partials.hero') 12 | 13 |
14 | {{ trans('messages.account.welcome') }} 15 |
16 | {{ trans('messages.account.confirm') }} 17 | 18 | 19 | {{ trans('messages.account.confirmLink') }} 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /resources/views/errors/503.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Be right back. 5 | 6 | 7 | 8 | 39 | 40 | 41 |
42 |
43 |
Be right back.
44 |
45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /resources/views/pages/home.blade.php: -------------------------------------------------------------------------------- 1 | @extends('app') 2 | 3 | @section('content') 4 | 5 |
6 | 7 | @stop 8 | -------------------------------------------------------------------------------- /resources/views/partials/errors.blade.php: -------------------------------------------------------------------------------- 1 | @if (isset($errors) && $errors->has()) 2 |
3 |
    4 | @foreach ($errors->all() as $error) 5 |
  • {{ $error }}
  • 6 | @endforeach 7 |
8 |
9 | @endif 10 | -------------------------------------------------------------------------------- /resources/views/partials/hero.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 10 |
11 |
12 | -------------------------------------------------------------------------------- /resources/views/useradmin/create.blade.php: -------------------------------------------------------------------------------- 1 | @extends('app') 2 | 3 | @section('content') 4 | 5 |
6 | 7 |

8 | {{ trans('messages.user.add') }} 9 | 10 | {{ trans('messages.back') }} 11 | 12 |

13 | 14 | {{ Form::open(['role' => 'form', 'url' => '/admin/user/store']) }} 15 | 16 | @include('useradmin/userform') 17 | 18 | @include('partials/errors') 19 | 20 |
21 | {{ Form::submit(trans('messages.create'), ['class' => 'btn btn-success']) }} 22 |
23 | 24 | {{ Form::close() }} 25 | 26 |
27 | 28 | @stop 29 | 30 | 31 | -------------------------------------------------------------------------------- /resources/views/useradmin/edit.blade.php: -------------------------------------------------------------------------------- 1 | @extends('app') 2 | 3 | @section('content') 4 | 5 |
6 | 7 |

8 | {{ trans('messages.user.edit') }} 9 | 10 | {{ trans("messages.back") }} 11 | 12 |

13 | 14 | {{ Form::model($user, ['role' => 'form', 'url' => '/admin/user/' . $user->id, 'method' => 'PUT']) }} 15 | 16 | @include('useradmin/userform') 17 | 18 | @include('partials/errors') 19 | 20 |
21 | {{ Form::submit(trans('messages.save'), ['class' => 'btn btn-success']) }} 22 |
23 | 24 | {{ Form::close() }} 25 | 26 |
27 | 28 | @stop 29 | 30 | 31 | -------------------------------------------------------------------------------- /resources/views/useradmin/index.blade.php: -------------------------------------------------------------------------------- 1 | @extends('app') 2 | 3 | @section('content') 4 | 5 |
6 |

7 | {{ trans('messages.user.admin') }} 8 | 9 | 20 |

21 | 22 |
23 | 24 | 25 | 26 | 29 | 32 | 35 | 38 | 39 | 40 | 41 | 42 | @foreach ($users as $user) 43 | 44 | 45 | 46 | 47 | 48 | 56 | 57 | @endforeach 58 | 59 |
27 | {{ trans('messages.user.name') }} 28 | 30 | {{ trans('messages.user.mail') }} 31 | 33 | {{ trans('messages.user.created') }} 34 | 36 | {{ trans('messages.user.adminRole') }} 37 |
{{ $user->name }}{{ $user->username }}{{ $user->created_at->format('F d, Y h:ia') }}{{ $user->administrator ? trans('messages.yes') : '' }} 49 | 50 | {{ trans('messages.edit') }} 51 | 52 | {{ Form::open(['url' => '/admin/user/' . $user->id, 'method' => 'DELETE']) }} 53 | {{ Form::submit(trans('messages.delete'), ['class' => 'btn btn-danger btn-xs'])}} 54 | {{ Form::close() }} 55 |
60 |
61 | 62 | 63 | {{ trans('messages.user.add') }} 64 | 65 |
66 | 67 | @stop 68 | 69 | 70 | -------------------------------------------------------------------------------- /resources/views/useradmin/registerform.blade.php: -------------------------------------------------------------------------------- 1 |
2 | {{ Form::label('name', trans('messages.user.name')) }} 3 | {{ Form::text('name', null, ['placeholder' => trans('messages.user.name'), 'class' => 'form-control']) }} 4 |
5 | 6 |
7 | {{ Form::label('username', 'Email') }} 8 | {{ Form::text('username', null, ['placeholder' => 'Email', 'class' => 'form-control']) }} 9 |
10 | 11 |
12 | {{ Form::label('password', trans('messages.user.password')) }} 13 | {{ Form::password('password', ['placeholder' => trans('messages.user.password'), 'class' => 'form-control']) }} 14 |
15 | 16 |
17 | {{ Form::label('password_confirmation', trans('messages.user.confirm')) }} 18 | {{ Form::password('password_confirmation', ['placeholder' => trans('messages.user.confirm'), 'class' => 'form-control']) }} 19 |
20 | -------------------------------------------------------------------------------- /resources/views/useradmin/userform.blade.php: -------------------------------------------------------------------------------- 1 |
2 | {{ Form::label('name', trans('messages.user.name')) }} 3 | {{ Form::text('name', null, ['placeholder' => trans('messages.user.name'), 'class' => 'form-control']) }} 4 |
5 | 6 |
7 | {{ Form::label('username', 'Email') }} 8 | {{ Form::text('username', null, ['placeholder' => 'Email', 'class' => 'form-control']) }} 9 |
10 | 11 |
12 | {{ Form::label('password', trans('messages.user.password')) }} 13 | {{ Form::password('password', ['placeholder' => trans('messages.user.password'), 'class' => 'form-control']) }} 14 |
15 | 16 |
17 | {{ Form::label('password_confirmation', trans('messages.user.confirm')) }} 18 | {{ Form::password('password_confirmation', ['placeholder' => trans('messages.user.confirm'), 'class' => 'form-control']) }} 19 |
20 | 21 |
22 | {{ Form::label('administrator', trans('messages.user.isAdmin')) }} 23 | {{ Form::checkbox('administrator', 1, null, ['class' => 'form-inline']) }} 24 |
25 | -------------------------------------------------------------------------------- /resources/views/vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devimust/easy-bookmark-manager/e03f619d1da89d32e5f06a33ac335c50710e9823/resources/views/vendor/.gitkeep -------------------------------------------------------------------------------- /server.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | $uri = urldecode( 11 | parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) 12 | ); 13 | 14 | // This file allows us to emulate Apache's "mod_rewrite" functionality from the 15 | // built-in PHP web server. This provides a convenient way to test a Laravel 16 | // application without having installed a "real" web server software here. 17 | if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) { 18 | return false; 19 | } 20 | 21 | require_once __DIR__.'/public/index.php'; 22 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | config.php 2 | routes.php 3 | compiled.php 4 | services.json 5 | events.scanned.php 6 | routes.scanned.php 7 | down 8 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tests/AppTitleTest.php: -------------------------------------------------------------------------------- 1 | visit('/') 17 | ->see('Easy Bookmark Manager'); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | loadEnvironmentFrom('.env.testing'); 22 | 23 | $app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap(); 24 | 25 | return $app; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/UserRegistrationTest.php: -------------------------------------------------------------------------------- 1 | $name, 18 | 'username' => $username, 19 | 'password' => $password, 20 | 'password_confirmation' => $confirmation, 21 | '_token' => csrf_token() 22 | ]; 23 | 24 | $response = $this->call('POST', 'auth/register', $payload); 25 | 26 | return $response; 27 | } 28 | 29 | /** 30 | * Run migration tasks 31 | * 32 | * @return void 33 | */ 34 | public function setUp() 35 | { 36 | parent::setUp(); 37 | 38 | Artisan::call('migrate'); 39 | // Artisan::call('db:seed'); 40 | } 41 | 42 | /** 43 | * Test is user is created after register route 44 | * We disable send mail middleware 45 | * 46 | * @return void 47 | */ 48 | public function testUserRegistration() 49 | { 50 | $beforeRegisterCount = count(User::all()); 51 | $this->assertEquals(0, $beforeRegisterCount); 52 | 53 | $response = $this->registerAUser(); 54 | $this->assertEquals(200, $response->getStatusCode()); 55 | 56 | $afterRegisterCount = count(User::all()); 57 | $this->assertEquals(1, $afterRegisterCount); 58 | } 59 | 60 | /** 61 | * Try to create a user with an already token username 62 | */ 63 | public function testUserRegistrationFailed() 64 | { 65 | 66 | $beforeRegisterCount = count(User::all()); 67 | $this->assertEquals(0, $beforeRegisterCount); 68 | 69 | $response = $this->registerAUser(); 70 | $this->assertEquals(200, $response->getStatusCode()); 71 | 72 | $response = $this->registerAUser(); 73 | $this->assertEquals(302, $response->getStatusCode()); 74 | 75 | $afterRegisterCount = count(User::all()); 76 | $this->assertEquals(1, $afterRegisterCount); 77 | } 78 | } 79 | --------------------------------------------------------------------------------