├── .env.example ├── .gitattributes ├── .gitignore ├── LICENSE.md ├── app ├── Archive │ ├── Log.php │ └── LogRepository.php ├── Breadcrumbs │ ├── BreadcrumbInterface.php │ └── CreitiveBreadcrumb.php ├── Console │ ├── Commands │ │ ├── Fetch.php │ │ ├── Import.php │ │ └── Setup.php │ └── Kernel.php ├── Exceptions │ └── Handler.php ├── Fetcher │ └── FetchLog.php ├── Http │ ├── Controllers │ │ ├── AdminController.php │ │ ├── Auth │ │ │ ├── ForgotPasswordController.php │ │ │ ├── LoginController.php │ │ │ ├── RegisterController.php │ │ │ └── ResetPasswordController.php │ │ ├── Controller.php │ │ ├── HomeController.php │ │ ├── LogController.php │ │ └── TweetController.php │ ├── Kernel.php │ └── Middleware │ │ ├── EncryptCookies.php │ │ ├── PrivateMiddleware.php │ │ ├── RedirectIfAuthenticated.php │ │ └── VerifyCsrfToken.php ├── Providers │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ ├── BroadcastServiceProvider.php │ ├── EventServiceProvider.php │ └── RouteServiceProvider.php ├── Tweets │ ├── Api.php │ ├── Formatter.php │ ├── Tweet.php │ ├── TweetPresenter.php │ ├── TweetQuery.php │ ├── TweetRepository.php │ └── TweetType.php ├── User.php └── helpers.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 ├── ttwitter.php └── view.php ├── database ├── .gitignore ├── factories │ └── ModelFactory.php ├── migrations │ ├── 2014_10_12_000000_create_users_table.php │ ├── 2014_10_12_100000_create_password_resets_table.php │ ├── 2016_12_17_092250_CreateTweetsTable.php │ ├── 2016_12_17_094706_CreateArchiveLogTable.php │ └── 2016_12_18_090425_CreateFetchLogTable.php └── seeds │ └── DatabaseSeeder.php ├── gulpfile.js ├── package.json ├── phpunit.xml ├── public ├── .htaccess ├── assets │ ├── css │ │ ├── bootstrap.min.css │ │ └── style.css │ └── js │ │ ├── archivey.js │ │ ├── bootstrap.min.js │ │ └── jquery.min.js ├── favicon.ico ├── index.php ├── robots.txt └── web.config ├── readme.md ├── resources ├── assets │ ├── js │ │ ├── app.js │ │ ├── bootstrap.js │ │ └── components │ │ │ └── Example.vue │ └── sass │ │ ├── _variables.scss │ │ └── app.scss ├── lang │ └── en │ │ ├── auth.php │ │ ├── pagination.php │ │ ├── passwords.php │ │ └── validation.php └── views │ ├── admin │ ├── logs.blade.php │ └── stats.blade.php │ ├── auth │ ├── login.blade.php │ ├── passwords │ │ ├── email.blade.php │ │ └── reset.blade.php │ └── register.blade.php │ ├── errors │ ├── 404.blade.php │ └── 503.blade.php │ ├── layouts │ └── main.blade.php │ ├── partials │ ├── _errors.blade.php │ ├── _flash.blade.php │ └── _nav.blade.php │ ├── tweets │ ├── index.blade.php │ └── partials │ │ ├── _counts.blade.php │ │ └── _tweets.blade.php │ └── vendor │ ├── .gitkeep │ ├── notifications │ ├── email-plain.blade.php │ └── email.blade.php │ └── pagination │ ├── bootstrap-4.blade.php │ ├── default.blade.php │ ├── simple-bootstrap-4.blade.php │ └── simple-default.blade.php ├── routes ├── api.php ├── console.php └── web.php ├── server.php ├── storage ├── app │ ├── .gitignore │ └── public │ │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore ├── tests ├── ExampleTest.php └── TestCase.php └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | APP_ENV=production 2 | APP_DEBUG=false 3 | 4 | DB_CONNECTION=mysql 5 | DB_HOST=127.0.0.1 6 | DB_PORT=3306 7 | DB_DATABASE=one40 8 | DB_USERNAME= 9 | DB_PASSWORD= 10 | 11 | PRIVATE=false 12 | TWITTER_USERNAME= 13 | TWITTER_CONSUMER_KEY= 14 | TWITTER_CONSUMER_SECRET= 15 | TWITTER_ACCESS_TOKEN= 16 | TWITTER_ACCESS_TOKEN_SECRET= 17 | 18 | SESSION_DRIVER=cookie -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.scss linguist-vendored 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /public/storage 3 | /storage/*.key 4 | /resources/archive/* 5 | /vendor 6 | /.idea 7 | Homestead.json 8 | Homestead.yaml 9 | .env 10 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Robb Lewis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/Archive/Log.php: -------------------------------------------------------------------------------- 1 | $filename]); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /app/Breadcrumbs/BreadcrumbInterface.php: -------------------------------------------------------------------------------- 1 | formatter = $formatter; 50 | $this->tweets = $tweets; 51 | $this->api = $api; 52 | } 53 | 54 | /** 55 | * Execute the console command. 56 | * 57 | * @return mixed 58 | */ 59 | public function handle() 60 | { 61 | $this->info('Fetching new tweets...'); 62 | 63 | $this->importTweets(); 64 | } 65 | 66 | private function importTweets() 67 | { 68 | $tweets = []; 69 | $sinceId = $this->tweets->getLatestId(); 70 | $maxId = 0; 71 | 72 | if (! $user = User::first()) { 73 | $this->error('No user found, run one40:setup'); 74 | return; 75 | } 76 | 77 | $screename = env('TWITTER_USERNAME'); 78 | 79 | $page = 1; 80 | 81 | do { 82 | try { 83 | $data = $this->api->getUserTimeline($screename, $sinceId, $maxId); 84 | } catch (\Exception $e) { 85 | $this->error($e->getMessage()); 86 | break; 87 | } 88 | 89 | if (empty($data)) continue; 90 | 91 | foreach ($data as $i => $tweet) { 92 | 93 | if (is_array($tweet) && is_object($tweet[0]) && property_exists($tweet[0], 'message')) { 94 | $this->error('Error: ' . $tweet[0]->message); 95 | } 96 | 97 | $tweets[] = $this->formatter->transformTweet($tweet); 98 | 99 | $maxId = $tweet->id_str; 100 | 101 | // Subtracting 1 from max_id to prevent duplicate, but only if we support 64-bit integer handling 102 | if ((int) "9223372036854775807" > 2147483647) $maxId = (int) $tweet->id - 1; 103 | } 104 | $page++; 105 | } while (! empty($data)); 106 | 107 | $tweetCount = count($tweets); 108 | 109 | if (! $tweetCount) { 110 | $this->info('No new tweets found'); 111 | $this->log($tweetCount); 112 | return; 113 | } 114 | 115 | $this->info($tweetCount . ' new tweets found'); 116 | 117 | // Ascending sort, oldest first 118 | $tweets = array_reverse($tweets); 119 | 120 | $this->tweets->addTweets($tweets); 121 | 122 | $this->log($tweetCount); 123 | } 124 | 125 | /** 126 | * @param $tweetCount 127 | */ 128 | private function log($tweetCount) 129 | { 130 | FetchLog::create(['count' => $tweetCount]); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /app/Console/Commands/Import.php: -------------------------------------------------------------------------------- 1 | logRepo = $logRepo; 42 | $this->formatter = $formatter; 43 | $this->tweets = $tweets; 44 | } 45 | 46 | /** 47 | * Execute the console command. 48 | * 49 | * @return mixed 50 | */ 51 | public function handle() 52 | { 53 | $this->info('Running importer...'); 54 | 55 | $archiveLog = $this->logRepo->all()->pluck('filename')->toArray(); 56 | 57 | $files = glob(base_path() . '/resources/archive/[0-9][0-9][0-9][0-9]_[0-1][0-9].js'); 58 | 59 | if (! count($files)) 60 | { 61 | $this->info('No archive files found. Aborting...'); 62 | return; 63 | } 64 | 65 | $tweets = []; 66 | 67 | foreach($files as $filename ) { 68 | 69 | if (in_array(basename($filename), $archiveLog)) { 70 | $this->info(basename($filename) . ' already imported, skipping...'); 71 | continue; 72 | } 73 | 74 | $this->info('Found archive file ' . basename($filename)); 75 | 76 | $fileLines = file($filename); 77 | array_shift($fileLines); // remove first line 78 | $data = json_decode(implode( '', $fileLines)); 79 | 80 | if (! is_array($data)) { 81 | $this->info('Error: Could not parse JSON for ' . basename($filename) . '. Aborting...'); 82 | continue; 83 | } 84 | 85 | $this->warn(count($data) . ' tweets found'); 86 | 87 | if (! empty($data)) { 88 | foreach($data as $i => $tweet) { 89 | // Create tweet element and add to list 90 | $tweets[] = $this->formatter->transformTweet($this->normalizeTweet($tweet)); 91 | } 92 | // Ascending sort, oldest first 93 | $tweets = array_reverse($tweets); 94 | 95 | $this->tweets->addTweets($tweets); 96 | } 97 | 98 | $tweets = []; 99 | $this->logRepo->markImported(basename($filename)); 100 | } 101 | } 102 | 103 | private function normalizeTweet($tweet) { 104 | foreach ($tweet as $k => $v) { 105 | // replace empty objects with null 106 | if (is_object($v) && count( get_object_vars($v)) === 0) { 107 | $tweet->$k = null; 108 | } 109 | } 110 | foreach(['geo', 'coordinates', 'place', 'contributors'] as $property ) { 111 | if(! property_exists( $tweet, $property ) ) { 112 | $tweet->$property = null; 113 | } 114 | } 115 | return $tweet; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /app/Console/Commands/Setup.php: -------------------------------------------------------------------------------- 1 | warn('User account already created, aborting.'); 47 | return; 48 | } 49 | 50 | if (! $user = $this->createUser()) { 51 | $this->info('Error: User creation failed. Please try again'); 52 | return; 53 | } 54 | 55 | $this->info('Setup successfully completed!'); 56 | } 57 | 58 | private function createUser() 59 | { 60 | $this->info('Creating user account'); 61 | 62 | $email = $this->ask('Email Address'); 63 | 64 | try 65 | { 66 | Validator::make(['email' => $email], ['email' => 'email'])->validate(); 67 | } catch (ValidationException $e) { 68 | $this->info('Error: Invalid email. aborting.'); 69 | return false; 70 | } 71 | 72 | $password = $this->secret('Password'); 73 | $confirmPassword = $this->secret('Confirm Password'); 74 | 75 | if ($password != $confirmPassword) 76 | { 77 | $this->info('Error: Passwords do not match. aborting.'); 78 | return false; 79 | } 80 | 81 | return User::create([ 82 | 'email' => $email, 83 | 'password' => $password 84 | ]); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('inspire') 30 | // ->hourly(); 31 | } 32 | 33 | /** 34 | * Register the Closure based commands for the application. 35 | * 36 | * @return void 37 | */ 38 | protected function commands() 39 | { 40 | require base_path('routes/console.php'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | expectsJson()) { 60 | return response()->json(['error' => 'Unauthenticated.'], 401); 61 | } 62 | 63 | return redirect()->guest('login'); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/Fetcher/FetchLog.php: -------------------------------------------------------------------------------- 1 | tweets = $tweets; 17 | } 18 | 19 | public function stats() 20 | { 21 | list($totals, $clients, $average) = $this->tweets->stats(); 22 | 23 | return view('admin.stats', compact( 24 | 'totals', 'clients', 'average' 25 | )); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/ForgotPasswordController.php: -------------------------------------------------------------------------------- 1 | middleware('guest'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/LoginController.php: -------------------------------------------------------------------------------- 1 | middleware('guest', ['except' => 'logout']); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/RegisterController.php: -------------------------------------------------------------------------------- 1 | send(); 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|max:255', 53 | 'email' => 'required|email|max:255|unique:users', 54 | 'password' => 'required|min:6|confirmed', 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/ResetPasswordController.php: -------------------------------------------------------------------------------- 1 | middleware('guest'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | middleware('auth'); 17 | } 18 | 19 | /** 20 | * Show the application dashboard. 21 | * 22 | * @return \Illuminate\Http\Response 23 | */ 24 | public function index() 25 | { 26 | return view('home'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Http/Controllers/LogController.php: -------------------------------------------------------------------------------- 1 | paginate(30); 12 | 13 | return view('admin.logs', compact( 14 | 'logs' 15 | )); 16 | } 17 | 18 | public function purge() 19 | { 20 | $logs = FetchLog::all(); 21 | 22 | $ids = $logs->pluck('id'); 23 | 24 | FetchLog::destroy($ids->toArray()); 25 | 26 | return redirect('logs'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Http/Controllers/TweetController.php: -------------------------------------------------------------------------------- 1 | tweets = $tweets; 20 | $this->breadcrumbs = $breadcrumbs; 21 | 22 | $this->breadcrumbs->setCssClasses('breadcrumb'); 23 | $this->breadcrumbs->setDivider(''); 24 | $this->breadcrumbs->setListElement('ol'); 25 | $this->breadcrumbs->addCrumb('All Tweets', '/'); 26 | } 27 | 28 | public function index($year = null, $month = null, $date = null) 29 | { 30 | $query = new TweetQuery(); 31 | $monthCounts = null; 32 | $dayCounts = null; 33 | 34 | if ($year) { 35 | $query->forYear($year); 36 | $this->breadcrumbs->addCrumb($year, $year); 37 | } 38 | if ($month) { 39 | $query->forMonth($month); 40 | $this->breadcrumbs->addCrumb(displayMonth($month), $month); 41 | } 42 | if ($date) { 43 | $query->forDate($date); 44 | $this->breadcrumbs->addCrumb(displayDate($date), $date); 45 | } 46 | 47 | list($ids, $tweets) = $this->tweets->all($query); 48 | 49 | if (! $month) $monthCounts = $this->tweets->monthCount($ids); 50 | if ($month && ! $date) $dayCounts = $this->tweets->dayCount($ids); 51 | 52 | return view('tweets.index', compact( 53 | 'tweets', 54 | 'monthCounts', 55 | 'dayCounts' 56 | )); 57 | } 58 | 59 | public function searchResults($search, $year = null, $month = null, $date = null) 60 | { 61 | $monthCounts = null; 62 | $dayCounts = null; 63 | $query = new TweetQuery(); 64 | $query->search($search); 65 | 66 | if ($year) $query->forYear($year); 67 | if ($month) $query->forMonth($month); 68 | if ($date) $query->forDate($date); 69 | 70 | list($ids, $tweets) = $this->tweets->all($query); 71 | 72 | if (! $month) $monthCounts = $this->tweets->monthCount($ids); 73 | if ($month && ! $date) $dayCounts = $this->tweets->dayCount($ids); 74 | 75 | $this->breadcrumbs->addCrumb(count($ids) . ' found containing "' . $search . '"', "search/$search"); 76 | if ($year) $this->breadcrumbs->addCrumb($year, $year); 77 | if ($month) $this->breadcrumbs->addCrumb(displayMonth($month), $month); 78 | if ($date) $this->breadcrumbs->addCrumb(displayDate($date), $date); 79 | 80 | return view('tweets.index', compact( 81 | 'tweets', 82 | 'search', 83 | 'monthCounts', 84 | 'dayCounts' 85 | )); 86 | } 87 | 88 | public function show($tweetId) 89 | { 90 | $this->breadcrumbs->addCrumb('Tweet ID: ' . $tweetId, $tweetId); 91 | 92 | $tweets = $this->tweets->findById($tweetId); 93 | $single = true; 94 | 95 | return view('tweets.index', compact( 96 | 'tweets', 97 | 'single' 98 | )); 99 | } 100 | 101 | public function random() 102 | { 103 | $tweet = $this->tweets->getRandomTweet(); 104 | 105 | return $this->show($tweet->tweetid); 106 | } 107 | 108 | public function search() 109 | { 110 | $search = Input::get('search'); 111 | 112 | return redirect('search/'.$search); 113 | } 114 | 115 | } -------------------------------------------------------------------------------- /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 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 34 | ], 35 | 36 | 'api' => [ 37 | 'throttle:60,1', 38 | 'bindings', 39 | ], 40 | ]; 41 | 42 | /** 43 | * The application's route middleware. 44 | * 45 | * These middleware may be assigned to groups or used individually. 46 | * 47 | * @var array 48 | */ 49 | protected $routeMiddleware = [ 50 | 'auth' => \Illuminate\Auth\Middleware\Authenticate::class, 51 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 52 | 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 53 | 'can' => \Illuminate\Auth\Middleware\Authorize::class, 54 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 55 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 56 | 'private' => PrivateMiddleware::class 57 | ]; 58 | } 59 | -------------------------------------------------------------------------------- /app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | user(); 19 | 20 | if (env('PRIVATE') && ! $user) return redirect('login'); 21 | 22 | return $next($request); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Http/Middleware/RedirectIfAuthenticated.php: -------------------------------------------------------------------------------- 1 | check()) { 21 | return redirect('/home'); 22 | } 23 | 24 | return $next($request); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Http/Middleware/VerifyCsrfToken.php: -------------------------------------------------------------------------------- 1 | 'App\Policies\ModelPolicy', 17 | ]; 18 | 19 | /** 20 | * Register any authentication / authorization services. 21 | * 22 | * @return void 23 | */ 24 | public function boot() 25 | { 26 | $this->registerPolicies(); 27 | 28 | // 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Providers/BroadcastServiceProvider.php: -------------------------------------------------------------------------------- 1 | id === (int) $userId; 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Providers/EventServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'App\Listeners\EventListener', 18 | ], 19 | ]; 20 | 21 | /** 22 | * Register any events for your application. 23 | * 24 | * @return void 25 | */ 26 | public function boot() 27 | { 28 | parent::boot(); 29 | 30 | // 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | mapApiRoutes(); 39 | 40 | $this->mapWebRoutes(); 41 | 42 | // 43 | } 44 | 45 | /** 46 | * Define the "web" routes for the application. 47 | * 48 | * These routes all receive session state, CSRF protection, etc. 49 | * 50 | * @return void 51 | */ 52 | protected function mapWebRoutes() 53 | { 54 | Route::group([ 55 | 'middleware' => 'web', 56 | 'namespace' => $this->namespace, 57 | ], function ($router) { 58 | require base_path('routes/web.php'); 59 | }); 60 | } 61 | 62 | /** 63 | * Define the "api" routes for the application. 64 | * 65 | * These routes are typically stateless. 66 | * 67 | * @return void 68 | */ 69 | protected function mapApiRoutes() 70 | { 71 | Route::group([ 72 | 'middleware' => 'api', 73 | 'namespace' => $this->namespace, 74 | 'prefix' => 'api', 75 | ], function ($router) { 76 | require base_path('routes/api.php'); 77 | }); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /app/Tweets/Api.php: -------------------------------------------------------------------------------- 1 | $username, 22 | 'include_rts' => true, 23 | 'include_entities' => true, 24 | 'count' => 200 25 | ]; 26 | 27 | if ($sinceId) $params['since_id'] = $sinceId; 28 | if ($maxId) $params['max_id'] = $maxId; 29 | 30 | $data = Twitter::getUserTimeline($params); 31 | 32 | if (is_array($data) && isset($data[0]) && $data[0] === false) { 33 | throw new \Exception('Error fetching timeline'); 34 | } 35 | 36 | return $data; 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /app/Tweets/Formatter.php: -------------------------------------------------------------------------------- 1 | "tweetid", 9 | "created_at" => "time", 10 | "text" => "text", 11 | "source" => "source", 12 | "coordinates" => "coordinates", 13 | "geo" => "geo", 14 | "place" => "place", 15 | "contributors" => "contributors", 16 | "user.id" => "userid" 17 | ); 18 | 19 | public function transformTweet($tweet) { 20 | $formattedTweet = []; 21 | $extra = []; 22 | 23 | foreach(get_object_vars($tweet) as $key => $tweetValue) { 24 | if (array_key_exists($key, self::DB_MAP)) { 25 | $formattedTweet = $this->formatEntities($key, $tweetValue, $formattedTweet); 26 | } elseif ($key == 'user') { 27 | $formattedTweet['userid'] = (string) $tweetValue->id_str; 28 | } elseif ($key == 'retweeted_status') { 29 | $retweet = []; 30 | $retweetExtra = []; 31 | foreach(get_object_vars($tweetValue) as $kk => $vv) { 32 | if (array_key_exists($kk, self::DB_MAP)) { 33 | $kkey = self::DB_MAP[$kk]; 34 | $vval = $vv; 35 | if (in_array($kkey, ['text', 'source', 'tweetid', 'id', 'id_str'])) { 36 | $vval = (string) $vv; 37 | } elseif ($kkey == "time") { 38 | $vval = strtotime($vv); 39 | } 40 | $retweet[$kkey] = $vval; 41 | } elseif ($kk == "user") { 42 | $retweet['userid'] = (string) $vv->id_str; 43 | $retweet['screenname'] = (string) $vv->screen_name; 44 | } else { 45 | $retweetExtra[$kk] = $vv; 46 | } 47 | } 48 | $retweet['extra'] = $retweetExtra; 49 | $extra['rt'] = $retweet; 50 | } else { 51 | $extra[$key] = $tweetValue; 52 | } 53 | } 54 | $formattedTweet['extra'] = $extra; 55 | $tt = $this->enhanceTweet($formattedTweet); 56 | if (!empty($tt) && is_array($tt) && $tt['text']) { 57 | $formattedTweet = $tt; 58 | } 59 | 60 | $type = ($formattedTweet['text'][0] == "@") ? 1 : (preg_match("/RT @\w+/", $formattedTweet['text']) ? 2 : 0); 61 | 62 | $entities = $formattedTweet['extra']['entities']; 63 | 64 | if (is_array($entities->urls)) 65 | { 66 | foreach ($entities->urls as $url) { 67 | $formattedTweet['text'] = str_replace($url->url, $url->expanded_url, $formattedTweet['text']); 68 | } 69 | } 70 | 71 | return [ 72 | 'userid' => $formattedTweet['userid'], 73 | 'tweetid' => $formattedTweet['tweetid'], 74 | 'type' => $type, 75 | 'time' => $formattedTweet['time'], 76 | 'text' => $this->entityDecode($formattedTweet['text']), 77 | 'source' => $formattedTweet['source'], 78 | 'extra' => serialize($formattedTweet['extra']), 79 | 'coordinates' => serialize($formattedTweet['coordinates']), 80 | 'geo' => serialize($formattedTweet['geo']), 81 | 'place' => serialize($formattedTweet['place']), 82 | 'contributors' => serialize($formattedTweet['contributors']) 83 | ]; 84 | } 85 | 86 | public function enhanceTweet($tweet) { 87 | // Finding entities 88 | $tweetextra = []; 89 | if (!empty($tweet['extra'])) { 90 | if (is_array($tweet['extra'])) { 91 | $tweetextra = $tweet['extra']; 92 | } else { 93 | @$tweetextra = unserialize($tweet['extra']); 94 | } 95 | } 96 | $rt = (array_key_exists("rt", $tweetextra) && !empty($tweetextra['rt'])); 97 | $entities = $rt ? $tweetextra['rt']['extra']['entities'] : $tweetextra['entities']; 98 | 99 | $imgs = []; 100 | $text = $rt ? $tweetextra['rt']['text'] : $tweet['text']; 101 | $mtext = $this->mediaLinkTweetText($text, $entities); 102 | $links = $this->findURLs($mtext); // Two link lists because media links might be different from public URLs 103 | $flinks = $this->findURLs($text); 104 | 105 | if (! empty($links) && ! empty($flinks)) { // connection between the two 106 | $linkmap = array_combine(array_keys($links), array_keys($flinks)); 107 | } 108 | 109 | foreach($links as $link => $l) { 110 | if (is_array($l) && array_key_exists("host", $l) && array_key_exists("path", $l)) { 111 | $domain = $this->domain($l['host']); 112 | $imgid = $this->imgid($l['path']); 113 | if ($imgid) { 114 | if ($domain == "twimg.com") { 115 | $displaylink = $linkmap ? $linkmap[$link] : $link; 116 | $imgs[$displaylink] = "//pbs.twimg.com" . $l['path'] . ":thumb"; 117 | } 118 | if ($domain == "twitpic.com") { 119 | $imgs[$link] = "//twitpic.com/show/thumb/" . $imgid; 120 | } 121 | if ($domain == "imgur.com") { 122 | $imgs[$link] = "//i.imgur.com/" . $imgid . "s.jpg"; 123 | } 124 | if ($domain == "moby.to") { 125 | $imgs[$link] = "http://moby.to/" . $imgid . ":square"; 126 | } 127 | if ($domain == "instagr.am" || $domain == "instagram.com") { 128 | $html = (string) $this->getURL($link); 129 | preg_match('//i', $html, $matches); 130 | if (isset($matches[1])) { 131 | $imgs[$link] = $matches[1]; 132 | } 133 | } 134 | } 135 | } 136 | } 137 | 138 | if (count($imgs) > 0) $tweet['extra']['imgs'] = $imgs; 139 | 140 | return $tweet; 141 | } 142 | 143 | // Replace t.co links with full links, for internal use 144 | private function fullLinkTweetText($text, $entities, $mediaUrl = false) { 145 | if (!$entities) { return $text; } 146 | $sources = property_exists($entities, 'media') ? array_merge($entities->urls, $entities->media) : $entities->urls; 147 | $replacements = []; 148 | foreach($sources as $entity) { 149 | if (property_exists($entity, 'expanded_url')) { 150 | $replacements[$entity->indices[0]] = array( 151 | 'end' => $entity->indices[1], 152 | 'content' => $mediaUrl && property_exists($entity, 'media_url_https') ? $entity->media_url_https : $entity->expanded_url 153 | ); 154 | } 155 | } 156 | $out = ''; 157 | $lastEntityEnded = 0; 158 | ksort($replacements); 159 | foreach($replacements as $position => $replacement) { 160 | $out .= mb_substr($text, $lastEntityEnded, $position - $lastEntityEnded); 161 | $out .= $replacement['content']; 162 | $lastEntityEnded = $replacement['end']; 163 | } 164 | $out .= mb_substr($text, $lastEntityEnded); 165 | return $out; 166 | } 167 | 168 | // Same as above, but prefer media urls 169 | private function mediaLinkTweetText($text, $entities) { 170 | return $this->fullLinkTweetText($text, $entities, true); 171 | } 172 | 173 | private function findURLs($str) { 174 | $urls = []; 175 | preg_match_all("/\b(((https*:\/\/)|www\.).+?)(([!?,.\"\)]+)?(\s|$))/", $str, $m); 176 | foreach($m[1] as $url) { 177 | $u = ($url[0] == "w") ? "//" . $url : $url; 178 | $urls[$u] = parse_url($u); 179 | } 180 | return $urls; 181 | } 182 | 183 | private function domain($host) { 184 | if (empty($host) || !is_string($host)) { return false; } 185 | if (preg_match("/^[0-9\.]+$/", $host)) { return $host; } // IP 186 | if (substr_count($host, ".") <= 1) { 187 | return $host; 188 | } else { 189 | $h = explode(".", $host, 2); 190 | return $h[1]; 191 | } 192 | } 193 | 194 | private function imgid($path) { 195 | $m = []; 196 | preg_match("@/([a-z0-9]+).*@i", $path, $m); 197 | if (count($m) > 0) { 198 | return $m[1]; 199 | } 200 | return false; 201 | } 202 | 203 | function getURL($url, $auth = NULL) { 204 | // HTTP grabbin' cURL options, also exsecror 205 | $httpOptions = array( 206 | CURLOPT_FORBID_REUSE => true, 207 | CURLOPT_POST => false, 208 | CURLOPT_RETURNTRANSFER => true, 209 | CURLOPT_TIMEOUT => 30, 210 | CURLOPT_USERAGENT => "Mozilla/5.0 (Compatible; libCURL)", 211 | CURLOPT_VERBOSE => false, 212 | CURLOPT_SSL_VERIFYPEER => false // Insecurity? 213 | ); 214 | $conn = curl_init($url); 215 | $o = $httpOptions; 216 | if (is_array($auth) && count($auth) == 2) { 217 | $o[CURLOPT_USERPWD] = $auth[0] . ":" . $auth[1]; 218 | } 219 | curl_setopt_array($conn, $o); 220 | $file = curl_exec($conn); 221 | if (!curl_errno($conn)) { 222 | curl_close($conn); 223 | return $file; 224 | } else { 225 | $a = array(false, curl_errno($conn), curl_error($conn)); 226 | curl_close($conn); 227 | return $a; 228 | } 229 | } 230 | 231 | public function entityDecode($str){ 232 | return str_replace("&", "&", str_replace("<", "<", str_replace(">", ">", $str))); 233 | } 234 | 235 | /** 236 | * @param $key 237 | * @param $tweetValue 238 | * @param $formattedTweet 239 | * @return array 240 | */ 241 | private function formatEntities($key, $tweetValue, $formattedTweet): array 242 | { 243 | $key = self::DB_MAP[$key]; 244 | $val = $tweetValue; 245 | if (in_array($key, ['text', 'source', 'tweetid', 'id', 'id_str'])) { 246 | $val = (string)$tweetValue; 247 | } elseif ($key == 'time') { 248 | $val = strtotime($tweetValue); 249 | } 250 | $formattedTweet[$key] = $val; 251 | return $formattedTweet; 252 | } 253 | 254 | } -------------------------------------------------------------------------------- /app/Tweets/Tweet.php: -------------------------------------------------------------------------------- 1 | extra['entities']->media)) return false; 24 | 25 | $photos = []; 26 | 27 | foreach( $this->extra['entities']->media as $url) 28 | { 29 | $photos[] = [ 30 | 'thumb' => $url->media_url . ':thumb', 31 | 'url' => $url->expanded_url 32 | ]; 33 | } 34 | 35 | return $photos; 36 | } 37 | 38 | public function getTimeAttribute($time) 39 | { 40 | return Carbon::createFromTimeStamp($time); 41 | } 42 | 43 | public function getExtraAttribute($extra) 44 | { 45 | // Fix serialization errors, see here: http://stackoverflow.com/questions/10152904/unserialize-function-unserialize-error-at-offset 46 | $extra = preg_replace_callback ( '!s:(\d+):"(.*?)";!', function($match) { 47 | return ($match[1] == strlen($match[2])) ? $match[0] : 's:' . strlen($match[2]) . ':"' . $match[2] . '";'; 48 | }, $extra); 49 | 50 | return @unserialize($extra); 51 | } 52 | 53 | public function getPlaceAttribute($place) 54 | { 55 | return unserialize(str_replace("O:16:\"SimpleXMLElement\"", "O:8:\"stdClass\"", $place)); 56 | } 57 | 58 | public function getReplyAttribute() 59 | { 60 | if ($this->type != TweetType::TYPE_REPLY) return false; 61 | 62 | if (! isset($this->extra['in_reply_to_status_id_str'])) return false; 63 | 64 | return [ 65 | 'id' => $this->extra['in_reply_to_status_id_str'], 66 | 'username' => $this->extra['in_reply_to_screen_name'], 67 | ]; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/Tweets/TweetPresenter.php: -------------------------------------------------------------------------------- 1 | text = Twitter_Autolink::create()->autolink($this->text); 12 | 13 | if ($this->extra['entities']) 14 | { 15 | if (property_exists($this->extra['entities'], 'media')) { 16 | foreach($this->extra['entities']->media as $url) 17 | { 18 | $this->text = str_replace($url->url, '', $this->text); 19 | } 20 | } 21 | } 22 | 23 | if ($this->type == '1') 24 | $this->text = ' ' . $this->text; 25 | else if ($this->type == '2') 26 | $this->text = str_replace('RT ', ' ', $this->text); 27 | 28 | return $this->text; 29 | } 30 | 31 | public function metadata() 32 | { 33 | $url = "https://twitter.com/" . env('TWITTER_USERNAME') . "/status/" .$this->tweetid; 34 | $metadata = ''.$this->time.''; 35 | 36 | if ($this->place) 37 | $metadata = $metadata . ' from ' . $this->place->full_name . ''; 38 | 39 | if ($replyData = $this->reply) 40 | { 41 | $metadata = $metadata . ' in reply to @' . $replyData['username'] . ''; 42 | 43 | } 44 | 45 | return $metadata; 46 | } 47 | } -------------------------------------------------------------------------------- /app/Tweets/TweetQuery.php: -------------------------------------------------------------------------------- 1 | year = $year; 15 | } 16 | 17 | public function forMonth($month) 18 | { 19 | $this->month = $month; 20 | } 21 | 22 | public function forDate($date) 23 | { 24 | $this->date = $date; 25 | } 26 | 27 | public function search($search) 28 | { 29 | $this->search = $search; 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /app/Tweets/TweetRepository.php: -------------------------------------------------------------------------------- 1 | year, $query->month, $query->date); 23 | $start = null; 24 | $end = null; 25 | 26 | if ($query->date) { 27 | $start = $date->copy()->startOfDay(); 28 | $end = $date->copy()->endOfDay(); 29 | } 30 | elseif ($query->month) { 31 | $start = $date->copy()->startOfMonth(); 32 | $end = $date->copy()->endOfMonth(); 33 | } 34 | elseif ($query->year) { 35 | $start = $date->copy()->startOfYear(); 36 | $end = $date->copy()->endOfYear(); 37 | } 38 | 39 | if ($start && $end) { 40 | $db->where('time', '>=', $start->getTimestamp()) 41 | ->where('time', '<=', $end->getTimestamp()); 42 | } 43 | 44 | if ($query->search) { 45 | $db->where('text', 'LIKE', "%$query->search%"); 46 | } 47 | 48 | return [$db->pluck('id')->toArray(), $db->paginate(self::$paginate)]; 49 | } 50 | 51 | /** 52 | * Find tweet by id 53 | * 54 | * @param $id 55 | * @return Tweet 56 | */ 57 | public function findById($id) 58 | { 59 | return Tweet::where('tweetid', $id)->get(); 60 | } 61 | 62 | /** 63 | * Get a random tweet 64 | * 65 | * @return Tweet 66 | */ 67 | public function getRandomTweet() 68 | { 69 | return Tweet::orderByRaw("RAND()")->where('type', '!=', '1')->first(); 70 | } 71 | 72 | /** 73 | * Get latest tweet 74 | * 75 | * @return Tweet 76 | */ 77 | public function getLatest() 78 | { 79 | return Tweet::orderBy('time', 'desc')->first(); 80 | } 81 | 82 | /** 83 | * Get latest tweet ID 84 | * 85 | * @return int 86 | */ 87 | public function getLatestId() 88 | { 89 | $latest = $this->getLatest(); 90 | return $latest ? $latest->tweetid : 0; 91 | } 92 | 93 | /** 94 | * Create tweet 95 | * 96 | * @param $tweet 97 | * @return Tweet 98 | */ 99 | public function addTweet($tweet) 100 | { 101 | return Tweet::firstOrCreate($tweet); 102 | } 103 | 104 | /** 105 | * Add tweets to DB 106 | * 107 | * @param array $tweets 108 | */ 109 | public function addTweets(array $tweets) 110 | { 111 | foreach ($tweets as $tweet) { 112 | $this->addTweet($tweet); 113 | } 114 | } 115 | 116 | public function stats() 117 | { 118 | $clients = $this->topClients(); 119 | 120 | $totals = $this->typeCounts()->mapWithKeys(function($type) { 121 | return [TweetType::getTypeString($type['type']) => $type['count']]; 122 | }); 123 | 124 | $totals['all'] = $totals->sum(); 125 | 126 | $average = $this->average(); 127 | 128 | return [$totals, $clients, $average]; 129 | 130 | } 131 | 132 | public function topClients() 133 | { 134 | return Tweet::select(DB::raw('count(*) as count, source')) 135 | ->groupBy('source') 136 | ->orderBy('count', 'desc') 137 | ->limit(10) 138 | ->get(); 139 | } 140 | 141 | public function typeCounts() 142 | { 143 | return Tweet::select(DB::raw('count(*) as count, type')) 144 | ->groupBy('type') 145 | ->get(); 146 | } 147 | 148 | public function average() 149 | { 150 | $firstTweet = Tweet::first(); 151 | 152 | $daysSince = $firstTweet->time->diffInDays(Carbon::now()); 153 | 154 | return [ 155 | 'average' => number_format(Tweet::count() / $daysSince, 2), 156 | 'daysSince' => $daysSince, 157 | 'first' => $firstTweet->time 158 | ]; 159 | } 160 | 161 | /** 162 | * Get tweets for specific date range 163 | * 164 | * @param Int $year 165 | * @param Int $month 166 | * @param Int $day 167 | * @return array Tweets for date range 168 | */ 169 | public function getForDate($year, $month, $day) 170 | { 171 | $date = Carbon::createFromDate($year, $month, $day); 172 | 173 | if ($day) 174 | { 175 | $start = strtotime($date->copy()->startOfDay()); 176 | $end = strtotime($date->copy()->endOfDay()); 177 | } 178 | elseif ($month) 179 | { 180 | $start = strtotime($date->copy()->startOfMonth()); 181 | $end = strtotime($date->copy()->endOfMonth()); 182 | } 183 | else 184 | { 185 | $start = strtotime($date->copy()->startOfYear()); 186 | $end = strtotime($date->copy()->endOfYear()); 187 | } 188 | 189 | return Tweet::where('time', '>=', $start) 190 | ->where('time', '<=', $end) 191 | ->orderBy('time', 'desc') 192 | ->paginate(self::$paginate); 193 | } 194 | 195 | public function monthCount($ids = null) 196 | { 197 | $counts = DB::select(DB::raw('select Year(FROM_UNIXTIME(time)) as year, Month(FROM_UNIXTIME(time)) as month, Count(*) as count 198 | FROM tweets 199 | ' . ($ids ? 'where id in (' . implode(',', $ids) . ')' : '') . ' 200 | GROUP BY Year(FROM_UNIXTIME(time)), Month(FROM_UNIXTIME(time)) 201 | ORDER BY Year(FROM_UNIXTIME(time)) desc, Month(FROM_UNIXTIME(time)) desc')); 202 | 203 | return $this->calculatePercentagesAndTotal($counts); 204 | } 205 | 206 | public function dayCount($ids = null) 207 | { 208 | $counts = DB::select(DB::raw('select Year(FROM_UNIXTIME(time)) as year, Month(FROM_UNIXTIME(time)) as month, Day(FROM_UNIXTIME(time)) as day, Count(*) as count 209 | FROM tweets 210 | ' . ($ids ? 'where id in (' . implode(',', $ids) . ')' : '') . ' 211 | GROUP BY Year(FROM_UNIXTIME(time)), Month(FROM_UNIXTIME(time)), Day(FROM_UNIXTIME(time))')); 212 | 213 | return $this->calculatePercentagesAndTotal($counts); 214 | } 215 | 216 | public function calculatePercentagesAndTotal($counts) 217 | { 218 | $max = 0; 219 | $yearCounts = []; 220 | 221 | foreach($counts as $count) 222 | { 223 | if($count->count > $max) 224 | { 225 | $max = $count->count; 226 | } 227 | } 228 | 229 | foreach($counts as $count) 230 | { 231 | $count->percentage = round(($count->count / $max) * 100); 232 | 233 | if ( ! isset($yearCounts[$count->year])) 234 | $yearCounts[$count->year] = 0; 235 | 236 | $yearCounts[$count->year] += $count->count; 237 | } 238 | 239 | $counts['tweet_count'] = $counts; 240 | 241 | $counts['year_counts'] = $yearCounts; 242 | 243 | return $counts; 244 | } 245 | 246 | } -------------------------------------------------------------------------------- /app/Tweets/TweetType.php: -------------------------------------------------------------------------------- 1 | 'tweet', 13 | self::TYPE_REPLY => 'reply', 14 | self::TYPE_RETWEET => 'retweet', 15 | ]; 16 | 17 | public static function getTypeString($type, $plural = false) 18 | { 19 | $typeString = self::$types[$type]; 20 | 21 | if (! $plural) return $typeString; 22 | 23 | if ($type == self::TYPE_REPLY) 24 | { 25 | $typeString = 'replies'; 26 | } 27 | else { 28 | $typeString = $typeString . 's'; 29 | } 30 | 31 | return $typeString; 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /app/User.php: -------------------------------------------------------------------------------- 1 | attributes['password'] = Hash::make($password); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/helpers.php: -------------------------------------------------------------------------------- 1 | 'January', 7 | 2 => 'February', 8 | 3 => 'March', 9 | 4 => 'April', 10 | 5 => 'May', 11 | 6 => 'June', 12 | 7 => 'July', 13 | 8 => 'August', 14 | 9 => 'September', 15 | 10 => 'October', 16 | 11 => 'November', 17 | 12 => 'December' 18 | ]; 19 | 20 | return $months[$month]; 21 | } 22 | 23 | function displayDate($date) 24 | { 25 | $ends = array('th','st','nd','rd','th','th','th','th','th','th'); 26 | if (($date %100) >= 11 && ($date%100) <= 13) 27 | $abbreviation = $date. 'th'; 28 | else 29 | $abbreviation = $date. $ends[$date % 10]; 30 | 31 | return $abbreviation; 32 | } -------------------------------------------------------------------------------- /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.6.4", 9 | "laravel/framework": "5.4.*", 10 | "thujohn/twitter": "^2.2", 11 | "nojimage/twitter-text-php": "^1.1", 12 | "creitive/laravel5-breadcrumbs": "^2.0", 13 | "guilhermegonzaga/presenter": "^1.0", 14 | "laravelcollective/html": "^5.3.0" 15 | 16 | }, 17 | "require-dev": { 18 | "fzaninotto/faker": "~1.4", 19 | "mockery/mockery": "0.9.*", 20 | "phpunit/phpunit": "~5.0", 21 | "symfony/css-selector": "3.1.*", 22 | "symfony/dom-crawler": "3.1.*" 23 | }, 24 | "autoload": { 25 | "classmap": [ 26 | "database" 27 | ], 28 | "psr-4": { 29 | "App\\": "app/" 30 | }, 31 | "files": [ 32 | "app/helpers.php" 33 | ] 34 | }, 35 | "autoload-dev": { 36 | "classmap": [ 37 | "tests/TestCase.php" 38 | ] 39 | }, 40 | "scripts": { 41 | "post-root-package-install": [ 42 | "php -r \"file_exists('.env') || copy('.env.example', '.env');\"" 43 | ], 44 | "post-create-project-cmd": [ 45 | "php artisan key:generate" 46 | ], 47 | "post-install-cmd": [ 48 | "Illuminate\\Foundation\\ComposerScripts::postInstall", 49 | "php artisan optimize" 50 | ], 51 | "post-update-cmd": [ 52 | "Illuminate\\Foundation\\ComposerScripts::postUpdate", 53 | "php artisan optimize" 54 | ] 55 | }, 56 | "config": { 57 | "preferred-install": "dist" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /config/app.php: -------------------------------------------------------------------------------- 1 | 'Laravel', 16 | 17 | /* 18 | |-------------------------------------------------------------------------- 19 | | Application Environment 20 | |-------------------------------------------------------------------------- 21 | | 22 | | This value determines the "environment" your application is currently 23 | | running in. This may determine how you prefer to configure various 24 | | services your application utilizes. Set this in your ".env" file. 25 | | 26 | */ 27 | 28 | 'env' => env('APP_ENV', 'production'), 29 | 30 | /* 31 | |-------------------------------------------------------------------------- 32 | | Application Debug Mode 33 | |-------------------------------------------------------------------------- 34 | | 35 | | When your application is in debug mode, detailed error messages with 36 | | stack traces will be shown on every error that occurs within your 37 | | application. If disabled, a simple generic error page is shown. 38 | | 39 | */ 40 | 41 | 'debug' => env('APP_DEBUG', false), 42 | 43 | /* 44 | |-------------------------------------------------------------------------- 45 | | Application URL 46 | |-------------------------------------------------------------------------- 47 | | 48 | | This URL is used by the console to properly generate URLs when using 49 | | the Artisan command line tool. You should set this to the root of 50 | | your application so that it is used when running Artisan tasks. 51 | | 52 | */ 53 | 54 | 'url' => env('APP_URL', 'http://localhost'), 55 | 56 | /* 57 | |-------------------------------------------------------------------------- 58 | | Application Timezone 59 | |-------------------------------------------------------------------------- 60 | | 61 | | Here you may specify the default timezone for your application, which 62 | | will be used by the PHP date and date-time functions. We have gone 63 | | ahead and set this to a sensible default for you out of the box. 64 | | 65 | */ 66 | 67 | 'timezone' => 'UTC', 68 | 69 | /* 70 | |-------------------------------------------------------------------------- 71 | | Application Locale Configuration 72 | |-------------------------------------------------------------------------- 73 | | 74 | | The application locale determines the default locale that will be used 75 | | by the translation service provider. You are free to set this value 76 | | to any of the locales which will be supported by the application. 77 | | 78 | */ 79 | 80 | 'locale' => 'en', 81 | 82 | /* 83 | |-------------------------------------------------------------------------- 84 | | Application Fallback Locale 85 | |-------------------------------------------------------------------------- 86 | | 87 | | The fallback locale determines the locale to use when the current one 88 | | is not available. You may change the value to correspond to any of 89 | | the language folders that are provided through your application. 90 | | 91 | */ 92 | 93 | 'fallback_locale' => 'en', 94 | 95 | /* 96 | |-------------------------------------------------------------------------- 97 | | Encryption Key 98 | |-------------------------------------------------------------------------- 99 | | 100 | | This key is used by the Illuminate encrypter service and should be set 101 | | to a random, 32 character string, otherwise these encrypted strings 102 | | will not be safe. Please do this before deploying an application! 103 | | 104 | */ 105 | 106 | 'key' => env('APP_KEY'), 107 | 108 | 'cipher' => 'AES-256-CBC', 109 | 110 | /* 111 | |-------------------------------------------------------------------------- 112 | | Logging Configuration 113 | |-------------------------------------------------------------------------- 114 | | 115 | | Here you may configure the log settings for your application. Out of 116 | | the box, Laravel uses the Monolog PHP logging library. This gives 117 | | you a variety of powerful log handlers / formatters to utilize. 118 | | 119 | | Available Settings: "single", "daily", "syslog", "errorlog" 120 | | 121 | */ 122 | 123 | 'log' => env('APP_LOG', 'single'), 124 | 125 | 'log_level' => env('APP_LOG_LEVEL', 'debug'), 126 | 127 | /* 128 | |-------------------------------------------------------------------------- 129 | | Autoloaded Service Providers 130 | |-------------------------------------------------------------------------- 131 | | 132 | | The service providers listed here will be automatically loaded on the 133 | | request to your application. Feel free to add your own services to 134 | | this array to grant expanded functionality to your applications. 135 | | 136 | */ 137 | 138 | 'providers' => [ 139 | 140 | /* 141 | * Laravel Framework Service Providers... 142 | */ 143 | Illuminate\Auth\AuthServiceProvider::class, 144 | Illuminate\Broadcasting\BroadcastServiceProvider::class, 145 | Illuminate\Bus\BusServiceProvider::class, 146 | Illuminate\Cache\CacheServiceProvider::class, 147 | Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, 148 | Illuminate\Cookie\CookieServiceProvider::class, 149 | Illuminate\Database\DatabaseServiceProvider::class, 150 | Illuminate\Encryption\EncryptionServiceProvider::class, 151 | Illuminate\Filesystem\FilesystemServiceProvider::class, 152 | Illuminate\Foundation\Providers\FoundationServiceProvider::class, 153 | Illuminate\Hashing\HashServiceProvider::class, 154 | Illuminate\Mail\MailServiceProvider::class, 155 | Illuminate\Notifications\NotificationServiceProvider::class, 156 | Illuminate\Pagination\PaginationServiceProvider::class, 157 | Illuminate\Pipeline\PipelineServiceProvider::class, 158 | Illuminate\Queue\QueueServiceProvider::class, 159 | Illuminate\Redis\RedisServiceProvider::class, 160 | Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, 161 | Illuminate\Session\SessionServiceProvider::class, 162 | Illuminate\Translation\TranslationServiceProvider::class, 163 | Illuminate\Validation\ValidationServiceProvider::class, 164 | Illuminate\View\ViewServiceProvider::class, 165 | 166 | Thujohn\Twitter\TwitterServiceProvider::class, 167 | Creitive\Breadcrumbs\BreadcrumbsServiceProvider::class, 168 | Collective\Html\HtmlServiceProvider::class, 169 | 170 | /* 171 | * Package Service Providers... 172 | */ 173 | 174 | // 175 | 176 | /* 177 | * Application Service Providers... 178 | */ 179 | App\Providers\AppServiceProvider::class, 180 | App\Providers\AuthServiceProvider::class, 181 | // App\Providers\BroadcastServiceProvider::class, 182 | App\Providers\EventServiceProvider::class, 183 | App\Providers\RouteServiceProvider::class, 184 | 185 | ], 186 | 187 | /* 188 | |-------------------------------------------------------------------------- 189 | | Class Aliases 190 | |-------------------------------------------------------------------------- 191 | | 192 | | This array of class aliases will be registered when this application 193 | | is started. However, feel free to register as many as you wish as 194 | | the aliases are "lazy" loaded so they don't hinder performance. 195 | | 196 | */ 197 | 198 | 'aliases' => [ 199 | 200 | 'App' => Illuminate\Support\Facades\App::class, 201 | 'Artisan' => Illuminate\Support\Facades\Artisan::class, 202 | 'Auth' => Illuminate\Support\Facades\Auth::class, 203 | 'Blade' => Illuminate\Support\Facades\Blade::class, 204 | 'Bus' => Illuminate\Support\Facades\Bus::class, 205 | 'Cache' => Illuminate\Support\Facades\Cache::class, 206 | 'Config' => Illuminate\Support\Facades\Config::class, 207 | 'Cookie' => Illuminate\Support\Facades\Cookie::class, 208 | 'Crypt' => Illuminate\Support\Facades\Crypt::class, 209 | 'DB' => Illuminate\Support\Facades\DB::class, 210 | 'Eloquent' => Illuminate\Database\Eloquent\Model::class, 211 | 'Event' => Illuminate\Support\Facades\Event::class, 212 | 'File' => Illuminate\Support\Facades\File::class, 213 | 'Gate' => Illuminate\Support\Facades\Gate::class, 214 | 'Hash' => Illuminate\Support\Facades\Hash::class, 215 | 'Lang' => Illuminate\Support\Facades\Lang::class, 216 | 'Log' => Illuminate\Support\Facades\Log::class, 217 | 'Mail' => Illuminate\Support\Facades\Mail::class, 218 | 'Notification' => Illuminate\Support\Facades\Notification::class, 219 | 'Password' => Illuminate\Support\Facades\Password::class, 220 | 'Queue' => Illuminate\Support\Facades\Queue::class, 221 | 'Redirect' => Illuminate\Support\Facades\Redirect::class, 222 | 'Redis' => Illuminate\Support\Facades\Redis::class, 223 | 'Request' => Illuminate\Support\Facades\Request::class, 224 | 'Response' => Illuminate\Support\Facades\Response::class, 225 | 'Route' => Illuminate\Support\Facades\Route::class, 226 | 'Schema' => Illuminate\Support\Facades\Schema::class, 227 | 'Session' => Illuminate\Support\Facades\Session::class, 228 | 'Storage' => Illuminate\Support\Facades\Storage::class, 229 | 'URL' => Illuminate\Support\Facades\URL::class, 230 | 'Validator' => Illuminate\Support\Facades\Validator::class, 231 | 'View' => Illuminate\Support\Facades\View::class, 232 | 233 | 'Twitter' => Thujohn\Twitter\Facades\Twitter::class, 234 | 'Breadcrumbs' => Creitive\Breadcrumbs\Facades\Breadcrumbs::class, 235 | 'Form' => Collective\Html\FormFacade::class, 236 | 'Html' => Collective\Html\HtmlFacade::class, 237 | ], 238 | 239 | ]; 240 | -------------------------------------------------------------------------------- /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 | | You may specify multiple password reset configurations if you have more 85 | | than one user table or model in the application and you want to have 86 | | separate password reset settings based on the specific user types. 87 | | 88 | | The expire time is the number of minutes that the reset token should be 89 | | considered valid. This security feature keeps tokens short-lived so 90 | | they have less time to be guessed. You may change this as needed. 91 | | 92 | */ 93 | 94 | 'passwords' => [ 95 | 'users' => [ 96 | 'provider' => 'users', 97 | 'table' => 'password_resets', 98 | 'expire' => 60, 99 | ], 100 | ], 101 | 102 | ]; 103 | -------------------------------------------------------------------------------- /config/broadcasting.php: -------------------------------------------------------------------------------- 1 | env('BROADCAST_DRIVER', 'null'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Broadcast Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the broadcast connections that will be used 26 | | to broadcast events to other systems or over websockets. Samples of 27 | | each available type of connection are provided inside this array. 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'pusher' => [ 34 | 'driver' => 'pusher', 35 | 'key' => env('PUSHER_KEY'), 36 | 'secret' => env('PUSHER_SECRET'), 37 | 'app_id' => env('PUSHER_APP_ID'), 38 | 'options' => [ 39 | // 40 | ], 41 | ], 42 | 43 | 'redis' => [ 44 | 'driver' => 'redis', 45 | 'connection' => 'default', 46 | ], 47 | 48 | 'log' => [ 49 | 'driver' => 'log', 50 | ], 51 | 52 | 'null' => [ 53 | 'driver' => 'null', 54 | ], 55 | 56 | ], 57 | 58 | ]; 59 | -------------------------------------------------------------------------------- /config/cache.php: -------------------------------------------------------------------------------- 1 | env('CACHE_DRIVER', 'file'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Cache Stores 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the cache "stores" for your application as 26 | | well as their drivers. You may even define multiple stores for the 27 | | same cache driver to group types of items stored in your caches. 28 | | 29 | */ 30 | 31 | 'stores' => [ 32 | 33 | 'apc' => [ 34 | 'driver' => 'apc', 35 | ], 36 | 37 | 'array' => [ 38 | 'driver' => 'array', 39 | ], 40 | 41 | 'database' => [ 42 | 'driver' => 'database', 43 | 'table' => 'cache', 44 | 'connection' => null, 45 | ], 46 | 47 | 'file' => [ 48 | 'driver' => 'file', 49 | 'path' => storage_path('framework/cache'), 50 | ], 51 | 52 | 'memcached' => [ 53 | 'driver' => 'memcached', 54 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), 55 | 'sasl' => [ 56 | env('MEMCACHED_USERNAME'), 57 | env('MEMCACHED_PASSWORD'), 58 | ], 59 | 'options' => [ 60 | // Memcached::OPT_CONNECT_TIMEOUT => 2000, 61 | ], 62 | 'servers' => [ 63 | [ 64 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 65 | 'port' => env('MEMCACHED_PORT', 11211), 66 | 'weight' => 100, 67 | ], 68 | ], 69 | ], 70 | 71 | 'redis' => [ 72 | 'driver' => 'redis', 73 | 'connection' => 'default', 74 | ], 75 | 76 | ], 77 | 78 | /* 79 | |-------------------------------------------------------------------------- 80 | | Cache Key Prefix 81 | |-------------------------------------------------------------------------- 82 | | 83 | | When utilizing a RAM based store such as APC or Memcached, there might 84 | | be other applications utilizing the same cache. So, we'll specify a 85 | | value to get prefixed to all our keys so we can avoid collisions. 86 | | 87 | */ 88 | 89 | 'prefix' => 'laravel', 90 | 91 | ]; 92 | -------------------------------------------------------------------------------- /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_OBJ, 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' => env('DB_DATABASE', database_path('database.sqlite')), 52 | 'prefix' => '', 53 | ], 54 | 55 | 'mysql' => [ 56 | 'driver' => 'mysql', 57 | 'host' => env('DB_HOST', 'localhost'), 58 | 'port' => env('DB_PORT', '3306'), 59 | 'database' => env('DB_DATABASE', 'forge'), 60 | 'username' => env('DB_USERNAME', 'forge'), 61 | 'password' => env('DB_PASSWORD', ''), 62 | 'charset' => 'utf8mb4', 63 | 'collation' => 'utf8mb4_unicode_ci', 64 | 'prefix' => '', 65 | 'strict' => true, 66 | 'engine' => null, 67 | ], 68 | 69 | 'pgsql' => [ 70 | 'driver' => 'pgsql', 71 | 'host' => env('DB_HOST', 'localhost'), 72 | 'port' => env('DB_PORT', '5432'), 73 | 'database' => env('DB_DATABASE', 'forge'), 74 | 'username' => env('DB_USERNAME', 'forge'), 75 | 'password' => env('DB_PASSWORD', ''), 76 | 'charset' => 'utf8', 77 | 'prefix' => '', 78 | 'schema' => 'public', 79 | 'sslmode' => 'prefer', 80 | ], 81 | 82 | ], 83 | 84 | /* 85 | |-------------------------------------------------------------------------- 86 | | Migration Repository Table 87 | |-------------------------------------------------------------------------- 88 | | 89 | | This table keeps track of all the migrations that have already run for 90 | | your application. Using this information, we can determine which of 91 | | the migrations on disk haven't actually been run in the database. 92 | | 93 | */ 94 | 95 | 'migrations' => 'migrations', 96 | 97 | /* 98 | |-------------------------------------------------------------------------- 99 | | Redis Databases 100 | |-------------------------------------------------------------------------- 101 | | 102 | | Redis is an open source, fast, and advanced key-value store that also 103 | | provides a richer set of commands than a typical key-value systems 104 | | such as APC or Memcached. Laravel makes it easy to dig right in. 105 | | 106 | */ 107 | 108 | 'redis' => [ 109 | 110 | 'cluster' => false, 111 | 112 | 'default' => [ 113 | 'host' => env('REDIS_HOST', 'localhost'), 114 | 'password' => env('REDIS_PASSWORD', null), 115 | 'port' => env('REDIS_PORT', 6379), 116 | 'database' => 0, 117 | ], 118 | 119 | ], 120 | 121 | ]; 122 | -------------------------------------------------------------------------------- /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'), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | SMTP Host Address 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Here you may provide the host address of the SMTP server used by your 27 | | applications. A default option is provided that is compatible with 28 | | the Mailgun mail service which will provide reliable deliveries. 29 | | 30 | */ 31 | 32 | 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | SMTP Host Port 37 | |-------------------------------------------------------------------------- 38 | | 39 | | This is the SMTP port used by your application to deliver e-mails to 40 | | users of the application. Like the host we have set this value to 41 | | stay compatible with the Mailgun e-mail application by default. 42 | | 43 | */ 44 | 45 | 'port' => env('MAIL_PORT', 587), 46 | 47 | /* 48 | |-------------------------------------------------------------------------- 49 | | Global "From" Address 50 | |-------------------------------------------------------------------------- 51 | | 52 | | You may wish for all e-mails sent by your application to be sent from 53 | | the same address. Here, you may specify a name and address that is 54 | | used globally for all e-mails that are sent by your application. 55 | | 56 | */ 57 | 58 | 'from' => [ 59 | 'address' => 'hello@example.com', 60 | 'name' => 'Example', 61 | ], 62 | 63 | /* 64 | |-------------------------------------------------------------------------- 65 | | E-Mail Encryption Protocol 66 | |-------------------------------------------------------------------------- 67 | | 68 | | Here you may specify the encryption protocol that should be used when 69 | | the application send e-mail messages. A sensible default using the 70 | | transport layer security protocol should provide great security. 71 | | 72 | */ 73 | 74 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'), 75 | 76 | /* 77 | |-------------------------------------------------------------------------- 78 | | SMTP Server Username 79 | |-------------------------------------------------------------------------- 80 | | 81 | | If your SMTP server requires a username for authentication, you should 82 | | set it here. This will get used to authenticate with your server on 83 | | connection. You may also set the "password" value below this one. 84 | | 85 | */ 86 | 87 | 'username' => env('MAIL_USERNAME'), 88 | 89 | /* 90 | |-------------------------------------------------------------------------- 91 | | SMTP Server Password 92 | |-------------------------------------------------------------------------- 93 | | 94 | | Here you may set the password required by your SMTP server to send out 95 | | messages from your application. This will be given to the server on 96 | | connection so that the application will be able to send messages. 97 | | 98 | */ 99 | 100 | 'password' => env('MAIL_PASSWORD'), 101 | 102 | /* 103 | |-------------------------------------------------------------------------- 104 | | Sendmail System Path 105 | |-------------------------------------------------------------------------- 106 | | 107 | | When using the "sendmail" driver to send e-mails, we will need to know 108 | | the path to where Sendmail lives on this server. A default path has 109 | | been provided here, which will work well on most of your systems. 110 | | 111 | */ 112 | 113 | 'sendmail' => '/usr/sbin/sendmail -bs', 114 | 115 | ]; 116 | -------------------------------------------------------------------------------- /config/queue.php: -------------------------------------------------------------------------------- 1 | env('QUEUE_DRIVER', 'sync'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Queue Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may configure the connection information for each server that 26 | | is used by your application. A default configuration has been added 27 | | for each back-end shipped with Laravel. You are free to add more. 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'sync' => [ 34 | 'driver' => 'sync', 35 | ], 36 | 37 | 'database' => [ 38 | 'driver' => 'database', 39 | 'table' => 'jobs', 40 | 'queue' => 'default', 41 | 'retry_after' => 90, 42 | ], 43 | 44 | 'beanstalkd' => [ 45 | 'driver' => 'beanstalkd', 46 | 'host' => 'localhost', 47 | 'queue' => 'default', 48 | 'retry_after' => 90, 49 | ], 50 | 51 | 'sqs' => [ 52 | 'driver' => 'sqs', 53 | 'key' => 'your-public-key', 54 | 'secret' => 'your-secret-key', 55 | 'prefix' => 'https://sqs.us-east-1.amazonaws.com/your-account-id', 56 | 'queue' => 'your-queue-name', 57 | 'region' => 'us-east-1', 58 | ], 59 | 60 | 'redis' => [ 61 | 'driver' => 'redis', 62 | 'connection' => 'default', 63 | 'queue' => 'default', 64 | 'retry_after' => 90, 65 | ], 66 | 67 | ], 68 | 69 | /* 70 | |-------------------------------------------------------------------------- 71 | | Failed Queue Jobs 72 | |-------------------------------------------------------------------------- 73 | | 74 | | These options configure the behavior of failed queue job logging so you 75 | | can control which database and table are used to store the jobs that 76 | | have failed. You may change them to any database / table you wish. 77 | | 78 | */ 79 | 80 | 'failed' => [ 81 | 'database' => env('DB_CONNECTION', 'mysql'), 82 | 'table' => 'failed_jobs', 83 | ], 84 | 85 | ]; 86 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => env('MAILGUN_DOMAIN'), 19 | 'secret' => env('MAILGUN_SECRET'), 20 | ], 21 | 22 | 'ses' => [ 23 | 'key' => env('SES_KEY'), 24 | 'secret' => env('SES_SECRET'), 25 | 'region' => 'us-east-1', 26 | ], 27 | 28 | 'sparkpost' => [ 29 | 'secret' => env('SPARKPOST_SECRET'), 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 Cache Store 91 | |-------------------------------------------------------------------------- 92 | | 93 | | When using the "apc" or "memcached" session drivers, you may specify a 94 | | cache store that should be used for these sessions. This value must 95 | | correspond with one of the application's configured cache stores. 96 | | 97 | */ 98 | 99 | 'store' => null, 100 | 101 | /* 102 | |-------------------------------------------------------------------------- 103 | | Session Sweeping Lottery 104 | |-------------------------------------------------------------------------- 105 | | 106 | | Some session drivers must manually sweep their storage location to get 107 | | rid of old sessions from storage. Here are the chances that it will 108 | | happen on a given request. By default, the odds are 2 out of 100. 109 | | 110 | */ 111 | 112 | 'lottery' => [2, 100], 113 | 114 | /* 115 | |-------------------------------------------------------------------------- 116 | | Session Cookie Name 117 | |-------------------------------------------------------------------------- 118 | | 119 | | Here you may change the name of the cookie used to identify a session 120 | | instance by ID. The name specified here will get used every time a 121 | | new session cookie is created by the framework for every driver. 122 | | 123 | */ 124 | 125 | 'cookie' => 'laravel_session', 126 | 127 | /* 128 | |-------------------------------------------------------------------------- 129 | | Session Cookie Path 130 | |-------------------------------------------------------------------------- 131 | | 132 | | The session cookie path determines the path for which the cookie will 133 | | be regarded as available. Typically, this will be the root path of 134 | | your application but you are free to change this when necessary. 135 | | 136 | */ 137 | 138 | 'path' => '/', 139 | 140 | /* 141 | |-------------------------------------------------------------------------- 142 | | Session Cookie Domain 143 | |-------------------------------------------------------------------------- 144 | | 145 | | Here you may change the domain of the cookie used to identify a session 146 | | in your application. This will determine which domains the cookie is 147 | | available to in your application. A sensible default has been set. 148 | | 149 | */ 150 | 151 | 'domain' => env('SESSION_DOMAIN', null), 152 | 153 | /* 154 | |-------------------------------------------------------------------------- 155 | | HTTPS Only Cookies 156 | |-------------------------------------------------------------------------- 157 | | 158 | | By setting this option to true, session cookies will only be sent back 159 | | to the server if the browser has a HTTPS connection. This will keep 160 | | the cookie from being sent to you if it can not be done securely. 161 | | 162 | */ 163 | 164 | 'secure' => env('SESSION_SECURE_COOKIE', false), 165 | 166 | /* 167 | |-------------------------------------------------------------------------- 168 | | HTTP Access Only 169 | |-------------------------------------------------------------------------- 170 | | 171 | | Setting this value to true will prevent JavaScript from accessing the 172 | | value of the cookie and the cookie will only be accessible through 173 | | the HTTP protocol. You are free to modify this option if needed. 174 | | 175 | */ 176 | 177 | 'http_only' => true, 178 | 179 | ]; 180 | -------------------------------------------------------------------------------- /config/ttwitter.php: -------------------------------------------------------------------------------- 1 | false, 7 | 8 | 'API_URL' => 'api.twitter.com', 9 | 'UPLOAD_URL' => 'upload.twitter.com', 10 | 'API_VERSION' => '1.1', 11 | 'AUTHENTICATE_URL' => 'https://api.twitter.com/oauth/authenticate', 12 | 'AUTHORIZE_URL' => 'https://api.twitter.com/oauth/authorize', 13 | 'ACCESS_TOKEN_URL' => 'https://api.twitter.com/oauth/access_token', 14 | 'REQUEST_TOKEN_URL' => 'https://api.twitter.com/oauth/request_token', 15 | 'USE_SSL' => true, 16 | 17 | 'CONSUMER_KEY' => function_exists('env') ? env('TWITTER_CONSUMER_KEY', '') : '', 18 | 'CONSUMER_SECRET' => function_exists('env') ? env('TWITTER_CONSUMER_SECRET', '') : '', 19 | 'ACCESS_TOKEN' => function_exists('env') ? env('TWITTER_ACCESS_TOKEN', '') : '', 20 | 'ACCESS_TOKEN_SECRET' => function_exists('env') ? env('TWITTER_ACCESS_TOKEN_SECRET', '') : '', 21 | ]; 22 | -------------------------------------------------------------------------------- /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/factories/ModelFactory.php: -------------------------------------------------------------------------------- 1 | define(App\User::class, function (Faker\Generator $faker) { 16 | static $password; 17 | 18 | return [ 19 | 'name' => $faker->name, 20 | 'email' => $faker->unique()->safeEmail, 21 | 'password' => $password ?: $password = bcrypt('secret'), 22 | 'remember_token' => str_random(10), 23 | ]; 24 | }); 25 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('email')->unique(); 19 | $table->string('password'); 20 | $table->rememberToken(); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('users'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_100000_create_password_resets_table.php: -------------------------------------------------------------------------------- 1 | string('email')->index(); 18 | $table->string('token')->index(); 19 | $table->timestamp('created_at')->nullable(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::dropIfExists('password_resets'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/migrations/2016_12_17_092250_CreateTweetsTable.php: -------------------------------------------------------------------------------- 1 | increments('id'); 19 | $table->string('userid', 100); 20 | $table->string('tweetid', 100)->unique('tweetid'); 21 | $table->boolean('type')->default(0); 22 | $table->integer('time')->unsigned(); 23 | $table->text('text'); 24 | $table->string('source'); 25 | $table->boolean('favorite')->default(0); 26 | $table->text('extra'); 27 | $table->text('coordinates'); 28 | $table->text('geo'); 29 | $table->text('place'); 30 | $table->text('contributors'); 31 | }); 32 | 33 | // Laravel migration doesn't support MEDIUMBLOB 34 | DB::statement('ALTER TABLE `tweets` MODIFY `extra` MEDIUMBLOB'); 35 | } 36 | 37 | /** 38 | * Reverse the migrations. 39 | * 40 | * @return void 41 | */ 42 | public function down() 43 | { 44 | Schema::drop('tweets'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /database/migrations/2016_12_17_094706_CreateArchiveLogTable.php: -------------------------------------------------------------------------------- 1 | increments('id'); 19 | $table->string('filename'); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::drop('archive_log'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /database/migrations/2016_12_18_090425_CreateFetchLogTable.php: -------------------------------------------------------------------------------- 1 | increments('id'); 19 | $table->integer('count'); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::drop('fetch_log'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /database/seeds/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call(UsersTableSeeder::class); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const elixir = require('laravel-elixir'); 2 | 3 | require('laravel-elixir-vue-2'); 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 your application as well as publishing vendor resources. 13 | | 14 | */ 15 | 16 | elixir(mix => { 17 | mix.sass('app.scss') 18 | .webpack('app.js'); 19 | }); 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "prod": "gulp --production", 5 | "dev": "gulp watch" 6 | }, 7 | "devDependencies": { 8 | "bootstrap-sass": "^3.3.7", 9 | "gulp": "^3.9.1", 10 | "jquery": "^3.1.0", 11 | "laravel-elixir": "^6.0.0-11", 12 | "laravel-elixir-vue-2": "^0.2.0", 13 | "laravel-elixir-webpack-official": "^1.0.2", 14 | "lodash": "^4.16.2", 15 | "vue": "^2.0.1", 16 | "vue-resource": "^1.0.3" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests 14 | 15 | 16 | 17 | 18 | ./app 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /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/assets/css/style.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Bootstrap overrides 3 | */ 4 | .btn { 5 | padding: 8px 14px; 6 | font-size: 16px; 7 | } 8 | 9 | .form-group label { 10 | text-transform: uppercase; 11 | font-size: small; 12 | } 13 | 14 | .list-group { 15 | font-size: 13px; 16 | } 17 | 18 | .well { 19 | background-color: #dbedf8; 20 | } 21 | 22 | .breadcrumb { 23 | background-color: #dbedf8; 24 | } 25 | 26 | /** 27 | * Main 28 | */ 29 | body { 30 | font-size: 1.7em; 31 | font-family: 'Titillium Web', sans-serif; 32 | } 33 | 34 | h1 { 35 | margin-bottom: 20px; 36 | } 37 | 38 | #app-wrapper { 39 | margin-top: 75px; 40 | } 41 | 42 | .year-heading { 43 | font-weight: bold; 44 | font-size: 120%; 45 | } 46 | 47 | .month-item { 48 | position: relative; 49 | padding-left: 35px; 50 | z-index: 1; 51 | } 52 | 53 | .tweet-percentage { 54 | position: absolute; 55 | background: #ea6c5f; 56 | height: 2px; 57 | bottom: 0; 58 | left: 0; 59 | z-index: -1; 60 | -webkit-transition: width 0.5s ease-in-out; 61 | -moz-transition: width 0.5s ease-in-out; 62 | -ms-transition: width 0.5s ease-in-out; 63 | -o-transition: width 0.5s ease-in-out; 64 | transition: width 0.5s ease-in-out; 65 | } 66 | 67 | .list-group-item { 68 | color: black!important; 69 | } 70 | 71 | .list-group-item:hover .tweet-percentage { 72 | width: 100%!important; 73 | } 74 | 75 | .tweet-list { 76 | list-style-type: none; 77 | margin-left: -30px; 78 | } 79 | 80 | .tweet-list__tweet-body { 81 | position: relative; 82 | border-bottom: 1px solid #f5f5f5; 83 | margin-bottom: 10px; 84 | padding: 15px 0 35px 5px; 85 | } 86 | 87 | .tweet-list__tweet-body i { 88 | color: #c5c5c5; 89 | } 90 | 91 | .tweet-list__meta { 92 | position: absolute; 93 | bottom: 0; 94 | right: 0; 95 | color: #c5c5c5; 96 | font-size: small; 97 | text-align: right; 98 | } 99 | 100 | .tweet-list__meta a { 101 | color: #c5c5c5; 102 | } 103 | 104 | footer { 105 | text-align: right; 106 | margin-bottom: 20px; 107 | } 108 | 109 | /* Activity feed from http://bootsnipp.com/snippets/Zlkg2 */ 110 | 111 | .activity-feed { 112 | padding: 15px; 113 | } 114 | .activity-feed .feed-item { 115 | position: relative; 116 | padding-bottom: 20px; 117 | padding-left: 30px; 118 | border-left: 2px solid #e4e8eb; 119 | } 120 | .activity-feed .feed-item:last-child { 121 | border-color: transparent; 122 | } 123 | .activity-feed .feed-item:after { 124 | content: ""; 125 | display: block; 126 | position: absolute; 127 | top: 0; 128 | left: -6px; 129 | width: 10px; 130 | height: 10px; 131 | border-radius: 6px; 132 | background: #fff; 133 | border: 1px solid #f37167; 134 | } 135 | .activity-feed .feed-item .date { 136 | position: relative; 137 | top: -5px; 138 | color: #8c96a3; 139 | text-transform: uppercase; 140 | font-size: 13px; 141 | } 142 | .activity-feed .feed-item .text { 143 | position: relative; 144 | top: -3px; 145 | } 146 | -------------------------------------------------------------------------------- /public/assets/js/archivey.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rknightuk/one40/616d82c1fe3c97fca048ce0c85dc1f8a92289749/public/assets/js/archivey.js -------------------------------------------------------------------------------- /public/assets/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.4 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.4",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.4",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.4",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.4",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.4",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(this.options.viewport.selector||this.options.viewport),this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c&&c.$tip&&c.$tip.is(":visible")?void(c.hoverState="in"):(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.options.container?a(this.options.container):this.$element.parent(),p=this.getPosition(o);h="bottom"==h&&k.bottom+m>p.bottom?"top":"top"==h&&k.top-mp.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.width&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type)})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.4",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.4",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.4",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=a(document.body).height();"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rknightuk/one40/616d82c1fe3c97fca048ce0c85dc1f8a92289749/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 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # One40 — Self Hosted Twitter Archive 2 | 3 | A personal, searchable Twitter archive built with Laravel. 4 | 5 | Archived repo. Try [Tweetback](https://github.com/tweetback) or [https://tinysubversions.com/twitter-archive/make-your-own/](https://tinysubversions.com/twitter-archive/make-your-own/) 6 | -------------------------------------------------------------------------------- /resources/assets/js/app.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * First we will load all of this project's JavaScript dependencies which 4 | * include Vue and Vue Resource. This gives a great starting point for 5 | * building robust, powerful web applications using Vue and Laravel. 6 | */ 7 | 8 | require('./bootstrap'); 9 | 10 | /** 11 | * Next, we will create a fresh Vue application instance and attach it to 12 | * the page. Then, you may begin adding components to this application 13 | * or customize the JavaScript scaffolding to fit your unique needs. 14 | */ 15 | 16 | Vue.component('example', require('./components/Example.vue')); 17 | 18 | const app = new Vue({ 19 | el: '#app' 20 | }); 21 | -------------------------------------------------------------------------------- /resources/assets/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | 2 | window._ = require('lodash'); 3 | 4 | /** 5 | * We'll load jQuery and the Bootstrap jQuery plugin which provides support 6 | * for JavaScript based Bootstrap features such as modals and tabs. This 7 | * code may be modified to fit the specific needs of your application. 8 | */ 9 | 10 | window.$ = window.jQuery = require('jquery'); 11 | require('bootstrap-sass'); 12 | 13 | /** 14 | * Vue is a modern JavaScript library for building interactive web interfaces 15 | * using reactive data binding and reusable components. Vue's API is clean 16 | * and simple, leaving you to focus on building your next great project. 17 | */ 18 | 19 | window.Vue = require('vue'); 20 | require('vue-resource'); 21 | 22 | /** 23 | * We'll register a HTTP interceptor to attach the "CSRF" header to each of 24 | * the outgoing requests issued by this application. The CSRF middleware 25 | * included with Laravel will automatically verify the header's value. 26 | */ 27 | 28 | Vue.http.interceptors.push((request, next) => { 29 | request.headers.set('X-CSRF-TOKEN', Laravel.csrfToken); 30 | 31 | next(); 32 | }); 33 | 34 | /** 35 | * Echo exposes an expressive API for subscribing to channels and listening 36 | * for events that are broadcast by Laravel. Echo and event broadcasting 37 | * allows your team to easily build robust real-time web applications. 38 | */ 39 | 40 | // import Echo from "laravel-echo" 41 | 42 | // window.Echo = new Echo({ 43 | // broadcaster: 'pusher', 44 | // key: 'your-pusher-key' 45 | // }); 46 | -------------------------------------------------------------------------------- /resources/assets/js/components/Example.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 24 | -------------------------------------------------------------------------------- /resources/assets/sass/_variables.scss: -------------------------------------------------------------------------------- 1 | 2 | // Body 3 | $body-bg: #f5f8fa; 4 | 5 | // Borders 6 | $laravel-border-color: darken($body-bg, 10%); 7 | $list-group-border: $laravel-border-color; 8 | $navbar-default-border: $laravel-border-color; 9 | $panel-default-border: $laravel-border-color; 10 | $panel-inner-border: $laravel-border-color; 11 | 12 | // Brands 13 | $brand-primary: #3097D1; 14 | $brand-info: #8eb4cb; 15 | $brand-success: #2ab27b; 16 | $brand-warning: #cbb956; 17 | $brand-danger: #bf5329; 18 | 19 | // Typography 20 | $font-family-sans-serif: "Raleway", sans-serif; 21 | $font-size-base: 14px; 22 | $line-height-base: 1.6; 23 | $text-color: #636b6f; 24 | 25 | // Navbar 26 | $navbar-default-bg: #fff; 27 | 28 | // Buttons 29 | $btn-default-color: $text-color; 30 | 31 | // Inputs 32 | $input-border: lighten($text-color, 40%); 33 | $input-border-focus: lighten($brand-primary, 25%); 34 | $input-color-placeholder: lighten($text-color, 30%); 35 | 36 | // Panels 37 | $panel-default-heading-bg: #fff; 38 | -------------------------------------------------------------------------------- /resources/assets/sass/app.scss: -------------------------------------------------------------------------------- 1 | 2 | // Fonts 3 | @import url(https://fonts.googleapis.com/css?family=Raleway:300,400,600); 4 | 5 | // Variables 6 | @import "variables"; 7 | 8 | // Bootstrap 9 | @import "node_modules/bootstrap-sass/assets/stylesheets/bootstrap"; 10 | -------------------------------------------------------------------------------- /resources/lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'These credentials do not match our records.', 17 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 'next' => 'Next »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/lang/en/passwords.php: -------------------------------------------------------------------------------- 1 | 'Passwords must be at least six characters and match the confirmation.', 17 | 'reset' => 'Your password has been reset!', 18 | 'sent' => 'We have e-mailed your password reset link!', 19 | 'token' => 'This password reset token is invalid.', 20 | 'user' => "We can't find a user with that e-mail address.", 21 | 22 | ]; 23 | -------------------------------------------------------------------------------- /resources/lang/en/validation.php: -------------------------------------------------------------------------------- 1 | 'The :attribute must be accepted.', 17 | 'active_url' => 'The :attribute is not a valid URL.', 18 | 'after' => 'The :attribute must be a date after :date.', 19 | 'alpha' => 'The :attribute may only contain letters.', 20 | 'alpha_dash' => 'The :attribute may only contain letters, numbers, and dashes.', 21 | 'alpha_num' => 'The :attribute may only contain letters and numbers.', 22 | 'array' => 'The :attribute must be an array.', 23 | 'before' => 'The :attribute must be a date before :date.', 24 | 'between' => [ 25 | 'numeric' => 'The :attribute must be between :min and :max.', 26 | 'file' => 'The :attribute must be between :min and :max kilobytes.', 27 | 'string' => 'The :attribute must be between :min and :max characters.', 28 | 'array' => 'The :attribute must have between :min and :max items.', 29 | ], 30 | 'boolean' => 'The :attribute field must be true or false.', 31 | 'confirmed' => 'The :attribute confirmation does not match.', 32 | 'date' => 'The :attribute is not a valid date.', 33 | 'date_format' => 'The :attribute does not match the format :format.', 34 | 'different' => 'The :attribute and :other must be different.', 35 | 'digits' => 'The :attribute must be :digits digits.', 36 | 'digits_between' => 'The :attribute must be between :min and :max digits.', 37 | 'dimensions' => 'The :attribute has invalid image dimensions.', 38 | 'distinct' => 'The :attribute field has a duplicate value.', 39 | 'email' => 'The :attribute must be a valid email address.', 40 | 'exists' => 'The selected :attribute is invalid.', 41 | 'file' => 'The :attribute must be a file.', 42 | 'filled' => 'The :attribute field is required.', 43 | 'image' => 'The :attribute must be an image.', 44 | 'in' => 'The selected :attribute is invalid.', 45 | 'in_array' => 'The :attribute field does not exist in :other.', 46 | 'integer' => 'The :attribute must be an integer.', 47 | 'ip' => 'The :attribute must be a valid IP address.', 48 | 'json' => 'The :attribute must be a valid JSON string.', 49 | 'max' => [ 50 | 'numeric' => 'The :attribute may not be greater than :max.', 51 | 'file' => 'The :attribute may not be greater than :max kilobytes.', 52 | 'string' => 'The :attribute may not be greater than :max characters.', 53 | 'array' => 'The :attribute may not have more than :max items.', 54 | ], 55 | 'mimes' => 'The :attribute must be a file of type: :values.', 56 | 'mimetypes' => 'The :attribute must be a file of type: :values.', 57 | 'min' => [ 58 | 'numeric' => 'The :attribute must be at least :min.', 59 | 'file' => 'The :attribute must be at least :min kilobytes.', 60 | 'string' => 'The :attribute must be at least :min characters.', 61 | 'array' => 'The :attribute must have at least :min items.', 62 | ], 63 | 'not_in' => 'The selected :attribute is invalid.', 64 | 'numeric' => 'The :attribute must be a number.', 65 | 'present' => 'The :attribute field must be present.', 66 | 'regex' => 'The :attribute format is invalid.', 67 | 'required' => 'The :attribute field is required.', 68 | 'required_if' => 'The :attribute field is required when :other is :value.', 69 | 'required_unless' => 'The :attribute field is required unless :other is in :values.', 70 | 'required_with' => 'The :attribute field is required when :values is present.', 71 | 'required_with_all' => 'The :attribute field is required when :values is present.', 72 | 'required_without' => 'The :attribute field is required when :values is not present.', 73 | 'required_without_all' => 'The :attribute field is required when none of :values are present.', 74 | 'same' => 'The :attribute and :other must match.', 75 | 'size' => [ 76 | 'numeric' => 'The :attribute must be :size.', 77 | 'file' => 'The :attribute must be :size kilobytes.', 78 | 'string' => 'The :attribute must be :size characters.', 79 | 'array' => 'The :attribute must contain :size items.', 80 | ], 81 | 'string' => 'The :attribute must be a string.', 82 | 'timezone' => 'The :attribute must be a valid zone.', 83 | 'unique' => 'The :attribute has already been taken.', 84 | 'uploaded' => 'The :attribute failed to upload.', 85 | 'url' => 'The :attribute format is invalid.', 86 | 87 | /* 88 | |-------------------------------------------------------------------------- 89 | | Custom Validation Language Lines 90 | |-------------------------------------------------------------------------- 91 | | 92 | | Here you may specify custom validation messages for attributes using the 93 | | convention "attribute.rule" to name the lines. This makes it quick to 94 | | specify a specific custom language line for a given attribute rule. 95 | | 96 | */ 97 | 98 | 'custom' => [ 99 | 'attribute-name' => [ 100 | 'rule-name' => 'custom-message', 101 | ], 102 | ], 103 | 104 | /* 105 | |-------------------------------------------------------------------------- 106 | | Custom Validation Attributes 107 | |-------------------------------------------------------------------------- 108 | | 109 | | The following language lines are used to swap attribute place-holders 110 | | with something more reader friendly such as E-Mail Address instead 111 | | of "email". This simply helps us make messages a little cleaner. 112 | | 113 | */ 114 | 115 | 'attributes' => [], 116 | 117 | ]; 118 | -------------------------------------------------------------------------------- /resources/views/admin/logs.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.main') 2 | 3 | @section('content') 4 | 5 |
6 | 7 |
8 | 9 |

Tweet Fetch Log

10 | 11 |
12 | {{ csrf_field() }} 13 | 14 | 15 |
16 | 17 |
18 | @forelse ($logs as $log) 19 |
20 | @if ($log->count) 21 |
{!! $log->created_at !!}
22 |
Imported {!! $log->count !!} @if ($log->count == 1) tweet @else tweets @endif
23 | @else 24 |
{!! $log->created_at !!} - No tweets found
25 | @endif 26 |
27 | @empty 28 | NO LOGS 29 | @endforelse 30 |
31 | 32 | {!! $logs->render() !!} 33 | 34 |
35 | 36 |
37 | 38 |
39 | 40 | @endsection -------------------------------------------------------------------------------- /resources/views/admin/stats.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.main') 2 | 3 | @section('content') 4 | 5 |
6 | 7 |
8 | 9 |

Tweet Stats

10 | 11 | Total of {{ $totals['all'] }} tweets, with an average of {{ $average['average'] }} tweets per day since {{ $average['first']->format('jS F Y') }}, {{ $average['daysSince'] }} days ago. 12 | 13 | You've replied {{ $totals['reply'] }} times, and retweeted {{ $totals['retweet'] }} tweets. 14 | 15 |

Types

16 | 17 |
    18 | @forelse ($totals as $key => $type) 19 |
  • {!! $type !!} {!! $key !!}
  • 20 | @empty 21 | NO STATS FOUND 22 | @endforelse 23 |
24 | 25 |

Top Clients

26 | 27 |
    28 | @forelse ($clients as $client) 29 |
  • {!! $client['count'] !!} tweets - {!! $client['source'] !!}
  • 30 | @empty 31 | NO STATS FOUND 32 | @endforelse 33 |
34 | 35 |
36 | 37 |
38 | 39 |
40 | 41 | @endsection -------------------------------------------------------------------------------- /resources/views/auth/login.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.main') 2 | 3 | @section('content') 4 | 5 |
6 | {!! csrf_field() !!} 7 | 8 |
9 | 10 | 11 |
12 |
13 | 14 | 15 |
16 |
17 | 20 |
21 | 22 |
23 | 24 | @endsection -------------------------------------------------------------------------------- /resources/views/auth/passwords/email.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | 4 | @section('content') 5 |
6 |
7 |
8 |
9 |
Reset Password
10 |
11 | @if (session('status')) 12 |
13 | {{ session('status') }} 14 |
15 | @endif 16 | 17 |
18 | {{ csrf_field() }} 19 | 20 |
21 | 22 | 23 |
24 | 25 | 26 | @if ($errors->has('email')) 27 | 28 | {{ $errors->first('email') }} 29 | 30 | @endif 31 |
32 |
33 | 34 |
35 |
36 | 39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | @endsection 48 | -------------------------------------------------------------------------------- /resources/views/auth/passwords/reset.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
Reset Password
9 | 10 |
11 |
12 | {{ csrf_field() }} 13 | 14 | 15 | 16 |
17 | 18 | 19 |
20 | 21 | 22 | @if ($errors->has('email')) 23 | 24 | {{ $errors->first('email') }} 25 | 26 | @endif 27 |
28 |
29 | 30 |
31 | 32 | 33 |
34 | 35 | 36 | @if ($errors->has('password')) 37 | 38 | {{ $errors->first('password') }} 39 | 40 | @endif 41 |
42 |
43 | 44 |
45 | 46 |
47 | 48 | 49 | @if ($errors->has('password_confirmation')) 50 | 51 | {{ $errors->first('password_confirmation') }} 52 | 53 | @endif 54 |
55 |
56 | 57 |
58 |
59 | 62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | @endsection 71 | -------------------------------------------------------------------------------- /resources/views/auth/register.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
Register
9 |
10 |
11 | {{ csrf_field() }} 12 | 13 |
14 | 15 | 16 |
17 | 18 | 19 | @if ($errors->has('name')) 20 | 21 | {{ $errors->first('name') }} 22 | 23 | @endif 24 |
25 |
26 | 27 |
28 | 29 | 30 |
31 | 32 | 33 | @if ($errors->has('email')) 34 | 35 | {{ $errors->first('email') }} 36 | 37 | @endif 38 |
39 |
40 | 41 |
42 | 43 | 44 |
45 | 46 | 47 | @if ($errors->has('password')) 48 | 49 | {{ $errors->first('password') }} 50 | 51 | @endif 52 |
53 |
54 | 55 |
56 | 57 | 58 |
59 | 60 | 61 | @if ($errors->has('password_confirmation')) 62 | 63 | {{ $errors->first('password_confirmation') }} 64 | 65 | @endif 66 |
67 |
68 | 69 |
70 |
71 | 74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | @endsection 83 | -------------------------------------------------------------------------------- /resources/views/errors/404.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Page not found 5 | 6 | 7 | 8 | 39 | 40 | 41 |
42 |
43 |
Page not found.
44 |
45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /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/layouts/main.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | One40 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | @include('partials._nav') 21 | 22 |
23 | 24 | @include('partials._flash') 25 | 26 | @include('partials._errors') 27 | 28 | @yield('content') 29 | 30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /resources/views/partials/_errors.blade.php: -------------------------------------------------------------------------------- 1 | @if ($errors->any()) 2 |
    3 | @foreach ($errors->all() as $error) 4 |
  • {!! $error !!}
  • 5 | @endforeach 6 |
7 | @endif -------------------------------------------------------------------------------- /resources/views/partials/_flash.blade.php: -------------------------------------------------------------------------------- 1 | @if (Session::has('flash_notification.message')) 2 |
3 | 4 | 5 | {!! Session::get('flash_notification.message') !!} 6 |
7 | @endif -------------------------------------------------------------------------------- /resources/views/partials/_nav.blade.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/views/tweets/index.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.main') 2 | 3 | @section('content') 4 | 5 |
6 | 7 |
8 | 9 | {!! Breadcrumbs::render() !!} 10 | 11 | @include('tweets.partials._tweets') 12 | 13 |
14 | 15 |
16 | 21 | 22 | @include('tweets.partials._counts') 23 | 24 |
25 | 26 |
27 | 28 | 31 | 32 | @endsection -------------------------------------------------------------------------------- /resources/views/tweets/partials/_counts.blade.php: -------------------------------------------------------------------------------- 1 | @if (isset($monthCounts) && $monthCounts) 2 | 3 | 4 | 5 | @foreach ($monthCounts['tweet_count'] as $month) 6 | 7 | @if ( ! in_array($month->year, $years)) 8 | 9 | year; ?> 10 | @if (count($years)) 11 | 12 | @endif 13 | 14 |
    15 | 16 | 17 | {!!$monthCounts['year_counts'][$month->year]!!} 18 | {!! $month->year !!} 19 | 20 | 21 | @endif 22 | 23 | 24 | {!!$month->count!!} 25 | {!! displayMonth($month->month) !!} {!! $month->year !!} 26 |
    27 |
    28 | 29 | @endforeach 30 | 31 | @endif 32 | 33 | @if (isset($dayCounts) && $dayCounts) 34 | 35 | 46 | 47 | @endif -------------------------------------------------------------------------------- /resources/views/tweets/partials/_tweets.blade.php: -------------------------------------------------------------------------------- 1 |
      2 | 3 | @forelse($tweets as $tweet) 4 | 5 |
      6 |
      7 |

      {!! nl2br(html_entity_decode($tweet->present()->tweet)) !!}

      8 |

      9 | {!! $tweet->present()->metadata !!} 10 |
      11 | {!! link_to_route('tweet.single', 'permalink', ['id' => $tweet->tweetid]) !!} 12 |

      13 |
      14 | 15 | @if ($tweet->photos()) 16 | @foreach ($tweet->photos() as $photo) 17 | 18 | 19 | 20 | 21 | 22 | @endforeach 23 | @endif 24 | 25 |
      26 | 27 | @empty 28 | 29 |

      No tweets found

      30 | 31 | @endforelse 32 | 33 |
        34 | 35 | @if ($tweets && ! isset($single)) 36 |
        37 | {!! $tweets->render() !!} 38 |
        39 | @endif -------------------------------------------------------------------------------- /resources/views/vendor/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/views/vendor/notifications/email-plain.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 16 | 17 | 18 | 'margin: 0; padding: 0; width: 100%; background-color: #F2F4F6;', 24 | 'email-wrapper' => 'width: 100%; margin: 0; padding: 0; background-color: #F2F4F6;', 25 | 26 | /* Masthead ----------------------- */ 27 | 28 | 'email-masthead' => 'padding: 25px 0; text-align: center;', 29 | 'email-masthead_name' => 'font-size: 16px; font-weight: bold; color: #2F3133; text-decoration: none; text-shadow: 0 1px 0 white;', 30 | 31 | 'email-body' => 'width: 100%; margin: 0; padding: 0; border-top: 1px solid #EDEFF2; border-bottom: 1px solid #EDEFF2; background-color: #FFF;', 32 | 'email-body_inner' => 'width: auto; max-width: 570px; margin: 0 auto; padding: 0;', 33 | 'email-body_cell' => 'padding: 35px;', 34 | 35 | 'email-footer' => 'width: auto; max-width: 570px; margin: 0 auto; padding: 0; text-align: center;', 36 | 'email-footer_cell' => 'color: #AEAEAE; padding: 35px; text-align: center;', 37 | 38 | /* Body ------------------------------ */ 39 | 40 | 'body_action' => 'width: 100%; margin: 30px auto; padding: 0; text-align: center;', 41 | 'body_sub' => 'margin-top: 25px; padding-top: 25px; border-top: 1px solid #EDEFF2;', 42 | 43 | /* Type ------------------------------ */ 44 | 45 | 'anchor' => 'color: #3869D4;', 46 | 'header-1' => 'margin-top: 0; color: #2F3133; font-size: 19px; font-weight: bold; text-align: left;', 47 | 'paragraph' => 'margin-top: 0; color: #74787E; font-size: 16px; line-height: 1.5em;', 48 | 'paragraph-sub' => 'margin-top: 0; color: #74787E; font-size: 12px; line-height: 1.5em;', 49 | 'paragraph-center' => 'text-align: center;', 50 | 51 | /* Buttons ------------------------------ */ 52 | 53 | 'button' => 'display: block; display: inline-block; width: 200px; min-height: 20px; padding: 10px; 54 | background-color: #3869D4; border-radius: 3px; color: #ffffff; font-size: 15px; line-height: 25px; 55 | text-align: center; text-decoration: none; -webkit-text-size-adjust: none;', 56 | 57 | 'button--green' => 'background-color: #22BC66;', 58 | 'button--red' => 'background-color: #dc4d2f;', 59 | 'button--blue' => 'background-color: #3869D4;', 60 | ]; 61 | ?> 62 | 63 | 64 | 65 | 66 | 67 | 68 | 189 | 190 |
        69 | 70 | 71 | 72 | 77 | 78 | 79 | 80 | 81 | 169 | 170 | 171 | 172 | 173 | 186 | 187 |
        73 | 74 | {{ config('app.name') }} 75 | 76 |
        82 | 83 | 84 | 166 | 167 |
        85 | 86 |

        87 | @if (! empty($greeting)) 88 | {{ $greeting }} 89 | @else 90 | @if ($level == 'error') 91 | Whoops! 92 | @else 93 | Hello! 94 | @endif 95 | @endif 96 |

        97 | 98 | 99 | @foreach ($introLines as $line) 100 |

        101 | {{ $line }} 102 |

        103 | @endforeach 104 | 105 | 106 | @if (isset($actionText)) 107 | 108 | 109 | 130 | 131 |
        110 | 122 | 123 | 127 | {{ $actionText }} 128 | 129 |
        132 | @endif 133 | 134 | 135 | @foreach ($outroLines as $line) 136 |

        137 | {{ $line }} 138 |

        139 | @endforeach 140 | 141 | 142 |

        143 | Regards,
        {{ config('app.name') }} 144 |

        145 | 146 | 147 | @if (isset($actionText)) 148 | 149 | 150 | 162 | 163 |
        151 |

        152 | If you’re having trouble clicking the "{{ $actionText }}" button, 153 | copy and paste the URL below into your web browser: 154 |

        155 | 156 |

        157 | 158 | {{ $actionUrl }} 159 | 160 |

        161 |
        164 | @endif 165 |
        168 |
        174 | 175 | 176 | 183 | 184 |
        177 |

        178 | © {{ date('Y') }} 179 | {{ config('app.name') }}. 180 | All rights reserved. 181 |

        182 |
        185 |
        188 |
        191 | 192 | 193 | -------------------------------------------------------------------------------- /resources/views/vendor/pagination/bootstrap-4.blade.php: -------------------------------------------------------------------------------- 1 | @if ($paginator->hasPages()) 2 |
          3 | {{-- Previous Page Link --}} 4 | @if ($paginator->onFirstPage()) 5 |
        • «
        • 6 | @else 7 |
        • 8 | @endif 9 | 10 | {{-- Pagination Elements --}} 11 | @foreach ($elements as $element) 12 | {{-- "Three Dots" Separator --}} 13 | @if (is_string($element)) 14 |
        • {{ $element }}
        • 15 | @endif 16 | 17 | {{-- Array Of Links --}} 18 | @if (is_array($element)) 19 | @foreach ($element as $page => $url) 20 | @if ($page == $paginator->currentPage()) 21 |
        • {{ $page }}
        • 22 | @else 23 |
        • {{ $page }}
        • 24 | @endif 25 | @endforeach 26 | @endif 27 | @endforeach 28 | 29 | {{-- Next Page Link --}} 30 | @if ($paginator->hasMorePages()) 31 |
        • 32 | @else 33 |
        • »
        • 34 | @endif 35 |
        36 | @endif 37 | -------------------------------------------------------------------------------- /resources/views/vendor/pagination/default.blade.php: -------------------------------------------------------------------------------- 1 | @if ($paginator->hasPages()) 2 |
          3 | {{-- Previous Page Link --}} 4 | @if ($paginator->onFirstPage()) 5 |
        • «
        • 6 | @else 7 |
        • 8 | @endif 9 | 10 | {{-- Pagination Elements --}} 11 | @foreach ($elements as $element) 12 | {{-- "Three Dots" Separator --}} 13 | @if (is_string($element)) 14 |
        • {{ $element }}
        • 15 | @endif 16 | 17 | {{-- Array Of Links --}} 18 | @if (is_array($element)) 19 | @foreach ($element as $page => $url) 20 | @if ($page == $paginator->currentPage()) 21 |
        • {{ $page }}
        • 22 | @else 23 |
        • {{ $page }}
        • 24 | @endif 25 | @endforeach 26 | @endif 27 | @endforeach 28 | 29 | {{-- Next Page Link --}} 30 | @if ($paginator->hasMorePages()) 31 |
        • 32 | @else 33 |
        • »
        • 34 | @endif 35 |
        36 | @endif 37 | -------------------------------------------------------------------------------- /resources/views/vendor/pagination/simple-bootstrap-4.blade.php: -------------------------------------------------------------------------------- 1 | @if ($paginator->hasPages()) 2 |
          3 | {{-- Previous Page Link --}} 4 | @if ($paginator->onFirstPage()) 5 |
        • «
        • 6 | @else 7 |
        • 8 | @endif 9 | 10 | {{-- Next Page Link --}} 11 | @if ($paginator->hasMorePages()) 12 |
        • 13 | @else 14 |
        • »
        • 15 | @endif 16 |
        17 | @endif 18 | -------------------------------------------------------------------------------- /resources/views/vendor/pagination/simple-default.blade.php: -------------------------------------------------------------------------------- 1 | @if ($paginator->hasPages()) 2 |
          3 | {{-- Previous Page Link --}} 4 | @if ($paginator->onFirstPage()) 5 |
        • «
        • 6 | @else 7 |
        • 8 | @endif 9 | 10 | {{-- Next Page Link --}} 11 | @if ($paginator->hasMorePages()) 12 |
        • 13 | @else 14 |
        • »
        • 15 | @endif 16 |
        17 | @endif 18 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | user(); 18 | })->middleware('auth:api'); 19 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 18 | })->describe('Display an inspiring quote'); 19 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | ['auth']], function() { 10 | Route::get('logs', 'LogController@index'); 11 | Route::post('logs/purge', 'LogController@purge'); 12 | Route::get('stats', 'AdminController@stats'); 13 | }); 14 | 15 | Route::group(['middleware' => ['private']], function() { 16 | Route::get('/home', function() { 17 | return redirect('/'); 18 | }); 19 | Route::get('/', 'TweetController@index'); 20 | 21 | Route::get('/tweet/{id}', [ 22 | 'as' => 'tweet.single', 23 | 'uses' => 'TweetController@show' 24 | ]); 25 | 26 | Route::get('/tweet', function() { return redirect('/random'); }); 27 | Route::get('/random', 'TweetController@random'); 28 | 29 | Route::get('{year}/{month?}/{date?}', 'TweetController@index')->where([ 30 | 'year' => '[0-9]+', 31 | 'month' => '[0-9]+', 32 | 'date' => '[0-9]+' 33 | ]); 34 | 35 | Route::get('/search', function() { return redirect('/'); }); 36 | Route::post('/search', 'TweetController@search'); 37 | 38 | Route::get('/search/{search?}/{year?}/{month?}/{day?}', [ 39 | 'as' => 'search', 40 | 'uses' => 'TweetController@searchResults' 41 | ]); 42 | }); 43 | 44 | App::bind(\App\Breadcrumbs\BreadcrumbInterface::class, \App\Breadcrumbs\CreitiveBreadcrumb::class); -------------------------------------------------------------------------------- /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 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | config.php 2 | routes.php 3 | schedule-* 4 | compiled.php 5 | services.json 6 | events.scanned.php 7 | routes.scanned.php 8 | down 9 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tests/ExampleTest.php: -------------------------------------------------------------------------------- 1 | visit('/') 17 | ->see('Laravel'); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | make(Illuminate\Contracts\Console\Kernel::class)->bootstrap(); 22 | 23 | return $app; 24 | } 25 | } 26 | --------------------------------------------------------------------------------