mirror of
https://ghproxy.net/https://github.com/aspirepress/AspireCloud.git
synced 2025-10-04 21:34:02 +08:00
update plugin_information, implement hot_tags for /plugins/info (#87)
* fix: mv grumphp.yml grumphp.yml.dist and use explicit phpcsfixer config * refactor: move NotFoundException to App\Exceptions * refactor: move plugin services to App\Services\Plugins * feat: implement hot_tags action for /plugins/info endpoint * fix: make plugin_information fields match upstream (order still differs) * fix: give nullable columns in Plugin nullable types * fix(tests): rm 'downloaded' key from assertWpPluginAPIStructure * refactor: break out QueryPluginsService from PluginInformationService * refactor: break queryPlugins into apply* methods * fix: change orderBy to reorder in applyBrowse() * feat: add slug to plugin search criteria * fix: variable naming in Plugin::fillFromMetadata * fix: use tags table for tag query * feat: add PluginTagFactory (not used yet) * feat: implement /core/importers * fix: tags fields removed and refactor plugin tests (#89) - On #87, a many-to-many relationship for tags was introduced; keeping the tags field on the Plugin table is unnecessary - Refactor the Plugin tests to use the new many-to-many relationshipt - Refactor the PluginFactory to allow create plugins with the tags relationship * style: make fix * style: take out some redundant ::query() calls * fix: add cascade delete to tag join tables --------- Co-authored-by: Enrique Chavez <noone@tmeister.net>
This commit is contained in:
parent
b2d0a89838
commit
123803ec83
28 changed files with 532 additions and 195 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -25,3 +25,6 @@ yarn-error.log
|
|||
# helper files would not normally be ignored, but they're currently broken, so it's a local decision to use them
|
||||
_ide_helper.php
|
||||
.phpstorm.meta.php
|
||||
|
||||
grumphp.yml
|
||||
π
|
||||
|
|
31
app/Data/WpOrg/Plugins/PluginHotTagsResponse.php
Normal file
31
app/Data/WpOrg/Plugins/PluginHotTagsResponse.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace App\Data\WpOrg\Plugins;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
class PluginHotTagsResponse extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public string $slug,
|
||||
public string $name,
|
||||
public int $count,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Static method to create an instance from a Plugin model.
|
||||
* @param Collection<int,covariant array{
|
||||
* slug: string,
|
||||
* name: string,
|
||||
* count: int,
|
||||
* }> $pluginTags
|
||||
* @return Collection<string, covariant PluginHotTagsResponse>
|
||||
*/
|
||||
public static function fromCollection(Collection $pluginTags): Collection
|
||||
{
|
||||
return $pluginTags->mapWithKeys(fn($plugin) => [
|
||||
$plugin['slug'] => self::from($plugin),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ namespace App\Data\WpOrg\Themes;
|
|||
use Illuminate\Support\Collection;
|
||||
use Spatie\LaravelData\Data;
|
||||
|
||||
class HotTagsResponse extends Data
|
||||
class ThemeHotTagsResponse extends Data
|
||||
{
|
||||
public function __construct(
|
||||
public string $slug,
|
||||
|
@ -20,7 +20,7 @@ class HotTagsResponse extends Data
|
|||
* name: string,
|
||||
* count: int,
|
||||
* }> $themeTags
|
||||
* @return Collection<string, covariant HotTagsResponse>
|
||||
* @return Collection<string, covariant ThemeHotTagsResponse>
|
||||
*/
|
||||
public static function fromCollection(Collection $themeTags): Collection
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
namespace App\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
68
app/Http/Controllers/API/WpOrg/Core/ImportersController.php
Normal file
68
app/Http/Controllers/API/WpOrg/Core/ImportersController.php
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\API\WpOrg\Core;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class ImportersController extends Controller
|
||||
{
|
||||
public function __invoke(string $version): JsonResponse|Response
|
||||
{
|
||||
$response = [
|
||||
"importers" => [
|
||||
"blogger" => [
|
||||
"name" => "Blogger",
|
||||
"description" => "Install the Blogger importer to import posts, comments, and users from a Blogger blog.",
|
||||
"plugin-slug" => "blogger-importer",
|
||||
"importer-id" => "blogger",
|
||||
],
|
||||
"wpcat2tag" => [
|
||||
"name" => "Categories and Tags Converter",
|
||||
"description" => "Install the category/tag converter to convert existing categories to tags or tags to categories, selectively.",
|
||||
"plugin-slug" => "wpcat2tag-importer",
|
||||
"importer-id" => "wpcat2tag",
|
||||
],
|
||||
"livejournal" => [
|
||||
"name" => "LiveJournal",
|
||||
"description" => "Install the LiveJournal importer to import posts from LiveJournal using their API.",
|
||||
"plugin-slug" => "livejournal-importer",
|
||||
"importer-id" => "livejournal",
|
||||
],
|
||||
"movabletype" => [
|
||||
"name" => "Movable Type and TypePad",
|
||||
"description" => "Install the Movable Type importer to import posts and comments from a Movable Type or TypePad blog.",
|
||||
"plugin-slug" => "movabletype-importer",
|
||||
"importer-id" => "mt",
|
||||
],
|
||||
"opml" => [
|
||||
"name" => "Blogroll",
|
||||
"description" => "Install the blogroll importer to import links in OPML format.",
|
||||
"plugin-slug" => "opml-importer",
|
||||
"importer-id" => "opml",
|
||||
],
|
||||
"rss" => [
|
||||
"name" => "RSS",
|
||||
"description" => "Install the RSS importer to import posts from an RSS feed.",
|
||||
"plugin-slug" => "rss-importer",
|
||||
"importer-id" => "rss",
|
||||
],
|
||||
"tumblr" => [
|
||||
"name" => "Tumblr",
|
||||
"description" => "Install the Tumblr importer to import posts & media from Tumblr using their API.",
|
||||
"plugin-slug" => "tumblr-importer",
|
||||
"importer-id" => "tumblr",
|
||||
],
|
||||
"wordpress" => [
|
||||
"name" => "WordPress",
|
||||
"description" => "Install the WordPress importer to import posts, pages, comments, custom fields, categories, and tags from a WordPress export file.",
|
||||
"plugin-slug" => "wordpress-importer",
|
||||
"importer-id" => "wordpress",
|
||||
],
|
||||
],
|
||||
"translated" => false,
|
||||
];
|
||||
return $version === '1.0' ? new Response(serialize((object) $response)) : response()->json($response);
|
||||
}
|
||||
}
|
|
@ -7,14 +7,18 @@ use App\Http\Requests\Plugins\PluginInformationRequest;
|
|||
use App\Http\Requests\Plugins\QueryPluginsRequest;
|
||||
use App\Http\Resources\Plugins\PluginCollection;
|
||||
use App\Http\Resources\Plugins\PluginResource;
|
||||
use App\Services\PluginInformationService;
|
||||
use App\Services\Plugins\PluginHotTagsService;
|
||||
use App\Services\Plugins\PluginInformationService;
|
||||
use App\Services\Plugins\QueryPluginsService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PluginInformation_1_2_Controller extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PluginInformationService $pluginService,
|
||||
private readonly PluginInformationService $pluginInfo,
|
||||
private readonly QueryPluginsService $queryPlugins,
|
||||
private readonly PluginHotTagsService $hotTags,
|
||||
) {}
|
||||
|
||||
public function __invoke(Request $request): JsonResponse
|
||||
|
@ -22,6 +26,7 @@ class PluginInformation_1_2_Controller extends Controller
|
|||
return match ($request->query('action')) {
|
||||
'query_plugins' => $this->queryPlugins(new QueryPluginsRequest($request->all())),
|
||||
'plugin_information' => $this->pluginInformation(new PluginInformationRequest($request->all())),
|
||||
'hot_tags', 'popular_tags' => $this->hotTags($request),
|
||||
default => response()->json(['error' => 'Invalid action'], 400),
|
||||
};
|
||||
}
|
||||
|
@ -33,7 +38,7 @@ class PluginInformation_1_2_Controller extends Controller
|
|||
return response()->json(['error' => 'Slug is required'], 400);
|
||||
}
|
||||
|
||||
$plugin = $this->pluginService->findBySlug($request->getSlug());
|
||||
$plugin = $this->pluginInfo->findBySlug($request->getSlug());
|
||||
|
||||
if (!$plugin) {
|
||||
return response()->json(['error' => 'Plugin not found'], 404);
|
||||
|
@ -44,7 +49,7 @@ class PluginInformation_1_2_Controller extends Controller
|
|||
|
||||
private function queryPlugins(QueryPluginsRequest $request): JsonResponse
|
||||
{
|
||||
$result = $this->pluginService->queryPlugins(
|
||||
$result = $this->queryPlugins->queryPlugins(
|
||||
page: $request->getPage(),
|
||||
perPage: $request->getPerPage(),
|
||||
search: $request->query('search'),
|
||||
|
@ -60,4 +65,10 @@ class PluginInformation_1_2_Controller extends Controller
|
|||
$result['total']
|
||||
));
|
||||
}
|
||||
|
||||
private function hotTags(Request $request): JsonResponse
|
||||
{
|
||||
$tags = $this->hotTags->getHotTags((int) $request->query('number', '-1'));
|
||||
return response()->json($tags);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ namespace App\Http\Controllers\API\WpOrg\Plugins;
|
|||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Plugins\PluginUpdateRequest;
|
||||
use App\Http\Resources\Plugins\PluginUpdateCollection;
|
||||
use App\Services\PluginUpdateService;
|
||||
use App\Services\Plugins\PluginUpdateService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
use function Safe\json_decode;
|
||||
|
|
|
@ -4,13 +4,13 @@ namespace App\Http\Controllers\API\WpOrg\Themes;
|
|||
|
||||
use App\Data\WpOrg\Themes\QueryThemesRequest;
|
||||
use App\Data\WpOrg\Themes\ThemeInformationRequest;
|
||||
use App\Exceptions\NotFoundException;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\ThemeCollection;
|
||||
use App\Http\Resources\ThemeResource;
|
||||
use App\NotFoundException;
|
||||
use App\Services\Themes\FeatureListService;
|
||||
use App\Services\Themes\HotTagsService;
|
||||
use App\Services\Themes\QueryThemesService;
|
||||
use App\Services\Themes\ThemeHotTagsService;
|
||||
use App\Services\Themes\ThemeInformationService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
@ -24,7 +24,7 @@ class ThemeController extends Controller
|
|||
public function __construct(
|
||||
private readonly QueryThemesService $queryThemes,
|
||||
private readonly ThemeInformationService $themeInfo,
|
||||
private readonly HotTagsService $hotTags,
|
||||
private readonly ThemeHotTagsService $hotTags,
|
||||
private readonly FeatureListService $featureList,
|
||||
) {}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Http\Resources\Plugins;
|
||||
|
||||
use App\Models\WpOrg\Plugin;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
|
@ -14,14 +15,17 @@ abstract class BasePluginResource extends JsonResource
|
|||
*/
|
||||
protected function getCommonAttributes(): array
|
||||
{
|
||||
$plugin = $this->resource;
|
||||
assert($plugin instanceof Plugin);
|
||||
|
||||
return [
|
||||
'name' => $this->resource->name,
|
||||
'slug' => $this->resource->slug,
|
||||
'version' => $this->resource->version,
|
||||
'requires' => $this->resource->requires,
|
||||
'tested' => $this->resource->tested,
|
||||
'requires_php' => $this->resource->requires_php,
|
||||
'download_link' => $this->resource->download_link,
|
||||
'name' => $plugin->name,
|
||||
'slug' => $plugin->slug,
|
||||
'version' => $plugin->version,
|
||||
'requires' => $plugin->requires,
|
||||
'tested' => $plugin->tested,
|
||||
'requires_php' => $plugin->requires_php,
|
||||
'download_link' => $plugin->download_link,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -2,10 +2,13 @@
|
|||
|
||||
namespace App\Http\Resources\Plugins;
|
||||
|
||||
use App\Models\WpOrg\Plugin;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PluginResource extends BasePluginResource
|
||||
{
|
||||
public const LAST_UPDATED_DATE_FORMAT = 'Y-m-d H:ia T'; // .org's goofy format: "2024-09-27 9:53pm GMT"
|
||||
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
|
@ -13,35 +16,45 @@ class PluginResource extends BasePluginResource
|
|||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
$plugin = $this->resource;
|
||||
assert($plugin instanceof Plugin);
|
||||
|
||||
$data = array_merge($this->getCommonAttributes(), [
|
||||
'author' => $this->resource->author,
|
||||
'author_profile' => $this->resource->author_profile,
|
||||
'rating' => $this->resource->rating,
|
||||
'num_ratings' => $this->resource->num_ratings,
|
||||
'ratings' => $this->mapRatings($this->resource->ratings),
|
||||
'support_threads' => $this->resource->support_threads,
|
||||
'support_threads_resolved' => $this->resource->support_threads_resolved,
|
||||
'active_installs' => $this->resource->active_installs,
|
||||
'downloaded' => $this->resource->downloaded,
|
||||
'last_updated' => $this->resource->last_updated?->format('Y-m-d H:i:s'),
|
||||
'added' => $this->resource->added?->format('Y-m-d'),
|
||||
'homepage' => $this->resource->homepage,
|
||||
'tags' => $this->resource->tags,
|
||||
'donate_link' => $this->resource->donate_link,
|
||||
'author' => $plugin->author,
|
||||
'author_profile' => $plugin->author_profile,
|
||||
'rating' => $plugin->rating,
|
||||
'num_ratings' => $plugin->num_ratings,
|
||||
'ratings' => $this->mapRatings($plugin->ratings),
|
||||
'support_threads' => $plugin->support_threads,
|
||||
'support_threads_resolved' => $plugin->support_threads_resolved,
|
||||
'active_installs' => $plugin->active_installs,
|
||||
'last_updated' => $plugin->last_updated?->format(self::LAST_UPDATED_DATE_FORMAT),
|
||||
'added' => $plugin->added->format('Y-m-d'),
|
||||
'homepage' => $plugin->homepage,
|
||||
'tags' => $plugin->tags,
|
||||
'donate_link' => $plugin->donate_link,
|
||||
'requires_plugins' => $plugin->requires_plugins ?? [],
|
||||
]);
|
||||
|
||||
return match ($request->query('action')) {
|
||||
'query_plugins' => array_merge($data, [
|
||||
'short_description' => $this->resource->short_description,
|
||||
'description' => $this->resource->description,
|
||||
'icons' => $this->resource->icons,
|
||||
'requires_plugins' => $this->resource->requires_plugins ?? [],
|
||||
'downloaded' => $plugin->downloaded,
|
||||
'short_description' => $plugin->short_description,
|
||||
'description' => $plugin->description,
|
||||
'icons' => $plugin->icons,
|
||||
]),
|
||||
'plugin_information' => array_merge($data, [
|
||||
'sections' => $this->resource->sections,
|
||||
'versions' => $this->resource->versions,
|
||||
'contributors' => $this->resource->contributors,
|
||||
'screenshots' => $this->resource->screenshots,
|
||||
'sections' => $plugin->sections,
|
||||
'versions' => $plugin->versions,
|
||||
'contributors' => $plugin->contributors,
|
||||
'screenshots' => $plugin->screenshots,
|
||||
'support_url' => $plugin->support_url,
|
||||
'upgrade_notice' => $plugin->upgrade_notice,
|
||||
'business_model' => $plugin->business_model,
|
||||
'repository_url' => $plugin->repository_url,
|
||||
'commercial_support_url' => $plugin->commercial_support_url,
|
||||
'banners' => $plugin->banners,
|
||||
'preview_link' => $plugin->preview_link,
|
||||
]),
|
||||
default => $data,
|
||||
};
|
||||
|
|
|
@ -15,7 +15,7 @@ use Illuminate\Support\Facades\DB;
|
|||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @property string $id
|
||||
* @property-read string $id
|
||||
* @property string $slug
|
||||
* @property string $name
|
||||
* @property string $short_description
|
||||
|
@ -23,37 +23,37 @@ use InvalidArgumentException;
|
|||
* @property string $version
|
||||
* @property string $author
|
||||
* @property string $requires
|
||||
* @property string $requires_php
|
||||
* @property string|null $requires_php
|
||||
* @property string $tested
|
||||
* @property string $download_link
|
||||
* @property CarbonImmutable $added
|
||||
* @property CarbonImmutable $last_updated
|
||||
* @property string $author_profile
|
||||
* @property CarbonImmutable|null $last_updated
|
||||
* @property string|null $author_profile
|
||||
* @property int $rating
|
||||
* @property array $ratings
|
||||
* @property array|null $ratings
|
||||
* @property int $num_ratings
|
||||
* @property int $support_threads
|
||||
* @property int $support_threads_resolved
|
||||
* @property int $active_installs
|
||||
* @property int $downloaded
|
||||
* @property string $homepage
|
||||
* @property array $banners
|
||||
* @property array $tags
|
||||
* @property string $donate_link
|
||||
* @property array $contributors
|
||||
* @property array $icons
|
||||
* @property array $source
|
||||
* @property string $business_model
|
||||
* @property string $commercial_support_url
|
||||
* @property string $support_url
|
||||
* @property string $preview_link
|
||||
* @property string $repository_url
|
||||
* @property array $requires_plugins
|
||||
* @property array $compatibility
|
||||
* @property array $screenshots
|
||||
* @property array $sections
|
||||
* @property array $versions
|
||||
* @property array $upgrade_notice
|
||||
* @property string|null $homepage
|
||||
* @property array|null $banners
|
||||
* @property string|null $donate_link
|
||||
* @property array|null $contributors
|
||||
* @property array|null $icons
|
||||
* @property array|null $source
|
||||
* @property string|null $business_model
|
||||
* @property string|null $commercial_support_url
|
||||
* @property string|null $support_url
|
||||
* @property string|null $preview_link
|
||||
* @property string|null $repository_url
|
||||
* @property array|null $requires_plugins
|
||||
* @property array|null $compatibility
|
||||
* @property array|null $screenshots
|
||||
* @property array|null $sections
|
||||
* @property array|null $versions
|
||||
* @property array|null $upgrade_notice
|
||||
* @property array<string, string> $tags
|
||||
*/
|
||||
final class Plugin extends BaseModel
|
||||
{
|
||||
|
@ -66,6 +66,9 @@ final class Plugin extends BaseModel
|
|||
|
||||
protected $table = 'plugins';
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
protected $appends = ['tags'];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
|
@ -129,7 +132,7 @@ final class Plugin extends BaseModel
|
|||
|
||||
public static function getOrCreateFromSyncPlugin(SyncPlugin $syncPlugin): self
|
||||
{
|
||||
return self::where('sync_id', $syncPlugin->id)->first() ?? self::createFromSyncPlugin($syncPlugin);
|
||||
return self::query()->firstWhere('sync_id', $syncPlugin->id) ?? self::createFromSyncPlugin($syncPlugin);
|
||||
}
|
||||
|
||||
public static function createFromSyncPlugin(SyncPlugin $syncPlugin): self
|
||||
|
@ -176,10 +179,11 @@ final class Plugin extends BaseModel
|
|||
$pluginTags = [];
|
||||
$this->tags()->detach();
|
||||
foreach ($data['tags'] as $tagSlug => $name) {
|
||||
$themeTags[] = PluginTag::firstOrCreate(['slug' => $tagSlug], ['slug' => $tagSlug, 'name' => $name]);
|
||||
$pluginTags[] = PluginTag::firstOrCreate(['slug' => $tagSlug], ['slug' => $tagSlug, 'name' => $name]);
|
||||
}
|
||||
$this->tags()->saveMany($pluginTags);
|
||||
}
|
||||
|
||||
return $this->fill([
|
||||
'name' => $data['name'],
|
||||
'short_description' => self::truncate($data['short_description'] ?? '', 149),
|
||||
|
@ -202,7 +206,7 @@ final class Plugin extends BaseModel
|
|||
'downloaded' => $data['downloaded'] ?? '',
|
||||
'homepage' => $data['homepage'] ?? null,
|
||||
'banners' => $data['banners'] ?? null,
|
||||
'tags' => $data['tags'] ?? null,
|
||||
// 'tags' => $data['tags'] ?? null,
|
||||
'donate_link' => self::truncate($data['donate_link'] ?? null, 1024),
|
||||
'contributors' => $data['contributors'] ?? null,
|
||||
'icons' => $data['icons'] ?? null,
|
||||
|
@ -232,4 +236,17 @@ final class Plugin extends BaseModel
|
|||
{
|
||||
return $str === null ? $str : mb_substr($str, 0, $len, 'utf8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tags attribute.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function getTagsAttribute(): array
|
||||
{
|
||||
return $this->tags()
|
||||
->get()
|
||||
->pluck('name', 'slug')
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,13 +3,17 @@
|
|||
namespace App\Models\WpOrg;
|
||||
|
||||
use App\Models\BaseModel;
|
||||
use Database\Factories\WpOrg\PluginTagFactory;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
|
||||
/**
|
||||
* @property string $id
|
||||
* @property string $plugi
|
||||
* @property-read string $id
|
||||
* @property string $slug
|
||||
* @property string $name
|
||||
* @property Collection<Plugin> $plugins
|
||||
*/
|
||||
final class PluginTag extends BaseModel
|
||||
{
|
||||
|
@ -17,6 +21,9 @@ final class PluginTag extends BaseModel
|
|||
|
||||
use HasUuids;
|
||||
|
||||
/** @use HasFactory<PluginTagFactory> */
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'plugin_tags';
|
||||
|
||||
protected function casts(): array
|
||||
|
@ -39,7 +46,7 @@ final class PluginTag extends BaseModel
|
|||
*/
|
||||
public function plugins(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Plugin::class, 'plugin_plugin_tags', 'tag_id', 'plugin_id');
|
||||
return $this->belongsToMany(Plugin::class, 'plugin_plugin_tags', 'plugin_tag_id', 'plugin_id');
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\WpOrg\Plugin;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class PluginInformationService
|
||||
{
|
||||
public function findBySlug(string $slug): ?Plugin
|
||||
{
|
||||
return Plugin::query()->where('slug', $slug)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Query plugins with filters and pagination
|
||||
*
|
||||
* @return array{
|
||||
* plugins: Collection<int, Plugin>,
|
||||
* page: int,
|
||||
* totalPages: int,
|
||||
* total: int
|
||||
* }
|
||||
*/
|
||||
public function queryPlugins(
|
||||
int $page,
|
||||
int $perPage,
|
||||
?string $search = null,
|
||||
?string $tag = null,
|
||||
?string $author = null,
|
||||
string $browse = 'popular',
|
||||
): array {
|
||||
$query = Plugin::query()
|
||||
->when($search, function (Builder $query, string $search) {
|
||||
$query->where(function (Builder $q) use ($search) {
|
||||
$q->where('name', 'ilike', "%{$search}%")
|
||||
->orWhere('short_description', 'like', "%{$search}%")
|
||||
->orWhereFullText('description', $search);
|
||||
});
|
||||
})
|
||||
->when($tag, function (Builder $query, string $tag) {
|
||||
$query->whereJsonContains('tags', $tag);
|
||||
})
|
||||
->when($author, function (Builder $query, string $author) {
|
||||
$query->where('author', 'like', "%{$author}%");
|
||||
});
|
||||
|
||||
$this->applyBrowseSort($query, $browse);
|
||||
|
||||
$total = $query->count();
|
||||
$totalPages = (int) ceil($total / $perPage);
|
||||
|
||||
$plugins = $query
|
||||
->offset(($page - 1) * $perPage)
|
||||
->limit($perPage)
|
||||
->get();
|
||||
|
||||
return [
|
||||
'plugins' => $plugins,
|
||||
'page' => $page,
|
||||
'totalPages' => $totalPages,
|
||||
'total' => $total,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply sorting based on browse parameter
|
||||
*
|
||||
* @param Builder<Plugin> $query
|
||||
*/
|
||||
private function applyBrowseSort(Builder $query, string $browse): void
|
||||
{
|
||||
match ($browse) {
|
||||
'new' => $query->orderBy('added', 'desc'),
|
||||
'updated' => $query->orderBy('last_updated', 'desc'),
|
||||
'top-rated' => $query->orderBy('rating', 'desc'),
|
||||
default => $query->orderBy('active_installs', 'desc'),
|
||||
};
|
||||
}
|
||||
}
|
33
app/Services/Plugins/PluginHotTagsService.php
Normal file
33
app/Services/Plugins/PluginHotTagsService.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Plugins;
|
||||
|
||||
use App\Data\WpOrg\Themes\ThemeHotTagsResponse;
|
||||
use App\Models\WpOrg\PluginTag;
|
||||
|
||||
class PluginHotTagsService
|
||||
{
|
||||
/**
|
||||
* Gets the top tags by plugin count
|
||||
*
|
||||
* @return array<string, array{
|
||||
* name: string,
|
||||
* slug: string,
|
||||
* count: int,
|
||||
* }> */
|
||||
public function getHotTags(int $count = -1): array
|
||||
{
|
||||
$hotTags = PluginTag::withCount('plugins')
|
||||
->orderBy('plugins_count', 'desc')
|
||||
->limit($count >= 0 ? $count : 100)
|
||||
->get(['slug', 'name', 'plugins_count'])
|
||||
->map(function ($tag) {
|
||||
return [
|
||||
'name' => (string) $tag->name,
|
||||
'slug' => (string) $tag->slug,
|
||||
'count' => (int) $tag->plugins_count,
|
||||
];
|
||||
});
|
||||
return ThemeHotTagsResponse::fromCollection($hotTags)->toArray();
|
||||
}
|
||||
}
|
13
app/Services/Plugins/PluginInformationService.php
Normal file
13
app/Services/Plugins/PluginInformationService.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Plugins;
|
||||
|
||||
use App\Models\WpOrg\Plugin;
|
||||
|
||||
class PluginInformationService
|
||||
{
|
||||
public function findBySlug(string $slug): ?Plugin
|
||||
{
|
||||
return Plugin::query()->where('slug', $slug)->first();
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
namespace App\Services\Plugins;
|
||||
|
||||
use App\Models\WpOrg\Plugin;
|
||||
use Illuminate\Support\Collection;
|
89
app/Services/Plugins/QueryPluginsService.php
Normal file
89
app/Services/Plugins/QueryPluginsService.php
Normal file
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Plugins;
|
||||
|
||||
use App\Models\WpOrg\Plugin;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class QueryPluginsService
|
||||
{
|
||||
/**
|
||||
* Query plugins with filters and pagination
|
||||
*
|
||||
* @return array{
|
||||
* plugins: Collection<int, Plugin>,
|
||||
* page: int,
|
||||
* totalPages: int,
|
||||
* total: int
|
||||
* }
|
||||
*/
|
||||
public function queryPlugins(
|
||||
int $page,
|
||||
int $perPage,
|
||||
?string $search = null,
|
||||
?string $tag = null, // TODO: make this work with more than one tag, the way Themes do
|
||||
?string $author = null,
|
||||
string $browse = 'popular',
|
||||
): array {
|
||||
$query = Plugin::query()
|
||||
->when($browse, self::applyBrowse(...))
|
||||
->when($search, self::applySearch(...))
|
||||
->when($tag, self::applyTag(...))
|
||||
->when($author, self::applyAuthor(...));
|
||||
|
||||
$total = $query->count();
|
||||
$totalPages = (int) ceil($total / $perPage);
|
||||
|
||||
$plugins = $query
|
||||
->offset(($page - 1) * $perPage)
|
||||
->limit($perPage)
|
||||
->get();
|
||||
|
||||
return [
|
||||
'plugins' => $plugins,
|
||||
'page' => $page,
|
||||
'totalPages' => $totalPages,
|
||||
'total' => $total,
|
||||
];
|
||||
}
|
||||
|
||||
/** @param Builder<Plugin> $query */
|
||||
private static function applySearch(Builder $query, string $search): void
|
||||
{
|
||||
$query->where(function (Builder $q) use ($search) {
|
||||
$q->where('slug', 'like', "%{$search}%")
|
||||
->orWhere('name', 'like', "%{$search}%")
|
||||
->orWhere('short_description', 'like', "%{$search}%")
|
||||
->orWhereFullText('description', $search);
|
||||
});
|
||||
}
|
||||
|
||||
/** @param Builder<Plugin> $query */
|
||||
private static function applyAuthor(Builder $query, string $author): void
|
||||
{
|
||||
$query->whereLike('author', $author);
|
||||
}
|
||||
|
||||
/** @param Builder<Plugin> $query */
|
||||
private static function applyTag(Builder $query, string $tag): void
|
||||
{
|
||||
$query->whereHas('tags', fn(Builder $q) => $q->whereIn('slug', [$tag]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply sorting based on browse parameter
|
||||
*
|
||||
* @param Builder<Plugin> $query
|
||||
*/
|
||||
private static function applyBrowse(Builder $query, string $browse): void
|
||||
{
|
||||
// TODO: replicate 'featured' browse (currently it's identical to 'popular')
|
||||
match ($browse) {
|
||||
'new' => $query->reorder('added', 'desc'),
|
||||
'updated' => $query->reorder('last_updated', 'desc'),
|
||||
'top-rated', 'popular', 'featured' => $query->reorder('rating', 'desc'),
|
||||
default => $query->reorder('active_installs', 'desc'),
|
||||
};
|
||||
}
|
||||
}
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
namespace App\Services\Themes;
|
||||
|
||||
use App\Data\WpOrg\Themes\HotTagsResponse;
|
||||
use App\Data\WpOrg\Themes\ThemeHotTagsResponse;
|
||||
use App\Models\WpOrg\ThemeTag;
|
||||
|
||||
class HotTagsService
|
||||
class ThemeHotTagsService
|
||||
{
|
||||
/**
|
||||
* Gets the top tags by theme count
|
||||
|
@ -28,6 +28,6 @@ class HotTagsService
|
|||
'count' => (int) $tag->themes_count,
|
||||
];
|
||||
});
|
||||
return HotTagsResponse::fromCollection($hotTags)->toArray();
|
||||
return ThemeHotTagsResponse::fromCollection($hotTags)->toArray();
|
||||
}
|
||||
}
|
|
@ -3,9 +3,9 @@
|
|||
namespace App\Services\Themes;
|
||||
|
||||
use App\Data\WpOrg\Themes\ThemeInformationRequest;
|
||||
use App\Exceptions\NotFoundException;
|
||||
use App\Http\Resources\ThemeResource;
|
||||
use App\Models\WpOrg\Theme;
|
||||
use App\NotFoundException;
|
||||
|
||||
class ThemeInformationService
|
||||
{
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace Database\Factories\WpOrg;
|
|||
|
||||
use App\Models\Sync\SyncPlugin;
|
||||
use App\Models\WpOrg\Plugin;
|
||||
use App\Models\WpOrg\PluginTag;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
|
@ -50,7 +51,6 @@ class PluginFactory extends Factory
|
|||
'low' => $this->faker->imageUrl(772, 250),
|
||||
'high' => $this->faker->imageUrl(1544, 500),
|
||||
],
|
||||
'tags' => $this->generateTags(),
|
||||
'donate_link' => $this->faker->optional()->url(),
|
||||
'contributors' => $this->generateContributors(),
|
||||
'icons' => [
|
||||
|
@ -75,14 +75,6 @@ class PluginFactory extends Factory
|
|||
];
|
||||
}
|
||||
|
||||
protected function generateTags(): array
|
||||
{
|
||||
$possibleTags = ['seo', 'security', 'social-media', 'woocommerce', 'forms', 'widgets',
|
||||
'admin', 'marketing', 'analytics', 'backup', 'cache', 'performance'];
|
||||
|
||||
return $this->faker->randomElements($possibleTags, $this->faker->numberBetween(2, 6));
|
||||
}
|
||||
|
||||
protected function generateContributors(): array
|
||||
{
|
||||
$contributors = [];
|
||||
|
@ -256,4 +248,38 @@ class PluginFactory extends Factory
|
|||
'active_installs' => $this->faker->numberBetween(100000, 1000000),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the model factory to create a plugin with tags
|
||||
*/
|
||||
public function withTags(int $count = 3): static
|
||||
{
|
||||
return $this->afterCreating(function (Plugin $plugin) use ($count) {
|
||||
$tags = PluginTag::factory()->count($count)->create();
|
||||
$plugin->tags()->attach($tags->pluck('id'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the model factory to create a plugin with specific tags
|
||||
* If tags already exist, they will be reused instead of creating duplicates
|
||||
*/
|
||||
public function withSpecificTags(array $tagNames): static
|
||||
{
|
||||
return $this->afterCreating(function (Plugin $plugin) use ($tagNames) {
|
||||
$tags = collect($tagNames)->map(function ($tagName) {
|
||||
$slug = Str::slug($tagName);
|
||||
|
||||
return PluginTag::query()->firstOrCreate(
|
||||
['slug' => $slug],
|
||||
[
|
||||
'id' => $this->faker->uuid(),
|
||||
'name' => $tagName,
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
$plugin->tags()->attach($tags->pluck('id'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
24
database/factories/WpOrg/PluginTagFactory.php
Normal file
24
database/factories/WpOrg/PluginTagFactory.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories\WpOrg;
|
||||
|
||||
use App\Models\WpOrg\PluginTag;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class PluginTagFactory extends Factory
|
||||
{
|
||||
protected $model = PluginTag::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
$name = $this->faker->words(3, true);
|
||||
$slug = Str::slug($name);
|
||||
|
||||
return [
|
||||
'id' => $this->faker->uuid(),
|
||||
'slug' => $slug,
|
||||
'name' => $name,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('plugins', function (Blueprint $table) {
|
||||
$table->dropColumn('tags');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('plugins', function (Blueprint $table) {
|
||||
$table->jsonb('tags')->nullable();
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('theme_theme_tags', function (Blueprint $table) {
|
||||
$table->dropForeign('theme_theme_tags_theme_id_foreign');
|
||||
$table->dropForeign('theme_theme_tags_theme_tag_id_foreign');
|
||||
$table->foreign('theme_id')->references('id')->on('themes')->onDelete('cascade');
|
||||
$table->foreign('theme_tag_id')->references('id')->on('theme_tags')->onDelete('cascade');
|
||||
});
|
||||
|
||||
Schema::table('plugin_plugin_tags', function (Blueprint $table) {
|
||||
$table->dropForeign('plugin_plugin_tags_plugin_id_foreign');
|
||||
$table->dropForeign('plugin_plugin_tags_plugin_tag_id_foreign');
|
||||
$table->foreign('plugin_id')->references('id')->on('plugins')->onDelete('cascade');
|
||||
$table->foreign('plugin_tag_id')->references('id')->on('plugin_tags')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('theme_theme_tags', function (Blueprint $table) {
|
||||
$table->dropForeign('theme_theme_tags_theme_id_foreign');
|
||||
$table->dropForeign('theme_theme_tags_theme_tag_id_foreign');
|
||||
$table->foreign('theme_id')->references('id')->on('themes')->onDelete('restrict');
|
||||
$table->foreign('theme_tag_id')->references('id')->on('theme_tags')->onDelete('restrict');
|
||||
});
|
||||
|
||||
Schema::table('plugin_plugin_tags', function (Blueprint $table) {
|
||||
$table->dropForeign('plugin_plugin_tags_plugin_id_foreign');
|
||||
$table->dropForeign('plugin_plugin_tags_plugin_tag_id_foreign');
|
||||
$table->foreign('plugin_id')->references('id')->on('plugins')->onDelete('restrict');
|
||||
$table->foreign('plugin_tag_id')->references('id')->on('plugin_tags')->onDelete('restrict');
|
||||
});
|
||||
}
|
||||
};
|
|
@ -1,2 +0,0 @@
|
|||
grumphp:
|
||||
tasks: { phpcsfixer: null }
|
6
grumphp.yml.dist
Normal file
6
grumphp.yml.dist
Normal file
|
@ -0,0 +1,6 @@
|
|||
grumphp:
|
||||
tasks:
|
||||
phpcsfixer:
|
||||
config: .php-cs-fixer.dist.php
|
||||
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
// Note: api routes are not prefixed, i.e. all routes in here are from the root like web routes
|
||||
|
||||
use App\Http\Controllers\API\WpOrg\Core\ImportersController;
|
||||
use App\Http\Controllers\API\WpOrg\Plugins\PluginInformation_1_2_Controller;
|
||||
use App\Http\Controllers\API\WpOrg\Plugins\PluginUpdateCheck_1_1_Controller;
|
||||
use App\Http\Controllers\API\WpOrg\SecretKey\SecretKeyController;
|
||||
|
@ -38,7 +39,7 @@ $routeDefinition
|
|||
$router->get('/core/checksums/{version}', CatchAllController::class)->where(['version' => '1.0']);
|
||||
$router->get('/core/credits/{version}', CatchAllController::class)->where(['version' => '1.[01]']);
|
||||
$router->get('/core/handbook/{version}', CatchAllController::class)->where(['version' => '1.0']);
|
||||
$router->get('/core/importers/{version}', CatchAllController::class)->where(['version' => '1.[01]']);
|
||||
$router->get('/core/importers/{version}', ImportersController::class)->where(['version' => '1.[01]']);
|
||||
$router->get('/core/serve-happy/{version}', CatchAllController::class)->where(['version' => '1.0']);
|
||||
$router->get('/core/stable-check/{version}', CatchAllController::class)->where(['version' => '1.0']);
|
||||
$router->get('/core/version-check/{version}', CatchAllController::class)->where(['version' => '1.[67]']);
|
||||
|
|
|
@ -2,24 +2,8 @@
|
|||
|
||||
use App\Models\WpOrg\Plugin;
|
||||
|
||||
beforeEach(function () {
|
||||
Plugin::factory()->create([
|
||||
'name' => 'JWT Auth',
|
||||
'slug' => 'jwt-auth',
|
||||
'tags' => ['authentication', 'jwt', 'api'],
|
||||
]);
|
||||
|
||||
Plugin::factory()->create([
|
||||
'name' => 'JWT Authentication for WP-API',
|
||||
'slug' => 'jwt-authentication-for-wp-rest-api',
|
||||
'tags' => ['jwt', 'api', 'rest-api'],
|
||||
'author' => 'tmeister',
|
||||
]);
|
||||
|
||||
Plugin::factory()->count(8)->create();
|
||||
});
|
||||
|
||||
it('returns 400 when slug is missing', function () {
|
||||
Plugin::factory(10)->create();
|
||||
$response = makeApiRequest('GET', '/plugins/info/1.2?action=plugin_information');
|
||||
|
||||
$response->assertStatus(400)
|
||||
|
@ -29,6 +13,7 @@ it('returns 400 when slug is missing', function () {
|
|||
});
|
||||
|
||||
it('returns 404 when plugin does not exist', function () {
|
||||
Plugin::factory(10)->create();
|
||||
$response = makeApiRequest('GET', '/plugins/info/1.2?action=plugin_information&slug=non-existent-plugin');
|
||||
|
||||
$response->assertStatus(404)
|
||||
|
@ -38,6 +23,12 @@ it('returns 404 when plugin does not exist', function () {
|
|||
});
|
||||
|
||||
it('returns plugin information in wp.org format', function () {
|
||||
Plugin::factory()->create([
|
||||
'name' => 'JWT Authentication for WP-API',
|
||||
'slug' => 'jwt-authentication-for-wp-rest-api',
|
||||
]);
|
||||
Plugin::factory(9)->create();
|
||||
|
||||
$response = makeApiRequest('GET', '/plugins/info/1.2?action=plugin_information&slug=jwt-authentication-for-wp-rest-api');
|
||||
|
||||
$response->assertStatus(200)
|
||||
|
@ -49,14 +40,15 @@ it('returns plugin information in wp.org format', function () {
|
|||
});
|
||||
|
||||
it('returns search results by tag in wp.org format', function () {
|
||||
$tag = 'jwt';
|
||||
$tags = ['jwt', 'authentication', 'rest api'];
|
||||
$tagToQuery = 'jwt';
|
||||
|
||||
Plugin::factory(8)->create();
|
||||
Plugin::factory()->count(2)->withSpecificTags($tags)->create();
|
||||
|
||||
expect(Plugin::query()->count())->toBe(10);
|
||||
|
||||
$jwtPlugins = Plugin::query()->where('tags', 'ilike', '%' . $tag . '%')->count();
|
||||
expect($jwtPlugins)->toBe(2);
|
||||
|
||||
$response = makeApiRequest('GET', '/plugins/info/1.2?action=query_plugins&tag=' . $tag);
|
||||
$response = makeApiRequest('GET', '/plugins/info/1.2?action=query_plugins&tag=' . $tagToQuery);
|
||||
|
||||
$response->assertStatus(200);
|
||||
assertWpPluginAPIStructureForSearch($response);
|
||||
|
@ -74,12 +66,19 @@ it('returns search results by tag in wp.org format', function () {
|
|||
->and($responseData['info']['results'])->toBe(2);
|
||||
|
||||
foreach ($responseData['plugins'] as $plugin) {
|
||||
expect($plugin['tags'])->toContain($tag);
|
||||
expect($plugin['tags'])->toContain($tagToQuery);
|
||||
}
|
||||
});
|
||||
|
||||
it('returns search results by query string in wp.org format', function () {
|
||||
$query = 'jwt';
|
||||
Plugin::factory()->create([
|
||||
'name' => 'JWT Authentication for WP-API',
|
||||
'slug' => 'jwt-authentication-for-wp-rest-api',
|
||||
]);
|
||||
|
||||
Plugin::factory(9)->create();
|
||||
|
||||
expect(Plugin::query()->count())->toBe(10);
|
||||
|
||||
$response = makeApiRequest('GET', '/plugins/info/1.2?action=query_plugins&search=' . $query);
|
||||
|
@ -88,7 +87,7 @@ it('returns search results by query string in wp.org format', function () {
|
|||
assertWpPluginAPIStructureForSearch($response);
|
||||
|
||||
$responseData = $response->json();
|
||||
expect(count($responseData['plugins']))->toBe(2)
|
||||
expect(count($responseData['plugins']))->toBe(1)
|
||||
->and($responseData['info'])->toHaveKeys([
|
||||
'page',
|
||||
'pages',
|
||||
|
@ -96,16 +95,23 @@ it('returns search results by query string in wp.org format', function () {
|
|||
])
|
||||
->and($responseData['info']['page'])->toBe(1)
|
||||
->and($responseData['info']['pages'])->toBe(1)
|
||||
->and($responseData['info']['results'])->toBe(2);
|
||||
->and($responseData['info']['results'])->toBe(1);
|
||||
});
|
||||
|
||||
it('returns search results by tag and author in wp.org format', function () {
|
||||
$tag = 'jwt';
|
||||
$tags = ['jwt', 'authentication', 'rest api'];
|
||||
$tagToQuery = 'jwt';
|
||||
$author = 'tmeister';
|
||||
|
||||
Plugin::factory(9)->create();
|
||||
Plugin::factory()->count(1)
|
||||
->withSpecificTags($tags)->create([
|
||||
'author' => $author,
|
||||
]);
|
||||
|
||||
expect(Plugin::query()->count())->toBe(10);
|
||||
|
||||
$response = makeApiRequest('GET', '/plugins/info/1.2?action=query_plugins&tag=' . $tag . '&author=' . $author);
|
||||
$response = makeApiRequest('GET', '/plugins/info/1.2?action=query_plugins&tag=' . $tagToQuery . '&author=' . $author);
|
||||
|
||||
$response->assertStatus(200);
|
||||
assertWpPluginAPIStructureForSearch($response);
|
||||
|
@ -126,6 +132,7 @@ it('returns a valid pagination', function () {
|
|||
$perPage = 2;
|
||||
$page = 2;
|
||||
|
||||
Plugin::factory(10)->create();
|
||||
expect(Plugin::query()->count())->toBe(10);
|
||||
|
||||
$response = makeApiRequest('GET', '/plugins/info/1.2?action=query_plugins&per_page=' . $perPage . '&page=' . $page);
|
||||
|
|
|
@ -134,7 +134,6 @@ function assertWpPluginAPIStructure($response)
|
|||
'support_threads',
|
||||
'support_threads_resolved',
|
||||
'active_installs',
|
||||
'downloaded',
|
||||
'last_updated',
|
||||
'added',
|
||||
'homepage',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue