From 34ee983becfa088d0c39c4f0d9b35f038b351a78 Mon Sep 17 00:00:00 2001 From: Developer Date: Sat, 27 Jun 2026 02:47:40 +0200 Subject: feat: add cinema module for browsing movies from hncrawler DB - Add 'cinema' database connection (hncrawler DB) to config/database.php - Create CinemaController with cinema(), show(), search() methods - Add /cinema, /cinema/movie/{imdb_id}, /cinema/search routes - Create cinema.blade.php (grid listing with posters, ratings, pagination) - Create cinema-show.blade.php (detail page with cast, genres, synopsis) - Add Cinema link to navbar - Update page title logic for cinema routes --- app/Article.php | 23 ++ app/Category.php | 18 ++ app/Console/Commands/GenerateSitemap.php | 52 ++++ app/Console/Kernel.php | 41 +++ app/Discussion.php | 24 ++ app/Exceptions/Handler.php | 55 ++++ .../Controllers/Auth/ConfirmPasswordController.php | 40 +++ .../Controllers/Auth/ForgotPasswordController.php | 22 ++ app/Http/Controllers/Auth/LoginController.php | 40 +++ app/Http/Controllers/Auth/RegisterController.php | 73 +++++ .../Controllers/Auth/ResetPasswordController.php | 30 ++ .../Controllers/Auth/VerificationController.php | 42 +++ app/Http/Controllers/CinemaController.php | 114 ++++++++ app/Http/Controllers/Controller.php | 13 + app/Http/Controllers/FeedController.php | 97 +++++++ app/Http/Controllers/HomeController.php | 28 ++ app/Http/Controllers/IndexController.php | 150 ++++++++++ app/Http/Kernel.php | 66 +++++ app/Http/Middleware/Authenticate.php | 21 ++ app/Http/Middleware/CheckForMaintenanceMode.php | 17 ++ app/Http/Middleware/EncryptCookies.php | 17 ++ app/Http/Middleware/RedirectIfAuthenticated.php | 27 ++ app/Http/Middleware/TrimStrings.php | 18 ++ app/Http/Middleware/TrustProxies.php | 29 ++ app/Http/Middleware/VerifyCsrfToken.php | 17 ++ app/Libraries/Helper.php | 303 +++++++++++++++++++++ app/Providers/AppServiceProvider.php | 29 ++ app/Providers/AuthServiceProvider.php | 30 ++ app/Providers/BroadcastServiceProvider.php | 21 ++ app/Providers/EventServiceProvider.php | 34 +++ app/Providers/RouteServiceProvider.php | 80 ++++++ app/User.php | 39 +++ 32 files changed, 1610 insertions(+) create mode 100644 app/Article.php create mode 100644 app/Category.php create mode 100644 app/Console/Commands/GenerateSitemap.php create mode 100644 app/Console/Kernel.php create mode 100644 app/Discussion.php create mode 100644 app/Exceptions/Handler.php create mode 100644 app/Http/Controllers/Auth/ConfirmPasswordController.php create mode 100644 app/Http/Controllers/Auth/ForgotPasswordController.php create mode 100644 app/Http/Controllers/Auth/LoginController.php create mode 100644 app/Http/Controllers/Auth/RegisterController.php create mode 100644 app/Http/Controllers/Auth/ResetPasswordController.php create mode 100644 app/Http/Controllers/Auth/VerificationController.php create mode 100644 app/Http/Controllers/CinemaController.php create mode 100644 app/Http/Controllers/Controller.php create mode 100644 app/Http/Controllers/FeedController.php create mode 100644 app/Http/Controllers/HomeController.php create mode 100644 app/Http/Controllers/IndexController.php create mode 100644 app/Http/Kernel.php create mode 100644 app/Http/Middleware/Authenticate.php create mode 100644 app/Http/Middleware/CheckForMaintenanceMode.php create mode 100644 app/Http/Middleware/EncryptCookies.php create mode 100644 app/Http/Middleware/RedirectIfAuthenticated.php create mode 100644 app/Http/Middleware/TrimStrings.php create mode 100644 app/Http/Middleware/TrustProxies.php create mode 100644 app/Http/Middleware/VerifyCsrfToken.php create mode 100644 app/Libraries/Helper.php create mode 100644 app/Providers/AppServiceProvider.php create mode 100644 app/Providers/AuthServiceProvider.php create mode 100644 app/Providers/BroadcastServiceProvider.php create mode 100644 app/Providers/EventServiceProvider.php create mode 100644 app/Providers/RouteServiceProvider.php create mode 100644 app/User.php (limited to 'app') diff --git a/app/Article.php b/app/Article.php new file mode 100644 index 0000000..9f11ea3 --- /dev/null +++ b/app/Article.php @@ -0,0 +1,23 @@ +hasMany('App\Discussion', 'article_id', 'id'); + return $this->hasMany('App\Discussion'); + } + + public function getCategories() { + return $this->belongsToMany('App\Category', 'article_category'); + } +} + diff --git a/app/Category.php b/app/Category.php new file mode 100644 index 0000000..7f0cdb2 --- /dev/null +++ b/app/Category.php @@ -0,0 +1,18 @@ +belongsToMany('App\Article', 'article_category'); + } +} diff --git a/app/Console/Commands/GenerateSitemap.php b/app/Console/Commands/GenerateSitemap.php new file mode 100644 index 0000000..46bb0ac --- /dev/null +++ b/app/Console/Commands/GenerateSitemap.php @@ -0,0 +1,52 @@ +shouldCrawl(function (UriInterface $url) { + /** + * Prevent the crawler from crawling the random pages. + */ + return strpos($url->getPath(), '/random') === false; + }) + ->writeToFile(public_path('sitemap.xml')); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php new file mode 100644 index 0000000..69914e9 --- /dev/null +++ b/app/Console/Kernel.php @@ -0,0 +1,41 @@ +command('inspire')->hourly(); + } + + /** + * Register the commands for the application. + * + * @return void + */ + protected function commands() + { + $this->load(__DIR__.'/Commands'); + + require base_path('routes/console.php'); + } +} diff --git a/app/Discussion.php b/app/Discussion.php new file mode 100644 index 0000000..facb847 --- /dev/null +++ b/app/Discussion.php @@ -0,0 +1,24 @@ +belongsTo('App\Article'); + } +} diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php new file mode 100644 index 0000000..59c585d --- /dev/null +++ b/app/Exceptions/Handler.php @@ -0,0 +1,55 @@ +middleware('auth'); + } +} diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php new file mode 100644 index 0000000..465c39c --- /dev/null +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -0,0 +1,22 @@ +middleware('guest')->except('logout'); + } +} diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php new file mode 100644 index 0000000..c6a6de6 --- /dev/null +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -0,0 +1,73 @@ +middleware('guest'); + } + + /** + * Get a validator for an incoming registration request. + * + * @param array $data + * @return \Illuminate\Contracts\Validation\Validator + */ + protected function validator(array $data) + { + return Validator::make($data, [ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], + 'password' => ['required', 'string', 'min:8', 'confirmed'], + ]); + } + + /** + * Create a new user instance after a valid registration. + * + * @param array $data + * @return \App\User + */ + protected function create(array $data) + { + return User::create([ + 'name' => $data['name'], + 'email' => $data['email'], + 'password' => Hash::make($data['password']), + ]); + } +} diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php new file mode 100644 index 0000000..b1726a3 --- /dev/null +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -0,0 +1,30 @@ +middleware('auth'); + $this->middleware('signed')->only('verify'); + $this->middleware('throttle:6,1')->only('verify', 'resend'); + } +} diff --git a/app/Http/Controllers/CinemaController.php b/app/Http/Controllers/CinemaController.php new file mode 100644 index 0000000..c1cb215 --- /dev/null +++ b/app/Http/Controllers/CinemaController.php @@ -0,0 +1,114 @@ +table('imdb') + ->select( + 'imdb.id', + 'imdb.imdb_id', + 'imdb.primary_title', + 'imdb.original_title', + 'imdb.start_year', + 'imdb.average_rating', + 'imdb.num_votes', + 'imdb.synopsis', + 'imdb.poster_url', + 'imdb.title_type', + 'imdb.runtime_minutes', + 'imdb.has_people' + ) + ->orderBy('imdb.average_rating', 'desc') + ->orderBy('imdb.num_votes', 'desc') + ->whereNotNull('imdb.primary_title') + ->where('imdb.title_type', 'movie') + ->simplePaginate(12); + + $total = DB::connection('cinema') + ->table('imdb') + ->whereNotNull('primary_title') + ->where('title_type', 'movie') + ->count(); + + return view('cinema', ['movies' => $movies, 'count' => $total]); + } + + public function show($id) + { + $movie = DB::connection('cinema') + ->table('imdb') + ->where('imdb.imdb_id', $id) + ->first(); + + if (! $movie) { + abort(404); + } + + $genres = DB::connection('cinema') + ->table('imdb_genre') + ->join('genre', 'genre.id', '=', 'imdb_genre.genre_id') + ->where('imdb_genre.imdb_id', $movie->id) + ->pluck('genre.name'); + + $casts = DB::connection('cinema') + ->table('who') + ->join('people', 'people.id', '=', 'who.people_id') + ->join('profession', 'profession.id', '=', 'who.profession_id') + ->where('who.imdb_id', $movie->id) + ->select('people.name', 'profession.name as profession') + ->get(); + + $actors = $casts->where('profession', 'actor')->take(8); + $directors = $casts->where('profession', 'director'); + $screenwriters = $casts->where('profession', 'screenwriter')->take(3); + + return view('cinema_show', [ + 'movie' => $movie, + 'genres' => $genres, + 'actors' => $actors, + 'directors' => $directors, + 'screenwriters' => $screenwriters, + ]); + } + + public function search(Request $request) + { + $query = $request->input('q', ''); + + $movies = DB::connection('cinema') + ->table('imdb') + ->select( + 'imdb.id', + 'imdb.imdb_id', + 'imdb.primary_title', + 'imdb.original_title', + 'imdb.start_year', + 'imdb.average_rating', + 'imdb.num_votes', + 'imdb.synopsis', + 'imdb.poster_url', + 'imdb.title_type', + 'imdb.runtime_minutes' + ) + ->whereNotNull('imdb.primary_title') + ->where('imdb.title_type', 'movie') + ->where(function ($q) use ($query) { + $q->where('imdb.primary_title', 'like', '%' . $query . '%') + ->orWhere('imdb.original_title', 'like', '%' . $query . '%') + ->orWhere('imdb.synopsis', 'like', '%' . $query . '%'); + }) + ->orderBy('imdb.average_rating', 'desc') + ->simplePaginate(12); + + $total = $movies->total(); + + return view('cinema', ['movies' => $movies, 'count' => $total, 'search_query' => $query]); + } +} diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php new file mode 100644 index 0000000..a0a2a8a --- /dev/null +++ b/app/Http/Controllers/Controller.php @@ -0,0 +1,13 @@ +select('view_article.id', 'view_article.article_id', 'view_article.url', 'view_article.title', 'view_article.excerpt_html', 'view_article.impact', 'view_article.upvotes', 'view_article.comments', 'view_article.repost') + ->groupBy('view_article.id', 'view_article.article_id', 'view_article.url', 'view_article.title', 'view_article.excerpt_html', 'view_article.impact', 'view_article.upvotes', 'view_article.comments', 'view_article.repost') + ->where('view_article.created_at', '<', now()->subMinutes(30)) + ->orderByRaw('MAX(discussion.posted_on) DESC') + ->take(20) + ->get(); + + return Helper::makeFeed($articles, "new"); + } + + public function mastodon() + { + $articles = Article::join('discussion', 'discussion.article_id', '=', 'view_article.id') + ->select('view_article.id', 'view_article.article_id', 'view_article.url', 'view_article.title', 'view_article.excerpt_html', 'view_article.impact', 'view_article.upvotes', 'view_article.comments', 'view_article.repost') + ->groupBy('view_article.id', 'view_article.article_id', 'view_article.url', 'view_article.title', 'view_article.excerpt_html', 'view_article.impact', 'view_article.upvotes', 'view_article.comments', 'view_article.repost') + ->where('view_article.created_at', '<', now()->subMinutes(30)) + ->orderByRaw('MAX(discussion.posted_on) DESC') + ->take(20) + ->get(); + return Helper::makeFeed($articles, "mastodon"); + } + public function mastodon_test() + { + $articles = Article::join('discussion', 'discussion.article_id', '=', 'view_article.id') + ->select('view_article.id', 'view_article.article_id', 'view_article.url', 'view_article.title', 'view_article.excerpt_html', 'view_article.impact', 'view_article.upvotes', 'view_article.comments', 'view_article.repost') + ->groupBy('view_article.id', 'view_article.article_id', 'view_article.url', 'view_article.title', 'view_article.excerpt_html', 'view_article.impact', 'view_article.upvotes', 'view_article.comments', 'view_article.repost') + ->where('view_article.created_at', '<', now()->subMinutes(30)) + ->orderByRaw('MAX(discussion.posted_on) DESC') + ->take(20) + ->get(); + return Helper::makeFeed($articles, "mastodon_test"); + } + + public function popular() + { + + $articles = new Article; + $articles = $articles->setTable('view_popular'); + $articles = $articles->take(20)->get(); + return Helper::makeFeed($articles, "popular"); + } + + public function search(Request $request) { + $search_unsafe = $request->input("q"); + + if ( "" == $search_unsafe ) { + $search_unsafe = ""; + } + + $search_unsafe = explode(",", $request->input("q")); + + $articles = new Article; + + if ( "on" == $request->input("onlypopular") ) { + $articles = $articles->setTable('view_popular'); + } + + foreach($search_unsafe as $q) { + $q = Helper::escapeLike($q); + $q = "%".$q."%"; + $articles = $articles->where(function ($query) use ($q) { + $query->whereHas('getCategories', function ($query) use ($q){ + $query->where('name', 'like', $q); + }) + ->orWhere('title', 'like', $q) + ->orWhere('url', 'like', $q) + ->orWhere('excerpt_html', 'like', $q); + }); + } + $count = $articles->count(); + + if ( "on" == $request->input("onlypopular") ) { + $articles = $articles->orderBy('impact', 'desc'); + } + $articles = $articles->orderBy('created_at', 'desc'); + + return Helper::makeFeed($articles->take(20)->get(), $request->input("q")); + } +} diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php new file mode 100644 index 0000000..7cbc2c3 --- /dev/null +++ b/app/Http/Controllers/HomeController.php @@ -0,0 +1,28 @@ +middleware('auth'); + } + + /** + * Show the application dashboard. + * + * @return \Illuminate\Contracts\Support\Renderable + */ + public function index() + { + return view('home'); + } +} diff --git a/app/Http/Controllers/IndexController.php b/app/Http/Controllers/IndexController.php new file mode 100644 index 0000000..7e7b31b --- /dev/null +++ b/app/Http/Controllers/IndexController.php @@ -0,0 +1,150 @@ + $count_articles->count, + "count_discussions" => $count_discussions->count, + "count_comments" => number_format($count_comments->count), + "count_upvotes" => number_format($count_upvotes->count)]); + } + + public function topic( $topic ) + { + $articles = new Category; + $articles = $articles->orWhere('name', $topic); + $articles = $articles->get()->first(); + if ( is_null($articles) ) { + abort(404); + } + $articles = $articles->getArticles(); + $articles = $articles->orderBy('impact', 'desc'); + $count = $articles->count(); + $articles = $articles->simplePaginate(10); + + return view('list', ["articles" => $articles, "count" => $count]); + } + + public function new() + { + $articles = Article::join('discussion', 'discussion.article_id', '=', 'view_article.id') + ->select('view_article.id', 'view_article.article_id', 'view_article.url', 'view_article.title', 'view_article.excerpt_html', 'view_article.impact', 'view_article.upvotes', 'view_article.comments', 'view_article.repost') + ->groupBy('view_article.id', 'view_article.article_id', 'view_article.url', 'view_article.title', 'view_article.excerpt_html', 'view_article.impact', 'view_article.upvotes', 'view_article.comments', 'view_article.repost') + ->orderByRaw('MAX(discussion.posted_on) DESC'); + + $count = $articles->count(); + $articles = $articles->simplePaginate(10); + + return view('list', ["articles" => $articles, "count" => $count]); + } + + public function show( $id ) { + $articles = Article::where('id', $id); + $articles = $articles->simplePaginate(10); + $page_title = ""; + foreach($articles as $a) { + $page_title = $a->title; + } + return view('list', ["articles" => $articles, "count" => 1, "page_title" => $page_title]); + } + + public function search(Request $request) { + $search_unsafe = $request->input("q"); + + if ( "" == $search_unsafe ) { + $search_unsafe = ""; + } + + $search_unsafe = explode(",", $request->input("q")); + + $articles = new Article; + + if ( "on" == $request->input("onlypopular") ) { + $articles = $articles->setTable('view_popular'); + } + + foreach($search_unsafe as $q) { + $q = Helper::escapeLike($q); + $q = "%".$q."%"; + $articles = $articles->where(function ($query) use ($q) { + $query->whereHas('getCategories', function ($query) use ($q){ + $query->where('name', 'like', $q); + }) + ->orWhere('title', 'like', $q) + ->orWhere('excerpt_html', 'like', $q); + }); + } + $count = $articles->count(); + + if ( "on" == $request->input("onlypopular") ) { + $articles = $articles->orderBy('impact', 'desc'); + } + $articles = $articles->orderBy('created_at', 'desc'); + $articles = $articles->simplePaginate(10); + + return view('list', ["articles" => $articles, "count" => $count]); + } + + public function popular() + { + $articles = new Article; + $articles = $articles->setTable('view_popular'); + $count = $articles->count(); + $articles = $articles->simplePaginate(10); + + return view('list', ["articles" => $articles, "count" => $count]); + } + + function topicindex() { + $categories = Category::orderBy('name'); + $letters = DB::select("SELECT DISTINCT LEFT(name, 1) as name FROM category ORDER BY left(name, 1);"); + + return view('topicindex', ['topics' => $categories, 'letters' => $letters]); + } + + public function random() { + $articles = new Article; + $articles = $articles->inRandomOrder(); + $count = $articles->count(); + $articles = $articles->simplePaginate(10); + + return view('list', ["articles" => $articles, "count" => $count]); + } + + public function populartopics() { + $topics = DB::select(" + SELECT + c.name, + count(c.name) AS count + FROM category AS c + JOIN + article_category AS ac ON c.id = ac.category_id + GROUP BY + c.name + ORDER BY count(c.name) DESC;"); +#echo "
"; var_dump($topics);exit;
+
+	    return view('topicpopular', ['topics' => $topics ]);
+    }
+}
diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php
new file mode 100644
index 0000000..09ccbc9
--- /dev/null
+++ b/app/Http/Kernel.php
@@ -0,0 +1,66 @@
+ [
+            \App\Http\Middleware\EncryptCookies::class,
+            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
+            \Illuminate\Session\Middleware\StartSession::class,
+            // \Illuminate\Session\Middleware\AuthenticateSession::class,
+            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
+            \App\Http\Middleware\VerifyCsrfToken::class,
+            \Illuminate\Routing\Middleware\SubstituteBindings::class,
+        ],
+
+        'api' => [
+            'throttle:60,1',
+            \Illuminate\Routing\Middleware\SubstituteBindings::class,
+        ],
+    ];
+
+    /**
+     * The application's route middleware.
+     *
+     * These middleware may be assigned to groups or used individually.
+     *
+     * @var array
+     */
+    protected $routeMiddleware = [
+        'auth' => \App\Http\Middleware\Authenticate::class,
+        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
+        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
+        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
+        'can' => \Illuminate\Auth\Middleware\Authorize::class,
+        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
+        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
+        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
+        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
+        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
+    ];
+}
diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php
new file mode 100644
index 0000000..704089a
--- /dev/null
+++ b/app/Http/Middleware/Authenticate.php
@@ -0,0 +1,21 @@
+expectsJson()) {
+            return route('login');
+        }
+    }
+}
diff --git a/app/Http/Middleware/CheckForMaintenanceMode.php b/app/Http/Middleware/CheckForMaintenanceMode.php
new file mode 100644
index 0000000..35b9824
--- /dev/null
+++ b/app/Http/Middleware/CheckForMaintenanceMode.php
@@ -0,0 +1,17 @@
+check()) {
+            return redirect(RouteServiceProvider::HOME);
+        }
+
+        return $next($request);
+    }
+}
diff --git a/app/Http/Middleware/TrimStrings.php b/app/Http/Middleware/TrimStrings.php
new file mode 100644
index 0000000..5a50e7b
--- /dev/null
+++ b/app/Http/Middleware/TrimStrings.php
@@ -0,0 +1,18 @@
+format("Y-m-d");
+	}
+
+	public static function makeFeed($model, $title, $cache = 60) {
+
+	    switch($title) {
+	    case("new"):
+		    $feed_title = 'Newest Articles';
+		    $feed_description = 'Newest interesting articles from Wikipedia. Keep exploring.';
+		    break;
+	    case( "popular"):
+		    $feed_title = 'Popular Articles';
+		    $feed_description = 'The most popular articles. Keep exploring.';
+		    break;
+	    case( "mastodon"):
+		    $feed_title = 'Feed for Mastodon';
+		    $feed_description = 'New articles are published automatically to Mastodon.';
+		    break;
+	    default:
+		    $feed_title = 'Search for: ' . $title;
+		    $feed_description = 'All articles for "' . $title . '".';
+		    break;
+	    }
+	    $feed_title .= " | mostdiscussed.com";
+
+	    // create new feed
+	    $feed = FeedFactory::create();
+
+	    // multiple feeds are supported
+	    // if you are using caching you should set different cache keys for your feeds
+
+	    // cache the feed for $cache minutes (second parameter is optional)
+	    $feed->setCache($cache, 'feed_' . $title);
+
+	    // check if there is cached feed and build new only if is not
+	    if (!$feed->isCached())
+	    {
+
+	       // set your feed's title, description, link, pubdate and language
+	       $feed->setTitle($feed_title);
+	       $feed->setDescription($feed_description);
+	       $feed->setSubtitle($feed_description);
+	       #$feed->setLogo('http://yoursite.tld/logo.jpg');
+	       $feed->setLink(url('feed/' . $title));
+	       $feed->setDateFormat('datetime'); // 'datetime', 'timestamp' or 'carbon'
+	       $feed->setPubdate($model[0]->created_at);
+	       $feed->setLang('en');
+	       $feed->setShortening(false); // true or false
+
+	       if ( "mastodon" == $title || "mastodon_test" == $title ) {
+		       $feed->setTextLimit(500); // maximum length of description text
+	       } else {
+		       $feed->setTextLimit(100); // maximum length of description text
+	       }
+
+	       foreach ($model as $post)
+	       {
+
+		       $desc = ($post->excerpt_html);
+		       $categories = null;
+
+		       if ( "mastodon" == $title || "mastodon_test" == $title ) {
+
+			       $cat_len= 0;
+
+			       if ( ! $post->getCategories()->get()->isEmpty()  ) {
+
+				       $categories_ar = array("#MostDiscussed");
+
+				       foreach( $post->getCategories()->get() as $cat ) {
+
+					       // uppercase for every word in a possible multi worded hashtag
+					       $tmp_cat = ucwords($cat->name);
+
+					       // strip everything after / for brevity
+					       if ( false !== strpos($tmp_cat, "/") )
+						       $tmp_cat = substr($tmp_cat, 0, strpos($tmp_cat, "/"));
+
+					       // replace any non-alphanumeric character except underscore, because
+					       // it's not allowed to be used in a hashtag
+					       $tmp_cat = preg_replace("/[^A-Za-z0-9_]/", '', $tmp_cat);
+
+					       // trim just in case
+					       $tmp_cat = trim($tmp_cat);
+
+					       // if it's not empty, add it to the hashtag array
+					       if ( "" != $tmp_cat )
+						       $categories_ar[] = "#". $tmp_cat;
+				       }
+
+				       // remove possible duplicates (because stripping after "/")
+				       $categories_ar = array_unique($categories_ar);
+
+				       // join to one string
+				       $categories = implode(" ", $categories_ar);
+
+				       $cat_counter = $post->getCategories()->get()->count();
+				       $cat_len = mb_strlen($categories, "UTF-8");
+			       }
+			       
+			       $link = env('APP_URL') . "/article/" . $post->id;
+
+			       // max desc length is 500 - 23 (Link) - $cat_len - 2 (white spaces)
+			       $max_len = 500 - 23 - $cat_len - 2;
+
+				if ( "mastodon_test" == $title ) {
+				       $desc = Helper::mastodon_summary($desc, $max_len);
+				}else {
+				       $desc = Helper::first_sentence($desc, $max_len);
+				       #$desc = Helper::mastodon_summary($desc, $max_len);
+				}
+
+			       $desc .= " " . $categories;# . $discussions;
+
+			       // set item's title, author, url, pubdate, description, content, enclosure (optional)*
+			       $feed->addItem([
+					'title' => $desc, // mastofeed.org seems to ignore the description field
+					'author' => env('APP_NAME'),
+					'url' => $link,
+					'link' => $link,
+					'pubdate' => $post->created_at,
+					'description' => $desc,
+					'content' => $desc
+			       ]);
+
+		       } else {
+			       if ( ! $post->getCategories()->get()->isEmpty()  ) {
+				       $categories = "
"; + foreach( $post->getCategories()->get() as $cat ) { + $categories .= "name) ."'>". $cat->name . " | "; + } + $categories = rtrim($categories, " | "); + $desc .= "
Topics:"; + $desc .= $categories; + } + + $discussions = "
"; + foreach( $post->getDiscussions()->orderBy('comments', 'desc')->get() as $dis ) { + $discussions .= "" . $dis->title . " "; + $discussions .= $dis->upvotes . " Upvotes | " . $dis->comments . " Comments
"; + } + $desc .= "

Discussions:"; + $desc .= $discussions; + + // set item's title, author, url, pubdate, description, content, enclosure (optional)* + $feed->addItem([ + 'title' => $post->title, + 'author' => env('APP_NAME'), + 'url' => \URL::to($post->url), + 'link' => \URL::to($post->url), + 'pubdate' => $post->created_at, + 'description' => $desc, + 'content' => $desc + ]); + } + } + + } + + // first param is the feed format + // optional: second param is cache duration (value of 0 turns off caching) + // optional: you can set custom cache key with 3rd param as string + $xml = $feed->render('atom', $cache, 'feed_' . $title); + return response($xml, 200, [ + 'Content-Type' => 'application/xml' + ]); + } + + public static function first_sentence($content, $max_len = NULL) { + + $content = html_entity_decode(strip_tags($content)); + $content = ltrim($content); + $pos = strpos($content, '.'); + + if($pos === false) { + if ( is_null($max_len) ) { + return $content; + } else { + return substr($content, 0, $max_len); + } + } + else { + return substr($content, 0, $pos+1); + } + + } + + private static function next_sentence($content, $max_len = NULL) { + + $content = ltrim($content); + $pos = strpos($content, '.'); + + if($pos === false) { + if ( is_null($max_len) ) { + return $content; + } else { + return substr($content, 0, $max_len); + } + } else { + return substr($content, 0, $pos+1); + } + + } + + /** + * Make sure to toot as much text as possible while still staying below the limit of 500 chars -link (23 chars) - 2 (white spaces) - possible hashtags + */ + public static function mastodon_summary($excerpt, $max_len = 475){ + + /** + * Strip HTML from Wikipedia excerpt + */ + $excerpt = ltrim(html_entity_decode(strip_tags($excerpt))); + + /** + * The toot to be returned + */ + $content = ""; + + /** + * https://stackoverflow.com/questions/16377437/split-a-text-into-sentences + */ + $sentences = preg_split('/(?<=[.?!])\s+(?=[a-z])/i', $excerpt); + echo "
";
+		echo "-----------------------";
+		var_dump($excerpt);
+		var_dump($sentences);
+
+		for ( $i = 0; $i < count($sentences); $i++ ) {
+			#if ( mb_strlen($content) < $max_len && (mb_strlen($content) + mb_strlen($sentences) ) {
+			if ( mb_strlen($content) < $max_len && (mb_strlen($content) + mb_strlen($sentences) ) < $max_len ){
+
+				//var_dump(rtrim(ltrim($sentences[$i])));
+				$content .= rtrim(ltrim($sentences[$i]));
+				var_dump("ok", $max_len);
+
+				$max_len = $max_len - mb_strlen($sentences[$i]);
+				var_dump("ok", $max_len);
+
+			} else {
+				var_dump("fail", mb_strlen($content), $max_len);
+				break;
+			}
+		}
+		return $content;
+
+		echo "
";
+		while ( mb_strlen($content) < $max_len ) {
+
+			/**
+			 * Get next sentence
+			 */
+			$next_sentence = Helper::next_sentence($excerpt, $max_len);
+			var_dump($next_sentence);
+
+			if ( "" == $next_sentence ) {
+				break;
+			}
+
+			/**
+			 * Check if $content + $next_sentence is still under $max_len
+			 */
+			if ( (mb_strlen($content) + mb_strlen($next_sentence)) < $max_len ) {
+
+				/**
+				 * add text
+				 */
+				$content .= $next_sentence;
+
+				/**
+				 * Recalc $max_len
+				 */
+				$max_len = $max_len - mb_strlen($next_sentence);
+
+				/**
+				 * Remove sentence from excerpt
+				 */
+				$excerpt = substr($excerpt, mb_strlen($next_sentence));
+
+			} else {
+				break;
+			}
+		}
+
+		return $content;
+	}
+}
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
new file mode 100644
index 0000000..141751a
--- /dev/null
+++ b/app/Providers/AppServiceProvider.php
@@ -0,0 +1,29 @@
+ 'App\Policies\ModelPolicy',
+    ];
+
+    /**
+     * Register any authentication / authorization services.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        $this->registerPolicies();
+
+        //
+    }
+}
diff --git a/app/Providers/BroadcastServiceProvider.php b/app/Providers/BroadcastServiceProvider.php
new file mode 100644
index 0000000..395c518
--- /dev/null
+++ b/app/Providers/BroadcastServiceProvider.php
@@ -0,0 +1,21 @@
+ [
+            SendEmailVerificationNotification::class,
+        ],
+    ];
+
+    /**
+     * Register any events for your application.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        parent::boot();
+
+        //
+    }
+}
diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php
new file mode 100644
index 0000000..527eee3
--- /dev/null
+++ b/app/Providers/RouteServiceProvider.php
@@ -0,0 +1,80 @@
+mapApiRoutes();
+
+        $this->mapWebRoutes();
+
+        //
+    }
+
+    /**
+     * Define the "web" routes for the application.
+     *
+     * These routes all receive session state, CSRF protection, etc.
+     *
+     * @return void
+     */
+    protected function mapWebRoutes()
+    {
+        Route::middleware('web')
+             ->namespace($this->namespace)
+             ->group(base_path('routes/web.php'));
+    }
+
+    /**
+     * Define the "api" routes for the application.
+     *
+     * These routes are typically stateless.
+     *
+     * @return void
+     */
+    protected function mapApiRoutes()
+    {
+        Route::prefix('api')
+             ->middleware('api')
+             ->namespace($this->namespace)
+             ->group(base_path('routes/api.php'));
+    }
+}
diff --git a/app/User.php b/app/User.php
new file mode 100644
index 0000000..e79dab7
--- /dev/null
+++ b/app/User.php
@@ -0,0 +1,39 @@
+ 'datetime',
+    ];
+}
-- 
cgit v1.2.3