diff --git a/app/Http/Controllers/SubProjectController.php b/app/Http/Controllers/SubProjectController.php index 041eed7146..66505aefe0 100644 --- a/app/Http/Controllers/SubProjectController.php +++ b/app/Http/Controllers/SubProjectController.php @@ -2,12 +2,17 @@ namespace App\Http\Controllers; +use App\Models\SubProject as EloquentSubProject; +use App\Models\SubProjectGroup as EloquentSubProjectGroup; use App\Models\User; use App\Services\ProjectService; use App\Utils\PageTimer; +use CDash\Model\Project; use CDash\Model\SubProject; +use CDash\Model\SubProjectGroup; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Http\Response; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; @@ -107,6 +112,158 @@ public function apiManageSubProject(): JsonResponse return response()->json(cast_data_for_JSON($response)); } + public function apiSubProject(Request $request): JsonResponse|Response + { + if (!$request->has('projectid')) { + abort(400, 'projectid not specified.'); + } + $projectid = $request->integer('projectid'); + + // Make sure the user has access to this page. + $project = new Project(); + $project->Id = $projectid; + if (!Gate::allows('edit-project', $project)) { + abort(403, "You don't have the permissions to access this page ($projectid)"); + } + + // Route based on what type of request this is. + return match ($request->method()) { + 'DELETE' => self::apiSubProjectDelete($request), + 'POST' => self::apiSubProjectPost($request, $projectid), + 'PUT' => self::apiSubProjectPut($request, $projectid), + default => self::apiSubProjectGet($request, $projectid), + }; + } + + private static function apiSubProjectGet(Request $request, int $projectid): JsonResponse + { + $subprojectid = self::getSubProjectIdFromRequest($request); + + $pageTimer = new PageTimer(); + $response = begin_JSON_response(); + $response['projectid'] = $projectid; + $response['subprojectid'] = $subprojectid; + + $SubProject = new SubProject(); + $SubProject->SetId($subprojectid); + $response['name'] = $SubProject->GetName(); + $response['group'] = $SubProject->GetGroupId(); + + $subprojects = EloquentSubProject::where('projectid', $projectid) + ->where('endtime', '1980-01-01 00:00:00') + ->get(['id', 'name']); + + $dependencies = $SubProject->GetDependencies(); + $dependencies_response = []; + $available_dependencies_response = []; + + foreach ($subprojects as $subproject) { + if ($subproject->id === $subprojectid) { + continue; + } + $subproject_response = [ + 'id' => $subproject->id, + 'name' => $subproject->name, + ]; + if (in_array($subproject->id, $dependencies, true)) { + $dependencies_response[] = $subproject_response; + } else { + $available_dependencies_response[] = $subproject_response; + } + } + + $response['dependencies'] = $dependencies_response; + $response['available_dependencies'] = $available_dependencies_response; + + $pageTimer->end($response); + return response()->json(cast_data_for_JSON($response)); + } + + private static function apiSubProjectDelete(Request $request): Response + { + if ($request->has('groupid')) { + // Delete subproject group. + $Group = new SubProjectGroup(); + $Group->SetId($request->integer('groupid')); + $Group->Delete(); + } + + return response()->noContent(); + } + + private static function apiSubProjectPost(Request $request, int $projectid): JsonResponse|Response + { + $response = response()->noContent(); + + if ($request->has('newgroup')) { + // Create a new group + $Group = new SubProjectGroup(); + $Group->SetProjectId($projectid); + $Group->SetName(htmlspecialchars($request->string('newgroup')->toString())); + if ($request->has('isdefault')) { + $Group->SetIsDefault($request->input('isdefault') === 'true' ? 1 : 0); + } + $Group->SetCoverageThreshold($request->integer('threshold')); + $Group->Save(); + + // Respond with a JSON representation of this new group + $response = response()->json(cast_data_for_JSON([ + 'id' => $Group->GetId(), + 'name' => $Group->GetName(), + 'is_default' => $Group->GetIsDefault(), + 'coverage_threshold' => $Group->GetCoverageThreshold(), + ])); + } + + if ($request->has('newLayout')) { + // Update the order of the SubProject groups. + foreach (array_keys((array) $request->input('newLayout')) as $index) { + // TODO: (williamjallen) refactor this to execute a constant number of queries + EloquentSubProjectGroup::findOrFail($request->integer("newLayout.{$index}.id")) + ->update([ + 'position' => $request->integer("newLayout.{$index}.position"), + ]); + } + } + + return $response; + } + + private static function apiSubProjectPut(Request $request, int $projectid): Response + { + if ($request->has('threshold')) { + // Modify an existing subproject group. + $Group = new SubProjectGroup(); + $Group->SetProjectId($projectid); + $Group->SetId($request->integer('groupid')); + $Group->SetName($request->string('name')->toString()); + $Group->SetCoverageThreshold($request->integer('threshold')); + $Group->SetIsDefault($request->input('is_default') === 'true' ? 1 : 0); + $Group->Save(); + + return response()->noContent(); + } + + $SubProject = new SubProject(); + $SubProject->SetId(self::getSubProjectIdFromRequest($request)); + + if ($request->has('groupname')) { + // Change which group a subproject belongs to. + $SubProject->SetGroup($request->string('groupname')->toString()); + $SubProject->Save(); + } + + return response()->noContent(); + } + + private static function getSubProjectIdFromRequest(Request $request): int + { + if (!$request->has('subprojectid')) { + abort(400, 'subprojectid not specified.'); + } + return $request->integer('subprojectid'); + } + public function dependenciesGraph(Request $request, string $project): View { $this->setProjectByName($project); @@ -296,7 +453,7 @@ public function apiDependenciesGraph(): JsonResponse } $result = []; // array to store the all the result - /** @var \App\Models\SubProject $subproject */ + /** @var EloquentSubProject $subproject */ foreach ($subprojects as $subproject) { $subarray = [ 'name' => $subproject->name, diff --git a/app/cdash/public/api/v1/subproject.php b/app/cdash/public/api/v1/subproject.php deleted file mode 100644 index a6b10afa51..0000000000 --- a/app/cdash/public/api/v1/subproject.php +++ /dev/null @@ -1,234 +0,0 @@ -Id = $projectid; -if (!Gate::allows('edit-project', $project)) { - echo_error("You don't have the permissions to access this page ($projectid)", 403); - return; -} - -// Route based on what type of request this is. -$method = $_SERVER['REQUEST_METHOD']; - -switch ($method) { - case 'DELETE': - rest_delete(); - break; - case 'POST': - rest_post($projectid); - break; - case 'PUT': - rest_put($projectid); - break; - case 'GET': - default: - rest_get($projectid); - break; -} - -/** Handle GET requests */ -function rest_get($projectid): bool -{ - $subprojectid = get_subprojectid(); - if ($subprojectid === false) { - return true; - } - - $projectid = intval($projectid); - - $pageTimer = new PageTimer(); - $response = begin_JSON_response(); - $response['projectid'] = $projectid; - $response['subprojectid'] = $subprojectid; - - $SubProject = new SubProject(); - $SubProject->SetId($subprojectid); - $response['name'] = $SubProject->GetName(); - $response['group'] = $SubProject->GetGroupId(); - - $db = Database::getInstance(); - - $query = $db->executePrepared(" - SELECT id, name - FROM subproject - WHERE projectid=? AND endtime='1980-01-01 00:00:00' - ", [$projectid]); - - if ($query === false) { - return false; - } - - $dependencies = $SubProject->GetDependencies(); - $dependencies_response = []; - $available_dependencies_response = []; - - foreach ($query as $row) { - if (intval($row['id']) === $subprojectid) { - continue; - } - if (in_array((int) $row['id'], $dependencies, true)) { - $dep = []; - $dep['id'] = intval($row['id']); - $dep['name'] = $row['name']; - $dependencies_response[] = $dep; - } else { - $avail = []; - $avail['id'] = intval($row['id']); - $avail['name'] = $row['name']; - $available_dependencies_response[] = $avail; - } - } - - $response['dependencies'] = $dependencies_response; - $response['available_dependencies'] = $available_dependencies_response; - - $pageTimer->end($response); - echo json_encode(cast_data_for_JSON($response)); - - return true; -} - -/** Handle DELETE requests */ -function rest_delete(): void -{ - if (isset($_GET['groupid'])) { - // Delete subproject group. - $Group = new SubProjectGroup(); - $Group->SetId((int) $_GET['groupid']); - $Group->Delete(); - return; - } -} - -/** Handle POST requests */ -function rest_post($projectid): void -{ - if (isset($_POST['newgroup'])) { - // Create a new group - $Group = new SubProjectGroup(); - $Group->SetProjectId(intval($projectid)); - - $newGroup = htmlspecialchars($_POST['newgroup'] ?? ''); - $Group->SetName($newGroup); - if (isset($_POST['isdefault'])) { - $Group->SetIsDefault($_POST['isdefault'] === 'true' ? 1 : 0); - } - $Group->SetCoverageThreshold(intval($_POST['threshold'])); - $Group->Save(); - - // Respond with a JSON representation of this new group - $response = []; - $response['id'] = $Group->GetId(); - $response['name'] = $Group->GetName(); - $response['is_default'] = $Group->GetIsDefault(); - $response['coverage_threshold'] = $Group->GetCoverageThreshold(); - echo json_encode(cast_data_for_JSON($response)); - } - - if (isset($_POST['newLayout'])) { - // Update the order of the SubProject groups. - $inputRows = $_POST['newLayout']; - foreach ($inputRows as $inputRow) { - // TODO: (williamjallen) refactor this to execute a constant number of queries - \App\Models\SubProjectGroup::findOrFail((int) ($inputRow['id'] ?? 0)) - ->update([ - 'position' => (int) ($inputRow['position'] ?? 0) - ]); - } - } -} - -/** Handle PUT requests */ -function rest_put($projectid): void -{ - if (isset($_GET['threshold'])) { - // Modify an existing subproject group. - $groupid = intval($_GET['groupid']); - $Group = new SubProjectGroup(); - $Group->SetProjectId(intval($projectid)); - $Group->SetId($groupid); - - $name = $_GET['name'] ?? ''; - $Group->SetName($name); - - $threshold = intval($_GET['threshold']); - $Group->SetCoverageThreshold($threshold); - - $Group->SetIsDefault($_GET['is_default'] === 'true' ? 1 : 0); - - $Group->Save(); - return; - } - - $subprojectid = get_subprojectid(); - if ($subprojectid === false) { - return; - } - $SubProject = new SubProject(); - $SubProject->SetId($subprojectid); - - if (isset($_GET['groupname'])) { - // Change which group a subproject belongs to. - $groupName = $_GET['groupname']; - $SubProject->SetGroup($groupName); - $SubProject->Save(); - } -} - -function get_subprojectid() -{ - if (!isset($_GET['subprojectid'])) { - echo_error('subprojectid not specified.'); - return false; - } - return intval($_GET['subprojectid']); -} - -function echo_error($msg, $status = 400) -{ - abort($status, $msg); -} diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index d465221ea3..df9bc7bf71 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -12963,117 +12963,6 @@ parameters: count: 1 path: app/cdash/public/api/v1/manageBuildGroup.php - - - rawMessage: 'Argument of an invalid type mixed supplied for foreach, only iterables are supported.' - identifier: foreach.nonIterable - count: 1 - path: app/cdash/public/api/v1/subproject.php - - - - rawMessage: ''' - Call to deprecated method executePrepared() of class CDash\Database: - 04/22/2023 Use Laravel query builder or Eloquent instead - ''' - identifier: method.deprecated - count: 1 - path: app/cdash/public/api/v1/subproject.php - - - - rawMessage: Cannot access offset 'id' on mixed. - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: app/cdash/public/api/v1/subproject.php - - - - rawMessage: Cannot access offset 'position' on mixed. - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: app/cdash/public/api/v1/subproject.php - - - - rawMessage: Cannot access offset 'projectid' on mixed. - identifier: offsetAccess.nonOffsetAccessible - count: 1 - path: app/cdash/public/api/v1/subproject.php - - - - rawMessage: Cannot cast mixed to int. - identifier: cast.int - count: 4 - path: app/cdash/public/api/v1/subproject.php - - - - rawMessage: 'Function CDash\Api\v1\SubProject\echo_error() has no return type specified.' - identifier: missingType.return - count: 1 - path: app/cdash/public/api/v1/subproject.php - - - - rawMessage: 'Function CDash\Api\v1\SubProject\echo_error() has parameter $msg with no type specified.' - identifier: missingType.parameter - count: 1 - path: app/cdash/public/api/v1/subproject.php - - - - rawMessage: 'Function CDash\Api\v1\SubProject\echo_error() has parameter $status with no type specified.' - identifier: missingType.parameter - count: 1 - path: app/cdash/public/api/v1/subproject.php - - - - rawMessage: 'Function CDash\Api\v1\SubProject\get_subprojectid() has no return type specified.' - identifier: missingType.return - count: 1 - path: app/cdash/public/api/v1/subproject.php - - - - rawMessage: 'Function CDash\Api\v1\SubProject\rest_get() has parameter $projectid with no type specified.' - identifier: missingType.parameter - count: 1 - path: app/cdash/public/api/v1/subproject.php - - - - rawMessage: 'Function CDash\Api\v1\SubProject\rest_post() has parameter $projectid with no type specified.' - identifier: missingType.parameter - count: 1 - path: app/cdash/public/api/v1/subproject.php - - - - rawMessage: 'Function CDash\Api\v1\SubProject\rest_put() has parameter $projectid with no type specified.' - identifier: missingType.parameter - count: 1 - path: app/cdash/public/api/v1/subproject.php - - - - rawMessage: 'Parameter #1 $groupName of method CDash\Model\SubProject::SetGroup() expects string, mixed given.' - identifier: argument.type - count: 1 - path: app/cdash/public/api/v1/subproject.php - - - - rawMessage: 'Parameter #1 $json of function json_decode expects string, string|false given.' - identifier: argument.type - count: 1 - path: app/cdash/public/api/v1/subproject.php - - - - rawMessage: 'Parameter #1 $name of method CDash\Model\SubProjectGroup::SetName() expects string, mixed given.' - identifier: argument.type - count: 1 - path: app/cdash/public/api/v1/subproject.php - - - - rawMessage: 'Parameter #1 $string of function htmlspecialchars expects string, mixed given.' - identifier: argument.type - count: 1 - path: app/cdash/public/api/v1/subproject.php - - - - rawMessage: 'Parameter #1 $value of function intval expects array|bool|float|GMP|int|resource|SimpleXMLElement|string|null, mixed given.' - identifier: argument.type - count: 4 - path: app/cdash/public/api/v1/subproject.php - - rawMessage: 'Call to an undefined method CDash\Test\UseCase\UseCase::setBuildCommand().' identifier: method.notFound diff --git a/routes/api.php b/routes/api.php index fab08827d8..3260495220 100755 --- a/routes/api.php +++ b/routes/api.php @@ -51,6 +51,8 @@ Route::middleware(['auth'])->group(function (): void { Route::get('/v1/manageSubProject.php', 'SubProjectController@apiManageSubProject'); + Route::match(['get', 'post', 'put', 'delete'], '/v1/subproject.php', 'SubProjectController@apiSubProject'); + Route::match(['get', 'post'], '/v1/manageOverview.php', 'ProjectOverviewController@apiManageOverview'); Route::middleware(['admin'])->group(function (): void {