-
+
sThumb) as $dimension => $value) {{ $dimension }}="{{ $value }}" @endforeach class="size-full wp-image-28 aligncenter" src="{{ $oArticle->sThumb }}" style="border-radius: 6px;">
{{ $oCategory->sName }}
-{{ $oCategory->sDescription }}
-看起来这里没有任何东西…
-{{@$aTag['name']}}{{@$aTag['count']}}
-{{ in_array($oCategory->sSlug, ['client', 'advanced']) ? $oCategory->sName : $oCategory->sSlug }}{{ $iArticleCount ?? count($oArticles) }}
-{{ $seo['heading'] }}
{{ $seo['description'] }}
+ @else +{{ $seo['heading'] }}
{{ $seo['description'] }}
{{ isset($sKeywords) ? '没有找到匹配的文章,请尝试其他关键词。' : '这个栏目暂时没有文章。' }}
+ @else +{{ $oArticle->sTitle }}
+{{ $seo['heading'] ?? $oArticle->sTitle }}
以下为最近一条资源记录。连接情况会随时间、地区和网络变化,请以客户端实际连接为准。
+ 查看这次更新记录 · 全部历史记录 + @else +这是 {{ $oArticle->created_at?->format('Y-m-d') }} 的历史记录,订阅与节点可能已经变化。
查看当前免费节点与订阅 + @endif + · 入门教程 · 连接与配置排错 +资源暂不可用
暂时没有可展示的节点记录,请稍后再试。
查看入门教程操作步骤
示例正文
','iCategoryId'=>3,'created_at'=>'2026-10-30 10:00:00','updated_at'=>'2026-10-31 01:00:00']); + } + public function test_category_aliases_pagination_and_parameters(): void + { + foreach(range(1,5) as $n)$this->article('guide'.$n); + $this->get('/windows?page=2&utm_source=x')->assertRedirect('https://example.org/client/windows?page=2')->assertStatus(301); + foreach(['/wrong/windows','/wrong/freenode','/client/freenode','/client/windows?page=0','/client/windows?page=-1','/client/windows?page=abc','/client/windows?page=99','/client/windows?page[]=2','/client/windows?page=2&page=3'] as $url)$this->get($url)->assertNotFound(); + $this->get('/client/windows?page=1')->assertStatus(301)->assertRedirect('https://example.org/client/windows'); + $html=$this->get('/client/windows?page=2&utm_source=x')->assertOk()->getContent(); + $this->assertStringContainsString('rel="canonical" href="https://example.org/client/windows?page=2"',$html); + $this->assertStringContainsString('rel="next" href="https://example.org/client/windows?page=3"',$html); + $this->assertStringContainsString('rel="prev" href="https://example.org/client/windows"',$html); + $this->assertStringNotContainsString('guide5.html',$html); + $this->get('/client/windows?page=3')->assertOk()->assertDontSee('class="pagination-next"',false); + $this->get('/robots.txt')->assertOk()->assertDontSee('Disallow: /*?*',false); + } + public function test_hidden_and_future_content_are_absent_from_every_public_query(): void + { + $good=$this->article('good'); + foreach(['draft'=>['iStatus'=>0],'future'=>['created_at'=>'2026-11-01 00:00:00']] as $slug=>$extra){$id=$this->article($slug,$extra);DB::table('article_tag')->insert(['iArticleId'=>$id,'iTagId'=>1]);$this->get('/a/'.$slug.'.html')->assertNotFound();} + $this->get('/search/'.rawurlencode('教程'))->assertOk()->assertSee('good.html',false)->assertDontSee('draft.html',false)->assertDontSee('future.html',false)->assertSee('noindex, follow',false); + $this->get('/?s='.rawurlencode('教程'))->assertStatus(301)->assertRedirect(Seo::url('/search/'.rawurlencode('教程'))); + $this->get('/search/'.rawurlencode('不存在的内容'))->assertOk()->assertSee('没有找到匹配的文章'); + $this->get('/tag/clash')->assertOk()->assertDontSee('draft.html',false); + DB::table('categories')->where('id',2)->update(['iStatus'=>0]); + $this->get('/a/good.html')->assertNotFound();$this->get('/client/windows')->assertNotFound(); + $this->get('/sitemap.xml')->assertOk()->assertDontSee('good.html',false); + $this->get('/search/'.rawurlencode('教程'))->assertOk()->assertDontSee('good.html',false); + } + public function test_home_is_stable_and_failed_refresh_does_not_replace_it(): void + { + $this->article('fn-good',['iCategoryId'=>1,'sTitle'=>'日期标题','sTitleSub'=>'共20个可用节点']); + $this->article('fn-zero',['iCategoryId'=>1,'sTitleSub'=>'本次更新共0个可用节点','created_at'=>'2026-10-31 11:00:00']); + $this->assertSame('fn-good',app(ArticlesService::class)->getLatestFreeNodeSlug()); + $html=$this->get('/')->assertOk()->getContent(); + $this->assertStringContainsString('免费节点与订阅使用指南
',$html); + $this->assertStringContainsString('rel="canonical" href="https://example.org/"',$html); + $this->assertStringContainsString('property="og:type" content="website"',$html); + $this->assertStringContainsString('fn-good.html',$html); + $this->travel(10)->days();$this->get('/')->assertOk()->assertSee('content="index, follow"',false); + $this->get('/a/fn-good.html')->assertOk()->assertSee('content="noindex, follow"',false)->assertSee('历史记录'); + DB::table('articles')->delete();$this->get('/')->assertStatus(503)->assertHeader('Retry-After','3600'); + } + public function test_new_snapshots_are_archives_and_content_clock_ignores_likes(): void + { + $id=$this->article('old',['iCategoryId'=>1]);$old=Articles::find($id); + $this->assertTrue(Seo::indexable($old));$this->assertNull(Seo::modified($old)); + $new=Articles::create(['sSlug'=>'new','sTitle'=>'新的记录','sTitleSub'=>'共10个可用节点','sContent'=>'有效资源
','iCategoryId'=>1,'iStatus'=>1]); + $this->assertSame('archive',$new->sSeoPolicy);$this->assertFalse(Seo::indexable($new)); + $clock=Seo::modified($new);$this->travel(1)->hours();$new->increment('iLikeCount');$this->assertSame($clock,Seo::modified($new->fresh())); + $new->sContent='更新资源内容
';$new->save();$this->assertNotSame($clock,Seo::modified($new->fresh())); + $this->get('/sitemap.xml')->assertOk()->assertDontSee('/a/new.html',false)->assertSee('/a/old.html',false); + $this->travelTo(\Carbon\Carbon::parse('2026-11-06 10:00:01','UTC')); + $this->get('/sitemap.xml')->assertOk()->assertDontSee('/a/old.html',false); + } + public function test_metadata_and_structured_data_are_clean_and_share_article_url(): void + { + $this->article('clean',['sTitle'=>'标题 & "引用"']); + $html=$this->get('/a/clean.html?utm_source=test')->assertOk()->getContent(); + $dom=new \DOMDocument();@$dom->loadHTML($html);$xp=new \DOMXPath($dom); + $this->assertSame('示例 & 摘要',$xp->query('//meta[@name="description"]')->item(0)->getAttribute('content')); + $this->assertSame('https://example.org/a/clean.html',$xp->query('//a[@id="urlCopy"]')->item(0)->getAttribute('data-clipboard-text')); + foreach($xp->query('//script[@type="application/ld+json"]') as $script)$this->assertIsArray(json_decode($script->textContent,true,512,JSON_THROW_ON_ERROR)); + $this->assertStringNotContainsString('dateModified',$html);$this->assertStringNotContainsString('AggregateRating',$html); + $this->assertStringContainsString('href="https://example.org/client/windows"',$html); + } + public function test_rejected_resource_does_not_change_articles(): void + { + $id=$this->article('last',['iCategoryId'=>1]); + $payload=['title'=>'更新','title_sub'=>'共12个可用节点','content'=>'内容
','tags'=>[]]; + $this->assertSame(12,ResourcePublication::validate($payload)['iResourceCount']); + $this->assertSame('unknown',ResourcePublication::validate($payload)['sResourceStatus']); + foreach([['check_status'=>'failed'],['valid_count'=>0],['title_sub'=>'本次更新共0个可用节点'],['content'=>'']] as $bad){ + try { ResourcePublication::validate(array_replace($payload,$bad));$this->fail('Invalid refresh accepted'); } catch(\Symfony\Component\HttpKernel\Exception\HttpException|\Illuminate\Validation\ValidationException $e){$this->assertNotEmpty($e->getMessage());} + } + $this->assertSame('last',Articles::find($id)->sSlug);$this->assertSame(1,Articles::count()); + } + public function test_durable_home_notifications_retry_and_deletions_need_no_http_probe(): void + { + config(['editorial.indexnow_enabled'=>true,'key.indexnow'=>'test-key-12345678']); + Http::fake(['api.indexnow.org/*'=>Http::sequence()->push('',429)->push('',202)->push('',200)]); + $id=$this->article('snapshot',['iCategoryId'=>1,'sSeoPolicy'=>'archive']);$service=app(SeoNotifications::class); + $service->changed(Articles::find($id));$service->changed(Articles::find($id));$this->assertSame(1,DB::table('seo_notifications')->count()); + $service->retry();Http::assertSentCount(1);$service->retry();Http::assertSentCount(1); + $this->travel(6)->minutes();$service->retry();Http::assertSentCount(2); + Http::assertSent(fn($req)=>$req['urlList']===['https://example.org/','https://example.org/freenode']); + $service->changed(Articles::find($id),true);DB::table('articles')->where('id',$id)->delete();$service->retry();Http::assertSentCount(3); + Http::assertSent(fn($req)=>in_array('https://example.org/a/snapshot.html',$req['urlList'])); + } + public function test_cleanup_retains_old_resource_urls_and_get_cannot_like(): void + { + $id=$this->article('historical',['iCategoryId'=>1,'created_at'=>'2025-01-01 00:00:00']); + $this->artisan('clear_db')->assertExitCode(0);$this->assertNotNull(Articles::find($id)); + $this->get('/like/'.$id)->assertNotFound();$this->assertSame(0,Articles::find($id)->iLikeCount); + } + public function test_commercial_attributes_and_zero_string_validation(): void + { + $html='推广参考'; + $body=Seo::body($html);$this->assertStringContainsString('rel="nofollow sponsored noopener"',$body); + $this->assertStringContainsString('参考',$body); + $this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class); + ResourcePublication::validate(['title'=>'资源','title_sub'=>'数量未知','content'=>'内容','tags'=>[],'valid_count'=>'0']); + } + +}
+ @foreach(\App\Services\Seo::imageSize("/sites/".config("app.site_code_short")."/images/logo/logo.png") as $dimension => $value) {{ $dimension }}="{{ $value }}" @endforeach class="d-inline-block logo-light nc-no-lazy"
+ alt="{{ config('seo.brand') }}">