diff --git a/app/Classes/Repositories/ArticleRepository.php b/app/Classes/Repositories/ArticleRepository.php index fe0a2a71..66efb241 100644 --- a/app/Classes/Repositories/ArticleRepository.php +++ b/app/Classes/Repositories/ArticleRepository.php @@ -30,7 +30,7 @@ class ArticleRepository extends BaseRepository */ public function __construct(Article $model) { - $this->model = $model; + $this->model = $model->withoutGlobalScopes(); } /** diff --git a/app/Classes/Repositories/BaseRepository.php b/app/Classes/Repositories/BaseRepository.php index e9003df1..6e5e6602 100644 --- a/app/Classes/Repositories/BaseRepository.php +++ b/app/Classes/Repositories/BaseRepository.php @@ -37,7 +37,7 @@ public function all() */ public function allDescendingOrder() { - return $this->model->orderBy('created_at', 'desc')->get(); + return $this->model->withoutGlobalScopes()->orderBy('created_at', 'desc')->get(); } /** diff --git a/app/Model/Article.php b/app/Model/Article.php index 7a09bc1a..74669044 100644 --- a/app/Model/Article.php +++ b/app/Model/Article.php @@ -111,10 +111,32 @@ class Article extends Model implements Linkable protected $dates = ['created_at', 'updated_at', 'deleted_at', 'publish_date', 'unpublish_date']; /** - * Undocumented function. + * The "booting" method of the model. * * @return void */ + protected static function boot() + { + parent::boot(); + + static::addGlobalScope('published', function (Builder $builder) { + return $builder->where([ + ['publish_date', '<=', Carbon::now()], + ['unpublish_date', '>', Carbon::now()], + ['status', '=', true], + ])->orWhere([ + ['publish_date', '<=', Carbon::now()], + ['unpublish_date', '=', null], + ['status', '=', true], + ]); + }); + } + + /** + * Undocumented function. + * + * @return string + */ public function getRouteKeyName() { return 'slug'; @@ -160,17 +182,6 @@ public function setTitleAttribute($value) $this->attributes['slug'] = str_slug($value); } - /** - * Scope the collection to only published. - * - * @param Builder $query - * @return Builder - */ - public function scopePublished(Builder $query) - { - return $query->where('status', true); - } - /** * The url that is used to view this model. * The category will be prefixed if one exists. diff --git a/app/Model/Page.php b/app/Model/Page.php index f6b28053..fdcf4e77 100644 --- a/app/Model/Page.php +++ b/app/Model/Page.php @@ -43,6 +43,8 @@ * @property Carbon $created_at * @property Carbon $updated_at * + * @method static Builder sitemap() Query only pages that are for sitemap. + * * @return Page|Collection|Builder */ class Page extends Model implements Linkable @@ -106,7 +108,7 @@ public function getDescriptionForEvent(string $eventName): string /** * Undocumented function. * - * @return void + * @return string */ public function getRouteKeyName() { @@ -117,22 +119,22 @@ public function getRouteKeyName() * Return the path to the page, these can sometimes have a prefix * attached. * - * @return void + * @return string */ public function path() { if ($this->prefix) { - return "{$this->prefix}/{$this->slug}"; + return url("{$this->prefix}/{$this->slug}"); } - return "{$this->slug}"; + return url("{$this->slug}"); } /** * Undocumented function. * * @param string $string - * @return void + * @return Page */ public static function whereIdentifier(string $string) { @@ -199,6 +201,17 @@ public function linked() return $this->morphMany(Link::class, 'to'); } + /** + * Scope a query to only include sitemap pages. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeSitemap($query) + { + return $query->where('option', '&', PageOptions::OPTION_SITEMAP); + } + /** * The url that is used to view this model. * diff --git a/app/Modules/Articles/BackendController.php b/app/Modules/Articles/BackendController.php index b894085d..4d39867f 100644 --- a/app/Modules/Articles/BackendController.php +++ b/app/Modules/Articles/BackendController.php @@ -168,6 +168,8 @@ public function categories_store(Request $request) * @param int $id * @param ArticleCategoryRepository $categoryRepository * @return \Illuminate\Http\RedirectResponse + * + * @throws \Exception */ public function categories_destroy(int $id, ArticleCategoryRepository $categoryRepository) { diff --git a/app/Modules/Articles/FrontendController.php b/app/Modules/Articles/FrontendController.php index 78df4cc4..f2cd8da2 100644 --- a/app/Modules/Articles/FrontendController.php +++ b/app/Modules/Articles/FrontendController.php @@ -8,17 +8,14 @@ use App\Model\Categories; use App\Jobs\IncrementViews; use Illuminate\Http\Request; -use App\Classes\SitemapGenerator; -use App\Classes\Interfaces\Sitemap; use Illuminate\Support\Facades\View; use App\Classes\Repositories\PageRepository; use App\Classes\Library\PageLoader\Frontpage; -use App\Classes\Repositories\ArticleRepository; /** * Class UserController. */ -class FrontendController implements Sitemap +class FrontendController { /** * @var Page @@ -43,7 +40,7 @@ public function __construct() */ public function allArticles(Article $article) { - View::share('articles', $article->published()->latest('created_at')->paginate(7)); + View::share('articles', $article->latest('created_at')->paginate(7)); return Frontpage::build($this->currentPage, 200, 'articles'); } @@ -54,7 +51,7 @@ public function allArticles(Article $article) * @param Categories $category The articles category. * @param Article $article The article model to interact with. * - * @return void + * @return Frontpage */ public function viewArticle($category, Article $article) { @@ -70,13 +67,12 @@ public function viewArticle($category, Article $article) /** * View all articles in the category. * - * @param ArticleRepository $repository * @param string $string - * @return void + * @return Frontpage */ public function categoryArticles(Categories $category) { - View::share('articles', $category->articles()->where('status', true)->paginate(7)); + View::share('articles', $category->articles()->paginate(7)); $this->currentPage->heading = 'Browse Categories'; @@ -84,12 +80,11 @@ public function categoryArticles(Categories $category) } /** - * Search all articles and return results. + * Search Articles. * - * @param ArticleRepository $repository * @param Request $request * - * @return void + * @return Frontpage */ public function searchArticles(Request $request) { @@ -103,11 +98,10 @@ public function searchArticles(Request $request) /** * Get all the articles created by an account. * - * @param ArticleRepository $repository * @param int $id * @return Frontpage */ - public function allCreatorsArticles(ArticleRepository $repository, Account $account) + public function allCreatorsArticles(Account $account) { $this->currentPage->heading = 'Browse Creators'; @@ -115,24 +109,4 @@ public function allCreatorsArticles(ArticleRepository $repository, Account $acco return Frontpage::build($this->currentPage, 200, 'articles'); } - - /** - * The sitemap function allows plugins to quickly and effectively - * show their content for search engines in a modular way. - * - * @param SitemapGenerator $sitemap - * @return SitemapGenerator - */ - public function sitemap(SitemapGenerator $sitemap) - { - /** @var ArticleRepository $repository */ - $repository = app(ArticleRepository::class); - - /** @var Article $article */ - foreach ($repository->whereSitemappable() as $article) { - $sitemap->store(url($article->route()), $article->updated_at, 'weekly', '1.0'); - } - - return $sitemap; - } } diff --git a/app/Modules/Configs/Controller.php b/app/Modules/Configs/Controller.php index c8645f60..92250019 100644 --- a/app/Modules/Configs/Controller.php +++ b/app/Modules/Configs/Controller.php @@ -1,12 +1,6 @@ '."\n" ?> + + + @foreach ($urlset as $set) + + @if (! empty($set['loc'])) + {{ $set['loc'] }} + @endif + + @if (! empty($set['lastmod'])) + {{ $set['lastmod'] }} + @endif + + @if (! empty($set['changefreq'])) + {{ $set['changefreq'] }} + @endif + + @if (! empty($set['priority'])) + {{ $set['priority'] }} + @endif + + @endforeach + + \ No newline at end of file diff --git a/app/Modules/Sitemap/Routes/frontend.php b/app/Modules/Sitemap/Routes/frontend.php index 962988fd..e659f1ee 100644 --- a/app/Modules/Sitemap/Routes/frontend.php +++ b/app/Modules/Sitemap/Routes/frontend.php @@ -15,7 +15,7 @@ // Get Requests. // ================================================================================== - //Route::get('/sitemap.xml')->uses('Controller@all')->name('sitemap'); + Route::get('/sitemap.xml')->uses('SitemapController@index')->name('sitemap'); // Post Requests. // ================================================================================== diff --git a/app/Modules/Sitemap/SitemapConstants.php b/app/Modules/Sitemap/SitemapConstants.php new file mode 100644 index 00000000..b203d05e --- /dev/null +++ b/app/Modules/Sitemap/SitemapConstants.php @@ -0,0 +1,37 @@ +sitemap = $sitemap; + } + + /** + * Return a response encoded with xml for sitemap.xml viewing. + * + * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response + */ + public function index() + { + /** @var Page $page */ + foreach (Page::sitemap()->get() as $page) { + $this->sitemap->add($page->path())->modified($page->updated_at)->withFrequency(Sitemap::FREQUENCY_WEEKLY)->andPriority(Sitemap::PRIORITY_NORMAL); + } + + /** @var Article $article */ + foreach (Article::all() as $article) { + $this->sitemap->add($article->path())->modified($article->updated_at)->withFrequency(Sitemap::FREQUENCY_WEEKLY)->andPriority(Sitemap::PRIORITY_NORMAL); + } + + return response($this->make('sitemap')->with('urlset', $this->sitemap->collection()), 200, ['Content-type' => 'text/xml']); + } +} diff --git a/app/Modules/Sitemap/SitemapGenerator.php b/app/Modules/Sitemap/SitemapGenerator.php new file mode 100644 index 00000000..5d032ae3 --- /dev/null +++ b/app/Modules/Sitemap/SitemapGenerator.php @@ -0,0 +1,91 @@ +items = $collection; + } + + /** + * Add a sitemap item to the collection. + * @param string $location + * @return SitemapGenerator + */ + public function add(string $location) + { + $this->items->push(['loc' => $location]); + + return $this; + } + + /** + * The date of last modification of the file. + * + * @param Carbon $datetime + * + * @return SitemapGenerator + */ + public function modified(Carbon $datetime) + { + $this->items->push(array_merge($this->items->pop(), ['lastmod' => $datetime->format('Y-m-d')])); + + return $this; + } + + /** + * The frequency of the file to be checked for changes. + * + * @param string $frequency + * + * @return SitemapGenerator + */ + public function withFrequency(string $frequency) + { + $this->items->push(array_merge($this->items->pop(), ['changefreq' => $frequency])); + + return $this; + } + + /** + * Set the priority of the file to be read or indexed. + * + * @param int $priority + * + * @return SitemapGenerator + */ + public function andPriority(float $priority) + { + $this->items->push(array_merge($this->items->pop(), ['priority' => $priority])); + + return $this; + } + + /** + * Return all the current sitemap mappings. + * + * @return Collection + */ + public function collection() + { + return $this->items; + } +} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 9a0847d9..bda114fb 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -2,6 +2,8 @@ namespace App\Providers; +use PDOException; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Route; use App\Modules\ModuleServiceProvider; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; @@ -27,10 +29,14 @@ class RouteServiceProvider extends ServiceProvider */ public function map() { - ModuleServiceProvider::map(); + try { + ModuleServiceProvider::map(); - Route::middleware('web')->group(base_path('routes/web.php')); + Route::middleware('web')->group(base_path('routes/web.php')); - Route::middleware('web')->group(base_path('routes/vendor.php')); + Route::middleware('web')->group(base_path('routes/vendor.php')); + } catch (PDOException $e) { + Log::warning('Unable to process routes: '.$e->getMessage().', '.$e->getFile()); + } } } diff --git a/database/migrations/2016_12_21_154441_edit_facebook_plugin_data.php b/database/migrations/2016_12_21_154441_edit_facebook_plugin_data.php deleted file mode 100644 index 7994751f..00000000 --- a/database/migrations/2016_12_21_154441_edit_facebook_plugin_data.php +++ /dev/null @@ -1,26 +0,0 @@ -
- @foreach (resolve('articles') as $article) + @foreach ($articles as $article)
{{ $article->title }}
diff --git a/tests/Article/ArticlePublishingTest.php b/tests/Article/ArticlePublishingTest.php new file mode 100644 index 00000000..e91ac1dc --- /dev/null +++ b/tests/Article/ArticlePublishingTest.php @@ -0,0 +1,62 @@ +create(['publish_date' => Carbon::tomorrow()]); + + $this->assertCount(0, Article::all()); + } + + /** + * @test + */ + public function it_should_show_articles_published_today() + { + factory('App\Model\Article')->create(['publish_date' => Carbon::today()]); + + $this->assertCount(1, Article::all()); + } + + /** + * @test + */ + public function it_should_not_show_articles_with_unpublish_date_less_than_today() + { + factory('App\Model\Article')->create(['publish_date' => Carbon::yesterday(), 'unpublish_date' => Carbon::today()]); + + $this->assertCount(0, Article::all()); + } + + /** + * @test + */ + public function it_should_show_articles_with_unpublish_date_tommorow() + { + factory('App\Model\Article')->create(['publish_date' => Carbon::now(), 'unpublish_date' => Carbon::tomorrow()]); + + $this->assertCount(1, Article::all()); + } +} diff --git a/tests/Article/ArticleViewingTest.php b/tests/Article/ArticleResponseTest.php similarity index 65% rename from tests/Article/ArticleViewingTest.php rename to tests/Article/ArticleResponseTest.php index 13a08cfe..c5c25e8f 100644 --- a/tests/Article/ArticleViewingTest.php +++ b/tests/Article/ArticleResponseTest.php @@ -2,13 +2,14 @@ namespace Tests\Article; +use Carbon\Carbon; use Tests\TestCase; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Route; use Illuminate\Foundation\Testing\WithFaker; use Illuminate\Foundation\Testing\RefreshDatabase; -class ArticleViewingTest extends TestCase +class ArticleResponseTest extends TestCase { /* * Provide fake content @@ -53,7 +54,7 @@ public function an_articles_views_are_incremented_after_being_viewed() /** * @test */ - public function a_single_article_can_be_viewed_on_the_frontpage() + public function it_should_show_an_article_that_is_published() { $article = factory('App\Model\Article')->create(['views' => 0]); @@ -62,30 +63,46 @@ public function a_single_article_can_be_viewed_on_the_frontpage() $response->assertSee($article->title)->assertViewIs('articles')->assertOk(); } + /** + * @test + */ + public function it_should_not_show_an_article_that_is_not_published() + { + $this->withExceptionHandling(); + + $article = factory('App\Model\Article')->create(['views' => 0, 'publish_date' => Carbon::yesterday(), 'unpublish_date' => Carbon::now()]); + + $response = $this->get($article->path()); + + $response->assertStatus(404); + } + /** * @test * t */ - public function a_collection_of_articles_can_be_viewed_on_the_frontpage() + public function a_collection_of_articles_can_be_viewed_on_the_frontpage_except_unpublished() { $collection = factory('App\Model\Article', 5)->create(); + $unpublished = factory('App\Model\Article')->create(['unpublish_date' => Carbon::now()]); $response = $this->get('articles'); - $response->assertSee($collection->random()->title)->assertViewIs('articles')->assertOk(); + $response->assertSee($collection->random()->title)->assertDontSee($unpublished->title)->assertViewIs('articles')->assertOk(); } /** * @test */ - public function a_category_can_have_a_collection_of_articles_on_the_frontpage() + public function a_category_can_have_a_collection_of_articles_on_the_frontpage_except_unpublished() { $category = factory('App\Model\Categories')->create(); + $unpublished = factory('App\Model\Article')->create(['unpublish_date' => Carbon::now()]); $collection = factory('App\Model\Article', 5)->create(['category_id' => $category->id]); $response = $this->get('articles/'.$category->slug); - $response->assertSee($collection->random()->title)->assertViewIs('articles')->assertOk(); + $response->assertSee($collection->random()->title)->assertDontSee($unpublished->title)->assertViewIs('articles')->assertOk(); } /** @@ -105,14 +122,27 @@ public function articles_can_be_searched_on_the_frontpage() /** * @test */ - public function view_all_articles_by_creator_on_the_frontpage() + public function it_should_not_show_unpublished_articles_on_search() + { + $unpublished = factory('App\Model\Article')->create(['unpublish_date' => Carbon::now()]); + + $response = $this->get('/articles/search?query='.$unpublished->title); + + $response->assertDontSee($unpublished->title)->assertViewIs('articles')->assertOk(); + } + + /** + * @test + */ + public function view_all_articles_by_creator_on_the_frontpage_except_unpublished() { $account = factory('App\Model\Account')->create(); $articles = factory('App\Model\Article', 3)->create(['creator_id' => $account->id]); + $unpublished = factory('App\Model\Article')->create(['unpublish_date' => Carbon::now()]); $response = $this->get('/articles/creator/'.$account->username); - $response->assertSee($articles->random()->title)->assertViewIs('articles')->assertOk(); + $response->assertSee($articles->random()->title)->assertDontSee($unpublished->title)->assertViewIs('articles')->assertOk(); } } diff --git a/tests/Common/BreadcrumbsTest.php b/tests/Common/BreadcrumbsTest.php index 4cee8069..a7037fd6 100644 --- a/tests/Common/BreadcrumbsTest.php +++ b/tests/Common/BreadcrumbsTest.php @@ -88,7 +88,7 @@ public function testFromCurrentRoute() $this->call('GET', '/breadcrumb/test'); - $this->assertCount(4, Breadcrumbs::fromCurrentRoute()->crumbs()); + $this->assertCount(3, Breadcrumbs::fromCurrentRoute()->crumbs()); } // @todo: Check the array for the correct return string.? diff --git a/tests/Module/ModuleManagerTest.php b/tests/Module/ModuleManagerTest.php index 6a9ba981..e18c77e9 100644 --- a/tests/Module/ModuleManagerTest.php +++ b/tests/Module/ModuleManagerTest.php @@ -8,6 +8,7 @@ use App\Modules\ModuleManager; use App\Modules\ModuleRepository; use Illuminate\Support\Facades\Config; +use App\Modules\ModuleNotFoundException; use App\Modules\Pages\Model\PageOptions; use Illuminate\Foundation\Testing\RefreshDatabase; diff --git a/tests/Products/ProductInstallationTest.php b/tests/Products/ProductInstallationTest.php index 8548ac0d..477064e7 100644 --- a/tests/Products/ProductInstallationTest.php +++ b/tests/Products/ProductInstallationTest.php @@ -20,12 +20,14 @@ class ProductInstallationTest extends TestCase */ public function modules_can_be_toggled() { + $this->signIn(); + $mocked = Mockery::mock(ModuleManager::class)->shouldReceive('toggle')->andReturn(true)->getMock(); $this->app->instance(ModuleManager::class, $mocked); $response = $this->get('/admin/products/test/toggle'); - $response->assertRedirect('/admin/products')->assertSee('test'); + $response->assertRedirect('/admin/products'); } } diff --git a/tests/Sitemap/SitemapGeneratorTest.php b/tests/Sitemap/SitemapGeneratorTest.php new file mode 100644 index 00000000..082706bc --- /dev/null +++ b/tests/Sitemap/SitemapGeneratorTest.php @@ -0,0 +1,89 @@ +generator = new SitemapGenerator(new Collection); + } + + /** + * @test + */ + public function it_should_add_urls() + { + $this->generator->add($this->faker->url); + + $this->assertCount(1, $this->generator->collection()); + } + + /** + * @test + */ + public function it_should_add_multiple_urls() + { + $this->generator->add($this->faker->url); + $this->generator->add($this->faker->url); + + $this->assertCount(2, $this->generator->collection()); + } + + /** + * @test + */ + public function it_can_have_a_last_modified_date() + { + $this->generator->add($this->faker->url)->modified(Carbon::create(2018, 7, 7)); + + $this->assertTrue(array_has($this->generator->collection(), '0.loc')); + } + + /** + * @test + */ + public function it_can_have_a_change_frequency() + { + $this->generator->add($this->faker->url)->withFrequency(Sitemap::FREQUENCY_DAILY); + + $this->assertTrue(array_has($this->generator->collection(), '0.changefreq')); + } + + /** + * @test + */ + public function it_can_have_a_priority() + { + $this->generator->add($this->faker->url)->andPriority(Sitemap::PRIORITY_HIGHEST); + + $this->assertTrue(array_has($this->generator->collection(), '0.priority')); + } +} diff --git a/tests/Sitemap/SitemapResponseTest.php b/tests/Sitemap/SitemapResponseTest.php new file mode 100644 index 00000000..4fe08fc9 --- /dev/null +++ b/tests/Sitemap/SitemapResponseTest.php @@ -0,0 +1,57 @@ +get('/sitemap.xml')->assertHeader('Content-type', 'text/xml; charset=UTF-8')->assertOk(); + } + + /** + * @test + */ + public function it_shows_pages_on_sitemap() + { + $page = factory('App\Model\Page')->create(['option' => PageOptions::OPTION_SITEMAP]); + + $response = $this->get('/sitemap.xml'); + + $response->assertSee(''.$page->path().''); + $response->assertSee(''.$page->updated_at->format('Y-m-d').''); + + $this->assertCount(2, $response->getOriginalContent()->getData()['urlset']); + } + + /** + * @test + */ + public function it_shows_articles_on_sitemap_except_unpublished() + { + $collection = factory('App\Model\Article', 2)->create(['status' => true]); + $unpublished = factory('App\Model\Article')->create(['unpublish_date' => Carbon::now()]); + + $response = $this->get('/sitemap.xml'); + + $this->assertCount(3, $response->getOriginalContent()->getData()['urlset']); + + $response->assertSee($collection->random()->path())->assertDontSee($unpublished->path()); + } +}