diff --git a/app/Console/Commands/AddAdmin.php b/app/Console/Commands/AddAdmin.php new file mode 100644 index 0000000..d3d00e0 --- /dev/null +++ b/app/Console/Commands/AddAdmin.php @@ -0,0 +1,43 @@ + 'admin' . $admin->id . '@mail', + 'password' => 'password', + ]); + + $admin->user()->save($user); + + $this->info('Admin created successfully!'); + $this->info('email = ' . $user->email); + $this->info('password = password'); + } +} diff --git a/app/Console/Commands/AddScores.php b/app/Console/Commands/AddScores.php new file mode 100644 index 0000000..ff59f2f --- /dev/null +++ b/app/Console/Commands/AddScores.php @@ -0,0 +1,43 @@ +argument('grade')); + + $grade->students->random(7)->each(function ($student) { + $student + ->lessons() + ->syncWithPivotValues($student->lessons()->pluck('id'), ['score' => 4]); + }); + + $grade->students->random(5)->each(function ($student) { + $student + ->lessons() + ->syncWithPivotValues($student->lessons()->pluck('id'), ['score' => 5]); + }); + } +} diff --git a/app/Enums/ScoreEnum.php b/app/Enums/ScoreEnum.php index fd17bbe..f7f5121 100644 --- a/app/Enums/ScoreEnum.php +++ b/app/Enums/ScoreEnum.php @@ -5,11 +5,20 @@ namespace App\Enums; enum ScoreEnum: string { case WithoutScore = 'Без оценки'; - case One = '1'; case Two = '2'; case Three = '3'; case Four = '4'; case Five = '5'; case Absent = 'Н'; case Sick = 'Б'; + + public static function getNumScores(): array + { + return [self::Two, self::Three, self::Four, self::Five]; + } + + public static function getDebtScores(): array + { + return [self::Absent, self::Sick]; + } } diff --git a/app/Enums/TypeLesson.php b/app/Enums/TypeLesson.php index 545bcb6..9f140a7 100644 --- a/app/Enums/TypeLesson.php +++ b/app/Enums/TypeLesson.php @@ -8,4 +8,16 @@ enum TypeLesson: string case Classwork = "Работа в классе"; case TestClass = "Самостоятельная работа"; case ExamClass = "Контрольная работа"; + + public static function getShortType($type) + { + + return match ($type) { + self::Homework->value => "д/р", + self::Classwork->value => "кл/р", + self::TestClass->value => "с/р", + self::ExamClass->value => "к/р", + default => "-", + }; + } } diff --git a/app/Export/JournalExport.php b/app/Export/JournalExport.php new file mode 100644 index 0000000..8871fef --- /dev/null +++ b/app/Export/JournalExport.php @@ -0,0 +1,73 @@ +lessons = $lessons; + $this->students = $students; + } + + /** + * @return \Illuminate\Support\Collection + */ + public function collection() + { + $result = collect(); + + $headRow = collect(); + $headRow->push(''); + + $this->lessons->each(function ($lesson) use ($headRow) { + $headRow->push($lesson->date); + }); + $result->push($headRow); + + $headRow = collect(); + $headRow->push('ФИО'); + + $this->lessons->each(function ($lesson) use ($headRow) { + $headRow->push($lesson->shortType); + }); + + $result->push($headRow); + + $this->students->each(function ($student) use ($result){ + $row = collect(); + $row->push($student->fio); + + $this->lessons->each(function ($lesson) use ($row, $student) { + $row->push($student->lessons->find($lesson->id)->pivot->score ?? ScoreEnum::WithoutScore); + }); + + $result->push($row); + }); + + return $result; + } + + public function title(): string + { + return 'Журнал'; + } + + public function styles(Worksheet $sheet) + { + $sheet->getStyle($sheet->calculateWorksheetDimension()) + ->getAlignment() + ->setHorizontal(Alignment::HORIZONTAL_CENTER); + } +} diff --git a/app/Http/Controllers/GradeController.php b/app/Http/Controllers/GradeController.php index 0c94466..2dce6bc 100644 --- a/app/Http/Controllers/GradeController.php +++ b/app/Http/Controllers/GradeController.php @@ -4,7 +4,8 @@ namespace App\Http\Controllers; use App\Http\Requests\GradePostRequest; use App\Models\Grade; -use App\Services\ServiceInterface; +use App\Services\FileService; +use App\Services\GradeService; use Illuminate\Http\RedirectResponse; use Illuminate\View\View; @@ -13,10 +14,14 @@ class GradeController extends Controller /** * Display a listing of the resource. */ - public function index(): View + public function index(GradeService $service): View { + if(request()->user()->cannot('viewAny', Grade::class)) { + abort(403); + } + return view('grades.index', [ - 'grades' => Grade::filter()->paginate(5)->withQueryString(), + 'grades' => $service->getGrades(), ]); } @@ -25,6 +30,10 @@ class GradeController extends Controller */ public function create(): View { + if(request()->user()->cannot('create', Grade::class)) { + abort(403); + } + return view('grades.create'); } @@ -33,6 +42,10 @@ class GradeController extends Controller */ public function store(GradePostRequest $request): RedirectResponse { + if(request()->user()->cannot('create', Grade::class)) { + abort(403); + } + return redirect()->route('grades.show', Grade::create($request->validated())); } @@ -41,6 +54,10 @@ class GradeController extends Controller */ public function show(Grade $grade): View { + if(request()->user()->cannot('view', $grade)) { + abort(403); + } + return view('grades.show', [ 'grade' => $grade, 'subjects' => $grade->subjects, @@ -52,6 +69,10 @@ class GradeController extends Controller */ public function edit(Grade $grade): View { + if(request()->user()->cannot('update', $grade)) { + abort(403); + } + return view('grades.edit', [ 'grade' => $grade, ]); @@ -62,7 +83,13 @@ class GradeController extends Controller */ public function update(GradePostRequest $request, Grade $grade): RedirectResponse { - return redirect()->route('grades.show', $grade->update($request->validated())); + if(request()->user()->cannot('update', $grade)) { + abort(403); + } + + $grade->update($request->validated()); + + return redirect()->route('grades.show', $grade); } /** @@ -70,8 +97,17 @@ class GradeController extends Controller */ public function destroy(Grade $grade): RedirectResponse { + if(request()->user()->cannot('delete', $grade)) { + abort(403); + } + $grade->delete(); return redirect()->route('grades.index'); } + + public function listStudents(Grade $grade, FileService $fileService) + { + return $fileService->exportStudents($grade); + } } diff --git a/app/Http/Controllers/GradeSubjectController.php b/app/Http/Controllers/GradeSubjectController.php index d7ee45d..c06d6fa 100644 --- a/app/Http/Controllers/GradeSubjectController.php +++ b/app/Http/Controllers/GradeSubjectController.php @@ -5,7 +5,8 @@ namespace App\Http\Controllers; use App\Http\Requests\GradeSubjectPostRequest; use App\Models\Grade; use App\Models\Subject; -use App\Services\ServiceInterface; +use App\Services\FileService; +use App\Services\JournalService; use Illuminate\Http\RedirectResponse; use Illuminate\View\View; @@ -26,27 +27,25 @@ class GradeSubjectController extends Controller return redirect()->route('grades.show', $grade); } - public function edit(Grade $grade, Subject $subject): View - { - return view('grade-subject.edit', [ - 'grade' => $grade, - 'updateSubject' => $subject, - 'subjects' => Subject::all(), - ]); - } - - public function update(GradeSubjectPostRequest $request, Grade $grade, Subject $subject): RedirectResponse - { - $grade->subjects()->detach($subject); - $grade->subjects()->attach($request->subject_id); - - return redirect()->route('grades.show', $grade); - } - public function destroy(Grade $grade, Subject $subject): RedirectResponse { $grade->subjects()->detach($subject); return redirect()->route('grades.show', $grade); } + + public function journal(Grade $grade, Subject $subject, JournalService $service): View + { + return view('grade-subject.journal', [ + 'lessons' => $grade->lessons()->where('subject_id', $subject->id)->with('students')->get(), + 'students' => $grade->students()->orderBy('last_name')->get(), + 'grade' => $grade, + 'subject' => $subject, + ]); + } + + public function exportToExcel(Grade $grade, Subject $subject, FileService $service) + { + return $service->exportJournal($grade, $subject); + } } diff --git a/app/Http/Controllers/GradeTeacherController.php b/app/Http/Controllers/GradeTeacherController.php index 64e6bd2..0e20ad5 100644 --- a/app/Http/Controllers/GradeTeacherController.php +++ b/app/Http/Controllers/GradeTeacherController.php @@ -6,7 +6,6 @@ use App\Http\Requests\GradeTeacherPostRequest; use App\Models\Grade; use App\Models\Subject; use App\Models\Teacher; -use App\Services\ServiceInterface; use Illuminate\Http\RedirectResponse; use Illuminate\View\View; diff --git a/app/Http/Controllers/LessonController.php b/app/Http/Controllers/LessonController.php index f9dec72..57dc6e0 100644 --- a/app/Http/Controllers/LessonController.php +++ b/app/Http/Controllers/LessonController.php @@ -7,7 +7,7 @@ use App\Enums\TypeLesson; use App\Http\Requests\LessonPostRequest; use App\Models\Grade; use App\Models\Lesson; -use App\Services\ServiceInterface; +use App\Services\LessonService; use Illuminate\Http\RedirectResponse; use Illuminate\View\View; @@ -23,10 +23,14 @@ class LessonController extends Controller /** * Display a listing of the resource. */ - public function index(Grade $grade): View + public function index(Grade $grade, LessonService $service): View { + if(request()->user()->cannot('viewAny', $grade)) { + abort(403); + } + return view('grade-lesson.index', [ - 'lessons' => $grade->lessons()->filter()->get(), + 'lessons' => $service->getLessons($grade), 'grade' => $grade, 'subjects' => $grade->subjects, ]); @@ -37,6 +41,10 @@ class LessonController extends Controller */ public function create(Grade $grade): View { + if(request()->user()->cannot('create', Lesson::class)) { + abort(403); + } + return view('grade-lesson.create', [ 'types' => TypeLesson::cases(), 'grade' => $grade, @@ -48,6 +56,10 @@ class LessonController extends Controller */ public function store(LessonPostRequest $request, Grade $grade): RedirectResponse { + if(request()->user()->cannot('create', [Lesson::class, $grade])) { + abort(403); + } + $lesson = Lesson::create($request->validated()); $lesson ->students() @@ -66,6 +78,10 @@ class LessonController extends Controller */ public function show(Grade $grade, Lesson $lesson): View { + if(request()->user()->cannot('view', $lesson)) { + abort(403); + } + return view('grade-lesson.show', [ 'lesson' => $lesson, 'grade' => $grade, @@ -77,6 +93,10 @@ class LessonController extends Controller */ public function edit(Grade $grade, Lesson $lesson): View { + if(request()->user()->cannot('update', $lesson)) { + abort(403); + } + return view('grade-lesson.edit', [ 'lesson' => $lesson, 'grade' => $grade, @@ -89,12 +109,13 @@ class LessonController extends Controller */ public function update(LessonPostRequest $request, Grade $grade, Lesson $lesson): RedirectResponse { - return redirect()->route( - 'grades.lessons.show',[ - $grade, - $lesson->update($request->validated()), - ] - ); + if(request()->user()->cannot('update', $lesson)) { + abort(403); + } + + $lesson->update($request->validated()); + + return redirect()->route('grades.lessons.show',[$grade, $lesson,]); } /** @@ -102,6 +123,10 @@ class LessonController extends Controller */ public function destroy(Grade $grade, Lesson $lesson): RedirectResponse { + if(request()->user()->cannot('update', $lesson)) { + abort(403); + } + $lesson->delete(); return redirect()->route('grades.lessons.index', $grade); diff --git a/app/Http/Controllers/ScoreController.php b/app/Http/Controllers/ScoreController.php index 4bc97af..b2bc163 100644 --- a/app/Http/Controllers/ScoreController.php +++ b/app/Http/Controllers/ScoreController.php @@ -5,7 +5,6 @@ namespace App\Http\Controllers; use App\Enums\ScoreEnum; use App\Models\Lesson; use App\Services\ScoreService; -use App\Services\ServiceInterface; use Illuminate\Http\Request; class ScoreController extends Controller @@ -19,7 +18,7 @@ class ScoreController extends Controller public function show(Lesson $lesson) { return view('scores.show', [ - 'students' => $lesson->students, + 'students' => $lesson->students()->orderBy('last_name')->get(), 'lesson' => $lesson, 'scores' => ScoreEnum::cases(), ]); diff --git a/app/Http/Controllers/StudentController.php b/app/Http/Controllers/StudentController.php index 5217ca6..5c0b0e8 100644 --- a/app/Http/Controllers/StudentController.php +++ b/app/Http/Controllers/StudentController.php @@ -5,7 +5,8 @@ namespace App\Http\Controllers; use App\Http\Requests\StudentPostRequest; use App\Models\Grade; use App\Models\Student; -use App\Services\ServiceInterface; +use App\Models\Subject; +use App\Services\FileService; use App\Services\StudentService; use Illuminate\Http\RedirectResponse; use Illuminate\View\View; @@ -22,6 +23,10 @@ class StudentController extends Controller */ public function index(): View { + if(request()->user()->cannot('viewAny', Student::class)) { + abort(403); + } + return view('students.index', [ 'students' => Student::filter()->paginate(5)->withQueryString(), ]); @@ -32,6 +37,10 @@ class StudentController extends Controller */ public function create(): View { + if(request()->user()->cannot('create', Student::class)) { + abort(403); + } + return view('students.create', [ 'grades' => Grade::all(), ]); @@ -42,6 +51,10 @@ class StudentController extends Controller */ public function store(StudentPostRequest $request): RedirectResponse { + if(request()->user()->cannot('create', Student::class)) { + abort(403); + } + return redirect()->route( 'students.show', $this->service->create($request->validated()) @@ -53,6 +66,10 @@ class StudentController extends Controller */ public function show(Student $student): View { + if(request()->user()->cannot('view', $student)) { + abort(403); + } + return view('students.show', [ 'student' => $student, 'grades' => Grade::all(), @@ -64,6 +81,10 @@ class StudentController extends Controller */ public function edit(Student $student): View { + if(request()->user()->cannot('update', $student)) { + abort(403); + } + return view('students.edit', [ 'student' => $student, 'grades' => Grade::all(), @@ -75,6 +96,10 @@ class StudentController extends Controller */ public function update(StudentPostRequest $request, Student $student): RedirectResponse { + if(request()->user()->cannot('update', $student)) { + abort(403); + } + return redirect()->route( 'students.show', $this->service->update($student, $request->validated()) @@ -86,9 +111,33 @@ class StudentController extends Controller */ public function destroy(Student $student): RedirectResponse { + if(request()->user()->cannot('delete', $student)) { + abort(403); + } + $student->user()->delete(); $student->delete(); return redirect()->route('students.index'); } + + public function scores(StudentService $service, Subject $subject): View + { + return view('students.scores', [ + 'lessons' => $service->getScores($subject), + 'avgScore' => $service->getAvgScore($subject), + ]); + } + + public function debts(StudentService $service): View + { + return view('students.debts', [ + 'lessons' => $service->getDebts(), + ]); + } + + public function exportAvgScores(StudentService $service) + { + return $service->exportAvgScores(); + } } diff --git a/app/Http/Controllers/SubjectController.php b/app/Http/Controllers/SubjectController.php index 1e7733d..832b677 100644 --- a/app/Http/Controllers/SubjectController.php +++ b/app/Http/Controllers/SubjectController.php @@ -4,7 +4,9 @@ namespace App\Http\Controllers; use App\Http\Requests\SubjectPostRequest; use App\Models\Subject; -use App\Services\ServiceInterface; + +use App\Services\SubjectService; +use App\Services\FileService; use Illuminate\Http\RedirectResponse; use Illuminate\View\View; @@ -13,10 +15,10 @@ class SubjectController extends Controller /** * Display a listing of the resource. */ - public function index(): View + public function index(SubjectService $service): View { return view('subjects.index', [ - 'subjects' => Subject::filter()->paginate(5)->withQueryString(), + 'subjects' => $service->getSubjects(), ]); } @@ -25,6 +27,10 @@ class SubjectController extends Controller */ public function create(): View { + if(request()->user()->cannot('create', Subject::class)) { + abort(403); + } + return view('subjects.create'); } @@ -33,6 +39,10 @@ class SubjectController extends Controller */ public function store(SubjectPostRequest $request): RedirectResponse { + if(request()->user()->cannot('create', Subject::class)) { + abort(403); + } + return redirect()->route( 'subjects.show', Subject::create($request->validated()), @@ -54,6 +64,10 @@ class SubjectController extends Controller */ public function edit(Subject $subject): View { + if(request()->user()->cannot('update', $subject)) { + abort(403); + } + return view('subjects.edit', [ 'subject' => $subject, ]); @@ -64,10 +78,13 @@ class SubjectController extends Controller */ public function update(SubjectPostRequest $request, Subject $subject): RedirectResponse { - return redirect()->route( - 'subjects.show', - $subject->update($request->validated()) - ); + if(request()->user()->cannot('update', $subject)) { + abort(403); + } + + $subject->update($request->validated()); + + return redirect()->route('subjects.show', $subject); } /** @@ -75,8 +92,17 @@ class SubjectController extends Controller */ public function destroy(Subject $subject): RedirectResponse { + if(request()->user()->cannot('delete', $subject)) { + abort(403); + } + $subject->delete(); return redirect()->route('subjects.index'); } + + public function exportToPDF(FileService $fileService) + { + return $fileService->exportSubjects(); + } } diff --git a/app/Http/Controllers/SubjectTeacherController.php b/app/Http/Controllers/SubjectTeacherController.php index efa3299..f0656b7 100644 --- a/app/Http/Controllers/SubjectTeacherController.php +++ b/app/Http/Controllers/SubjectTeacherController.php @@ -5,12 +5,19 @@ namespace App\Http\Controllers; use App\Http\Requests\SubjectTeacherPostRequest; use App\Models\Subject; use App\Models\Teacher; -use App\Services\ServiceInterface; +use App\Services\TeacherService; use Illuminate\Http\RedirectResponse; use Illuminate\View\View; class SubjectTeacherController extends Controller { + public function index(Teacher $teacher, TeacherService $service) + { + return view('subject-teacher.index', [ + 'subjects' => $service->getSubjects($teacher), + ]); + } + public function create(Teacher $teacher): View { return view('subject-teacher.create', [ diff --git a/app/Http/Controllers/TeacherController.php b/app/Http/Controllers/TeacherController.php index edaa9e0..eb2bf08 100644 --- a/app/Http/Controllers/TeacherController.php +++ b/app/Http/Controllers/TeacherController.php @@ -4,7 +4,6 @@ namespace App\Http\Controllers; use App\Http\Requests\TeacherPostRequest; use App\Models\Teacher; -use App\Services\ServiceInterface; use App\Services\TeacherService; use Illuminate\Http\RedirectResponse; use Illuminate\View\View; @@ -19,10 +18,14 @@ class TeacherController extends Controller /** * Display a listing of the resource. */ - public function index(): View + public function index(TeacherService $service): View { + if(request()->user()->cannot('viewAny', Teacher::class)) { + abort(403); + } + return view('teachers.index', [ - 'teachers' => Teacher::filter()->paginate(5)->withQueryString(), + 'teachers' => $service->getTeachers(), ]); } @@ -31,6 +34,10 @@ class TeacherController extends Controller */ public function create(): View { + if(request()->user()->cannot('create', Teacher::class)) { + abort(403); + } + return view('teachers.create'); } @@ -39,6 +46,10 @@ class TeacherController extends Controller */ public function store(TeacherPostRequest $request): RedirectResponse { + if(request()->user()->cannot('create', Teacher::class)) { + abort(403); + } + return redirect()->route( 'teachers.show', $this->service->create($request->validated()) @@ -50,6 +61,10 @@ class TeacherController extends Controller */ public function show(Teacher $teacher): View { + if(request()->user()->cannot('view', $teacher)) { + abort(403); + } + return view('teachers.show', [ 'teacher' => $teacher, 'subjects' => $teacher->subjects, @@ -61,6 +76,10 @@ class TeacherController extends Controller */ public function edit(Teacher $teacher): View { + if(request()->user()->cannot('update', $teacher)) { + abort(403); + } + return view('teachers.edit', [ 'teacher' => $teacher, ]); @@ -71,6 +90,10 @@ class TeacherController extends Controller */ public function update(TeacherPostRequest $request, Teacher $teacher): RedirectResponse { + if(request()->user()->cannot('update', $teacher)) { + abort(403); + } + return redirect()->route( 'teachers.show', $this->service->update($teacher, $request->validated()) @@ -82,6 +105,10 @@ class TeacherController extends Controller */ public function destroy(Teacher $teacher): RedirectResponse { + if(request()->user()->cannot('update', $teacher)) { + abort(403); + } + $teacher->user()->delete(); $teacher->delete(); diff --git a/app/Http/Middleware/AdminAction.php b/app/Http/Middleware/AdminAction.php new file mode 100644 index 0000000..804f3d3 --- /dev/null +++ b/app/Http/Middleware/AdminAction.php @@ -0,0 +1,26 @@ +userable_type != Admin::class) { + abort(403); + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/StudentAction.php b/app/Http/Middleware/StudentAction.php new file mode 100644 index 0000000..8fba04c --- /dev/null +++ b/app/Http/Middleware/StudentAction.php @@ -0,0 +1,26 @@ +userable_type != Student::class) { + abort(403); + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/TeacherAction.php b/app/Http/Middleware/TeacherAction.php new file mode 100644 index 0000000..eb62975 --- /dev/null +++ b/app/Http/Middleware/TeacherAction.php @@ -0,0 +1,26 @@ +userable_type != Teacher::class) { + abort(403); + } + + return $next($request); + } +} diff --git a/app/Http/Requests/StudentPostRequest.php b/app/Http/Requests/StudentPostRequest.php index 132c34d..cbb0f33 100644 --- a/app/Http/Requests/StudentPostRequest.php +++ b/app/Http/Requests/StudentPostRequest.php @@ -2,7 +2,9 @@ namespace App\Http\Requests; +use App\Models\User; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Validation\Rule; class StudentPostRequest extends FormRequest { @@ -27,7 +29,7 @@ class StudentPostRequest extends FormRequest 'middle_name' => 'required|max:255', 'birthday' => 'required|date', 'grade_id' => 'required|exists:grades,id', - 'email' => 'required|max:255|lowercase|unique:users,email', + 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique(User::class)->ignore($this->route('student')?->user->id), 'regex:/^(([^<>()\[\]\\.,;:\s@”]+(\.[^<>()\[\]\\.,;:\s@”]+)*)|(“.+”))@((\[[0–9]{1,3}\.[0–9]{1,3}\.[0–9]{1,3}\.[0–9]{1,3}])|(([a-zA-Z\-0–9]+\.)+[a-zA-Z]{2,}))$/'], 'password' => 'required|max:255', ]; } diff --git a/app/Http/Requests/TeacherPostRequest.php b/app/Http/Requests/TeacherPostRequest.php index e3aea8a..eb4dfa2 100644 --- a/app/Http/Requests/TeacherPostRequest.php +++ b/app/Http/Requests/TeacherPostRequest.php @@ -2,7 +2,9 @@ namespace App\Http\Requests; +use App\Models\User; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Validation\Rule; class TeacherPostRequest extends FormRequest { @@ -26,7 +28,7 @@ class TeacherPostRequest extends FormRequest 'last_name' => 'required|max:255', 'middle_name' => 'required|max:255', 'birthday' => 'required|date', - 'email' => 'required|max:255|lowercase|unique:users,email', + 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique(User::class)->ignore($this->route('teacher')?->user->id), 'regex:/^(([^<>()\[\]\\.,;:\s@”]+(\.[^<>()\[\]\\.,;:\s@”]+)*)|(“.+”))@((\[[0–9]{1,3}\.[0–9]{1,3}\.[0–9]{1,3}\.[0–9]{1,3}])|(([a-zA-Z\-0–9]+\.)+[a-zA-Z]{2,}))$/'], 'password' => 'required|max:255', ]; } diff --git a/app/Mail/UserCreated.php b/app/Mail/UserCreated.php new file mode 100644 index 0000000..27d94c8 --- /dev/null +++ b/app/Mail/UserCreated.php @@ -0,0 +1,52 @@ + + */ + public function attachments(): array + { + return []; + } +} diff --git a/app/Models/Lesson.php b/app/Models/Lesson.php index 060f07d..bba613c 100644 --- a/app/Models/Lesson.php +++ b/app/Models/Lesson.php @@ -2,12 +2,14 @@ namespace App\Models; +use App\Enums\TypeLesson; use Illuminate\Contracts\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; -use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Support\Carbon; class Lesson extends Model { @@ -51,4 +53,18 @@ class Lesson extends Model $query->where('subject_id', $subject_id); }); } + + public function shortType(): Attribute + { + return Attribute::make( + get: fn () => TypeLesson::getShortType($this->type), + ); + } + + protected function date(): Attribute + { + return Attribute::make( + get: fn () => Carbon::parse($this->lesson_date)->format('d-m-Y') + ); + } } diff --git a/app/Models/Student.php b/app/Models/Student.php index fdc1552..1c96a5f 100644 --- a/app/Models/Student.php +++ b/app/Models/Student.php @@ -39,7 +39,7 @@ class Student extends Model public function lessons(): BelongsToMany { - return $this->belongsToMany(Lesson::class); + return $this->belongsToMany(Lesson::class)->withPivot('score'); } public function scopeFilter(Builder $query): void diff --git a/app/Models/User.php b/app/Models/User.php index e6541ff..269f223 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -3,11 +3,15 @@ namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; + +use App\Observers\UserObserver; +use Illuminate\Database\Eloquent\Attributes\ObservedBy; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +#[ObservedBy(UserObserver::class)] class User extends Authenticatable { use HasFactory, Notifiable; diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php new file mode 100644 index 0000000..e6520a2 --- /dev/null +++ b/app/Observers/UserObserver.php @@ -0,0 +1,18 @@ +send(new UserCreated(request()->all()['password'])); + } +} diff --git a/app/Policies/GradePolicy.php b/app/Policies/GradePolicy.php new file mode 100644 index 0000000..6343761 --- /dev/null +++ b/app/Policies/GradePolicy.php @@ -0,0 +1,62 @@ +userable_type != Student::class; + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, Grade $grade): bool + { + return $user->userable_type != Student::class; + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->userable_type == Admin::class; + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, Grade $grade): bool + { + return $user->userable_type == Admin::class; + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, Grade $grade): bool + { + return $user->userable_type == Admin::class; + } + + public function journal(User $user) + { + return $user->userable_type == Teacher::class; + } + + public function list(User $user) + { + return $user->userable_type == Teacher::class; + } +} diff --git a/app/Policies/LessonPolicy.php b/app/Policies/LessonPolicy.php new file mode 100644 index 0000000..23f825d --- /dev/null +++ b/app/Policies/LessonPolicy.php @@ -0,0 +1,53 @@ +userable_type == Teacher::class; + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, Lesson $lesson): bool + { + return $user->userable_type == Teacher::class; + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->userable_type == Teacher::class; + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, Lesson $lesson): bool + { + return $user->userable_type == Teacher::class; + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, Lesson $lesson): bool + { + return $user->userable_type == Teacher::class; + } +} diff --git a/app/Policies/StudentPolicy.php b/app/Policies/StudentPolicy.php new file mode 100644 index 0000000..3d027da --- /dev/null +++ b/app/Policies/StudentPolicy.php @@ -0,0 +1,60 @@ +userable_type != Student::class; + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, Student $student): bool + { + return $user->userable_type == Admin::class; + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->userable_type == Admin::class; + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, Student $student): bool + { + return $user->userable_type == Admin::class; + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, Student $student): bool + { + return $user->userable_type == Admin::class; + } + + public function debts(User $user): bool + { + return $user->userable_type == Student::class; + } + + public function avgScores(User $user): bool + { + return $user->userable_type == Student::class; + } +} diff --git a/app/Policies/SubjectPolicy.php b/app/Policies/SubjectPolicy.php new file mode 100644 index 0000000..2db2c27 --- /dev/null +++ b/app/Policies/SubjectPolicy.php @@ -0,0 +1,50 @@ +userable_type != Teacher::class; + } + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->userable_type == Admin::class; + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, Subject $subject): bool + { + return $user->userable_type == Admin::class; + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, Subject $subject): bool + { + return $user->userable_type == Admin::class; + } + + public function scores(User $user, Subject $subject): bool + { + return $user->userable_type == Student::class; + } + + public function pdf(User $user): bool + { + return $user->userable_type == Student::class; + } +} diff --git a/app/Policies/TeacherPolicy.php b/app/Policies/TeacherPolicy.php new file mode 100644 index 0000000..cfe16d9 --- /dev/null +++ b/app/Policies/TeacherPolicy.php @@ -0,0 +1,56 @@ +userable_type != Teacher::class; + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, Teacher $teacher): bool + { + return $user->userable_type == Admin::class; + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->userable_type == Admin::class; + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, Teacher $teacher): bool + { + return $user->userable_type == Admin::class; + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, Teacher $teacher): bool + { + return $user->userable_type == Admin::class; + } + + public function teacherSubjects(User $user, Teacher $teacher): bool + { + return $user->userable_type == Student::class; + } +} diff --git a/app/Services/FileService.php b/app/Services/FileService.php new file mode 100644 index 0000000..e84d887 --- /dev/null +++ b/app/Services/FileService.php @@ -0,0 +1,58 @@ +userable; + $subjects = $student->grade->subjects; + $teachers = $student->grade->teachers; + + $teachers->each(function ($teacher) use ($subjects, $listSubjects) { + $teacher->subjects->each(function ($subject) use ($subjects, $listSubjects, $teacher) { + if ($subjects->contains($subject)) { + $listSubjects->push(['subject' => $subject->name, 'teacher' => $teacher->fio]); + } + }); + }); + + return Pdf::loadView('subjects.pdf', ['subjects' => $listSubjects])->download('Предметы.pdf'); + } + + public function exportStudents(Grade $grade) + { + $excellentStudents = $this->getMinScore($grade, 5); + $goodStudents = $this->getMinScore($grade, 4); + + return Pdf::loadView('grades.list-students', [ + 'excellentStudents' => $excellentStudents, + 'goodStudents' => $goodStudents, + ])->download('Студенты.pdf'); + } + + public function getMinScore(Grade $grade, $minScore) + { + return $grade->students->filter(function ($student) use ($minScore) { + return $student->lessons->min('pivot.score') == $minScore; + }); + } + + public function exportJournal(Grade $grade, Subject $subject) + { + $lessons = $grade->lessons()->where('subject_id', $subject->id)->with('students')->get(); + $students = $grade->students()->orderBy('last_name')->get(); + $fileName = $subject->name . '(' . $grade->name . ').xlsx'; + + return Excel::download(new JournalExport($lessons, $students), $fileName); + } +} diff --git a/app/Services/GradeService.php b/app/Services/GradeService.php new file mode 100644 index 0000000..78d969f --- /dev/null +++ b/app/Services/GradeService.php @@ -0,0 +1,23 @@ +userable_type == Teacher::class) { + return Grade::join('grade_teacher', 'grade_teacher.grade_id', '=', 'grades.id') + ->where('grade_teacher.teacher_id', Auth::user()->userable_id) + ->filter() + ->paginate(5) + ->withQueryString(); + } + + return Grade::filter()->paginate(5)->withQueryString(); + } +} diff --git a/app/Services/JournalService.php b/app/Services/JournalService.php new file mode 100644 index 0000000..daa1238 --- /dev/null +++ b/app/Services/JournalService.php @@ -0,0 +1,19 @@ +where('grade_id', $grade->id) + ->where('subject_id', $subject->id) + ->orderBy('lesson_date') + ->get(); + } +} diff --git a/app/Services/LessonService.php b/app/Services/LessonService.php new file mode 100644 index 0000000..940faa5 --- /dev/null +++ b/app/Services/LessonService.php @@ -0,0 +1,23 @@ +userable_type == Teacher::class) { + return $grade + ->lessons() + ->where('teacher_id', Auth::user()->userable_id) + ->filter() + ->get(); + } + + return $grade->lessons()->filter()->get(); + } +} diff --git a/app/Services/ScoreService.php b/app/Services/ScoreService.php index a2855ac..c276fc3 100644 --- a/app/Services/ScoreService.php +++ b/app/Services/ScoreService.php @@ -3,7 +3,6 @@ namespace App\Services; use App\Models\Lesson; -use Illuminate\Database\Eloquent\Model; class ScoreService { diff --git a/app/Services/StudentService.php b/app/Services/StudentService.php index 94643b3..33a75f2 100644 --- a/app/Services/StudentService.php +++ b/app/Services/StudentService.php @@ -4,8 +4,12 @@ namespace App\Services; use App\Enums\ScoreEnum; use App\Models\Student; +use App\Models\Subject; use App\Models\User; +use Barryvdh\DomPDF\Facade\Pdf; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Facades\Auth; class StudentService { @@ -53,4 +57,44 @@ class StudentService $model->user()->delete(); $model->delete(); } + + public function getScores(Subject $subject): Collection + { + $student = Auth::user()->userable; + + return $student->lessons()->where('subject_id', $subject->id)->get(); + } + + public function getAvgScore(Subject $subject) + { + $student = Auth::user()->userable; + $scores = $student + ->lessons() + ->where('subject_id', $subject->id) + ->whereIn('score', ScoreEnum::getNumScores()) + ->pluck('score'); + + return round($scores->avg(), 2); + } + + public function getDebts(): Collection + { + $student = Auth::user()->userable; + + return $student->lessons()->whereIn('score', ScoreEnum::getDebtScores())->get(); + } + + public function exportAvgScores() + { + $subjects = Auth::user()->userable->grade->subjects; + $avgScores = collect(); + + $subjects->each(function ($subject) use ($avgScores) { + $avgScores->put($subject->name, $this->getAvgScore($subject)); + }); + + return Pdf::loadView('students.avg-scores', [ + 'avgScores' => $avgScores, + ])->download('Успеваемость.pdf'); + } } diff --git a/app/Services/SubjectService.php b/app/Services/SubjectService.php new file mode 100644 index 0000000..1a07737 --- /dev/null +++ b/app/Services/SubjectService.php @@ -0,0 +1,22 @@ +userable_type == Student::class) { + return Subject::whereIn('id', Auth::user()->userable->grade->subjects->pluck('id')) + ->filter() + ->paginate(5) + ->withQueryString(); + } + + return Subject::filter()->paginate(5)->withQueryString(); + } +} diff --git a/app/Services/TeacherService.php b/app/Services/TeacherService.php index 4059882..74da357 100644 --- a/app/Services/TeacherService.php +++ b/app/Services/TeacherService.php @@ -2,9 +2,12 @@ namespace App\Services; +use App\Models\Student; +use App\Models\Subject; use App\Models\Teacher; use App\Models\User; use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Facades\Auth; class TeacherService { @@ -47,4 +50,30 @@ class TeacherService $teacher->user()->delete(); $teacher->delete(); } + + public function getTeachers() + { + if (Auth::user()->userable_type == Student::class) { + return Teacher::join('grade_teacher', 'teachers.id', '=', 'grade_teacher.teacher_id') + ->where('grade_id', Auth::user()->userable->grade_id) + ->filter() + ->paginate(5) + ->withQueryString(); + } + + return Teacher::filter()->paginate(5)->withQueryString(); + } + + public function getSubjects(Teacher $teacher) + { + if (Auth::user()->userable_type == Student::class) { + return Subject::join('subject_teacher', 'subject_teacher.subject_id', '=', 'subjects.id') + ->join('grade_subject', 'grade_subject.subject_id', '=', 'subjects.id') + ->where('grade_subject.grade_id', Auth::user()->userable->grade_id) + ->where('subject_teacher.teacher_id', $teacher->id) + ->get(); + } + + return $teacher->subjects; + } } diff --git a/composer.json b/composer.json index af2957a..ac8b6b7 100644 --- a/composer.json +++ b/composer.json @@ -6,10 +6,12 @@ "license": "MIT", "require": { "php": "^8.2", + "barryvdh/laravel-dompdf": "^2.2", "laravel/framework": "^11.0", "laravel/telescope": "^5.0", "laravel/tinker": "^2.9", - "laravel/ui": "^4.5" + "laravel/ui": "^4.5", + "maatwebsite/excel": "^3.1" }, "require-dev": { "fakerphp/faker": "^1.23", diff --git a/composer.lock b/composer.lock index e24c025..3c9bfb7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,85 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "22026148ad171149c6b293e18a64f230", + "content-hash": "04b6d198f726939fe72146f62909bd81", "packages": [ + { + "name": "barryvdh/laravel-dompdf", + "version": "v2.2.0", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-dompdf.git", + "reference": "c96f90c97666cebec154ca1ffb67afed372114d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/c96f90c97666cebec154ca1ffb67afed372114d8", + "reference": "c96f90c97666cebec154ca1ffb67afed372114d8", + "shasum": "" + }, + "require": { + "dompdf/dompdf": "^2.0.7", + "illuminate/support": "^6|^7|^8|^9|^10|^11", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "larastan/larastan": "^1.0|^2.7.0", + "orchestra/testbench": "^4|^5|^6|^7|^8|^9", + "phpro/grumphp": "^1 || ^2.5", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + }, + "laravel": { + "providers": [ + "Barryvdh\\DomPDF\\ServiceProvider" + ], + "aliases": { + "Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf", + "PDF": "Barryvdh\\DomPDF\\Facade\\Pdf" + } + } + }, + "autoload": { + "psr-4": { + "Barryvdh\\DomPDF\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "A DOMPDF Wrapper for Laravel", + "keywords": [ + "dompdf", + "laravel", + "pdf" + ], + "support": { + "issues": "https://github.com/barryvdh/laravel-dompdf/issues", + "source": "https://github.com/barryvdh/laravel-dompdf/tree/v2.2.0" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2024-04-25T13:16:04+00:00" + }, { "name": "brick/math", "version": "0.11.0", @@ -130,6 +207,87 @@ ], "time": "2024-02-09T16:56:22+00:00" }, + { + "name": "composer/semver", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2023-08-31T09:50:34+00:00" + }, { "name": "dflydev/dot-access-data", "version": "v3.0.2", @@ -373,6 +531,68 @@ ], "time": "2024-02-05T11:56:58+00:00" }, + { + "name": "dompdf/dompdf", + "version": "v2.0.8", + "source": { + "type": "git", + "url": "https://github.com/dompdf/dompdf.git", + "reference": "c20247574601700e1f7c8dab39310fca1964dc52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/c20247574601700e1f7c8dab39310fca1964dc52", + "reference": "c20247574601700e1f7c8dab39310fca1964dc52", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "masterminds/html5": "^2.0", + "phenx/php-font-lib": ">=0.5.4 <1.0.0", + "phenx/php-svg-lib": ">=0.5.2 <1.0.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "ext-json": "*", + "ext-zip": "*", + "mockery/mockery": "^1.3", + "phpunit/phpunit": "^7.5 || ^8 || ^9", + "squizlabs/php_codesniffer": "^3.5" + }, + "suggest": { + "ext-gd": "Needed to process images", + "ext-gmagick": "Improves image processing performance", + "ext-imagick": "Improves image processing performance", + "ext-zlib": "Needed for pdf stream compression" + }, + "type": "library", + "autoload": { + "psr-4": { + "Dompdf\\": "src/" + }, + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "The Dompdf Community", + "homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md" + } + ], + "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", + "homepage": "https://github.com/dompdf/dompdf", + "support": { + "issues": "https://github.com/dompdf/dompdf/issues", + "source": "https://github.com/dompdf/dompdf/tree/v2.0.8" + }, + "time": "2024-04-29T13:06:17+00:00" + }, { "name": "dragonmantank/cron-expression", "version": "v3.3.3", @@ -501,6 +721,67 @@ ], "time": "2023-10-06T06:47:41+00:00" }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.17.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/bbc513d79acf6691fa9cf10f192c90dd2957f18c", + "reference": "bbc513d79acf6691fa9cf10f192c90dd2957f18c", + "shasum": "" + }, + "require": { + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0" + }, + "require-dev": { + "cerdic/css-tidy": "^1.7 || ^2.0", + "simpletest/simpletest": "dev-master" + }, + "suggest": { + "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", + "ext-bcmath": "Used for unit conversion and imagecrash protection", + "ext-iconv": "Converts text to and from non-UTF-8 encodings", + "ext-tidy": "Used for pretty-printing HTML" + }, + "type": "library", + "autoload": { + "files": [ + "library/HTMLPurifier.composer.php" + ], + "psr-0": { + "HTMLPurifier": "library/" + }, + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "support": { + "issues": "https://github.com/ezyang/htmlpurifier/issues", + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.17.0" + }, + "time": "2023-11-17T15:01:25+00:00" + }, { "name": "fruitcake/php-cors", "version": "v1.3.0", @@ -1958,6 +2239,342 @@ ], "time": "2024-01-28T23:22:08+00:00" }, + { + "name": "maatwebsite/excel", + "version": "3.1.55", + "source": { + "type": "git", + "url": "https://github.com/SpartnerNL/Laravel-Excel.git", + "reference": "6d9d791dcdb01a9b6fd6f48d46f0d5fff86e6260" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/6d9d791dcdb01a9b6fd6f48d46f0d5fff86e6260", + "reference": "6d9d791dcdb01a9b6fd6f48d46f0d5fff86e6260", + "shasum": "" + }, + "require": { + "composer/semver": "^3.3", + "ext-json": "*", + "illuminate/support": "5.8.*||^6.0||^7.0||^8.0||^9.0||^10.0||^11.0", + "php": "^7.0||^8.0", + "phpoffice/phpspreadsheet": "^1.18", + "psr/simple-cache": "^1.0||^2.0||^3.0" + }, + "require-dev": { + "laravel/scout": "^7.0||^8.0||^9.0||^10.0", + "orchestra/testbench": "^6.0||^7.0||^8.0||^9.0", + "predis/predis": "^1.1" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Maatwebsite\\Excel\\ExcelServiceProvider" + ], + "aliases": { + "Excel": "Maatwebsite\\Excel\\Facades\\Excel" + } + } + }, + "autoload": { + "psr-4": { + "Maatwebsite\\Excel\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Patrick Brouwers", + "email": "patrick@spartner.nl" + } + ], + "description": "Supercharged Excel exports and imports in Laravel", + "keywords": [ + "PHPExcel", + "batch", + "csv", + "excel", + "export", + "import", + "laravel", + "php", + "phpspreadsheet" + ], + "support": { + "issues": "https://github.com/SpartnerNL/Laravel-Excel/issues", + "source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.55" + }, + "funding": [ + { + "url": "https://laravel-excel.com/commercial-support", + "type": "custom" + }, + { + "url": "https://github.com/patrickbrouwers", + "type": "github" + } + ], + "time": "2024-02-20T08:27:10+00:00" + }, + { + "name": "maennchen/zipstream-php", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "b8174494eda667f7d13876b4a7bfef0f62a7c0d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/b8174494eda667f7d13876b4a7bfef0f62a7c0d1", + "reference": "b8174494eda667f7d13876b4a7bfef0f62a7c0d1", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-zlib": "*", + "php-64bit": "^8.1" + }, + "require-dev": { + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.16", + "guzzlehttp/guzzle": "^7.5", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^10.0", + "vimeo/psalm": "^5.0" + }, + "suggest": { + "guzzlehttp/psr7": "^2.4", + "psr/http-message": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.0" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + }, + { + "url": "https://opencollective.com/zipstream", + "type": "open_collective" + } + ], + "time": "2023-06-21T14:59:35+00:00" + }, + { + "name": "markbaker/complex", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" + }, + "time": "2022-12-06T16:21:08+00:00" + }, + { + "name": "markbaker/matrix", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" + }, + "time": "2022-12-02T22:17:43+00:00" + }, + { + "name": "masterminds/html5", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" + }, + "time": "2024-03-31T07:05:07+00:00" + }, { "name": "monolog/monolog", "version": "3.6.0", @@ -2459,6 +3076,201 @@ ], "time": "2024-03-06T16:17:14+00:00" }, + { + "name": "phenx/php-font-lib", + "version": "0.5.6", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-font-lib.git", + "reference": "a1681e9793040740a405ac5b189275059e2a9863" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a1681e9793040740a405ac5b189275059e2a9863", + "reference": "a1681e9793040740a405ac5b189275059e2a9863", + "shasum": "" + }, + "require": { + "ext-mbstring": "*" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3 || ^4 || ^5 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "FontLib\\": "src/FontLib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Fabien Ménager", + "email": "fabien.menager@gmail.com" + } + ], + "description": "A library to read, parse, export and make subsets of different types of font files.", + "homepage": "https://github.com/PhenX/php-font-lib", + "support": { + "issues": "https://github.com/dompdf/php-font-lib/issues", + "source": "https://github.com/dompdf/php-font-lib/tree/0.5.6" + }, + "time": "2024-01-29T14:45:26+00:00" + }, + { + "name": "phenx/php-svg-lib", + "version": "0.5.4", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-svg-lib.git", + "reference": "46b25da81613a9cf43c83b2a8c2c1bdab27df691" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/46b25da81613a9cf43c83b2a8c2c1bdab27df691", + "reference": "46b25da81613a9cf43c83b2a8c2c1bdab27df691", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0", + "sabberworm/php-css-parser": "^8.4" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Svg\\": "src/Svg" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Fabien Ménager", + "email": "fabien.menager@gmail.com" + } + ], + "description": "A library to read, parse and export to PDF SVG files.", + "homepage": "https://github.com/PhenX/php-svg-lib", + "support": { + "issues": "https://github.com/dompdf/php-svg-lib/issues", + "source": "https://github.com/dompdf/php-svg-lib/tree/0.5.4" + }, + "time": "2024-04-08T12:52:34+00:00" + }, + { + "name": "phpoffice/phpspreadsheet", + "version": "1.29.0", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fde2ccf55eaef7e86021ff1acce26479160a0fa0", + "reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "ezyang/htmlpurifier": "^4.15", + "maennchen/zipstream-php": "^2.1 || ^3.0", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": "^7.4 || ^8.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-main", + "dompdf/dompdf": "^1.0 || ^2.0", + "friendsofphp/php-cs-fixer": "^3.2", + "mitoteam/jpgraph": "^10.3", + "mpdf/mpdf": "^8.1.1", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^8.5 || ^9.0 || ^10.0", + "squizlabs/php_codesniffer": "^3.7", + "tecnickcom/tcpdf": "^6.5" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "ext-intl": "PHP Internationalization Functions", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.29.0" + }, + "time": "2023-06-14T22:48:31+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.2", @@ -3250,6 +4062,71 @@ ], "time": "2023-11-08T05:53:05+00:00" }, + { + "name": "sabberworm/php-css-parser", + "version": "v8.5.1", + "source": { + "type": "git", + "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", + "reference": "4a3d572b0f8b28bb6fd016ae8bbfc445facef152" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/4a3d572b0f8b28bb6fd016ae8bbfc445facef152", + "reference": "4a3d572b0f8b28bb6fd016ae8bbfc445facef152", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=5.6.20" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.27" + }, + "suggest": { + "ext-mbstring": "for parsing UTF-8 CSS" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Sabberworm\\CSS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Raphael Schweikert" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Jake Hotson", + "email": "jake.github@qzdesign.co.uk" + } + ], + "description": "Parser for CSS Files written in PHP", + "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", + "keywords": [ + "css", + "parser", + "stylesheet" + ], + "support": { + "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.5.1" + }, + "time": "2024-02-15T16:41:13+00:00" + }, { "name": "symfony/clock", "version": "v7.0.5", @@ -5864,87 +6741,6 @@ }, "time": "2024-01-28T17:52:47+00:00" }, - { - "name": "composer/semver", - "version": "3.4.0", - "source": { - "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Semver\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", - "keywords": [ - "semantic", - "semver", - "validation", - "versioning" - ], - "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.0" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2023-08-31T09:50:34+00:00" - }, { "name": "dragon-code/contracts", "version": "2.23.0", diff --git a/config/dompdf.php b/config/dompdf.php new file mode 100644 index 0000000..c9a928f --- /dev/null +++ b/config/dompdf.php @@ -0,0 +1,284 @@ + false, // Throw an Exception on warnings from dompdf + + 'public_path' => null, // Override the public path if needed + + /* + * Dejavu Sans font is missing glyphs for converted entities, turn it off if you need to show € and £. + */ + 'convert_entities' => true, + + 'options' => array( + /** + * The location of the DOMPDF font directory + * + * The location of the directory where DOMPDF will store fonts and font metrics + * Note: This directory must exist and be writable by the webserver process. + * *Please note the trailing slash.* + * + * Notes regarding fonts: + * Additional .afm font metrics can be added by executing load_font.php from command line. + * + * Only the original "Base 14 fonts" are present on all pdf viewers. Additional fonts must + * be embedded in the pdf file or the PDF may not display correctly. This can significantly + * increase file size unless font subsetting is enabled. Before embedding a font please + * review your rights under the font license. + * + * Any font specification in the source HTML is translated to the closest font available + * in the font directory. + * + * The pdf standard "Base 14 fonts" are: + * Courier, Courier-Bold, Courier-BoldOblique, Courier-Oblique, + * Helvetica, Helvetica-Bold, Helvetica-BoldOblique, Helvetica-Oblique, + * Times-Roman, Times-Bold, Times-BoldItalic, Times-Italic, + * Symbol, ZapfDingbats. + */ + "font_dir" => storage_path('fonts'), // advised by dompdf (https://github.com/dompdf/dompdf/pull/782) + + /** + * The location of the DOMPDF font cache directory + * + * This directory contains the cached font metrics for the fonts used by DOMPDF. + * This directory can be the same as DOMPDF_FONT_DIR + * + * Note: This directory must exist and be writable by the webserver process. + */ + "font_cache" => storage_path('fonts'), + + /** + * The location of a temporary directory. + * + * The directory specified must be writeable by the webserver process. + * The temporary directory is required to download remote images and when + * using the PDFLib back end. + */ + "temp_dir" => sys_get_temp_dir(), + + /** + * ==== IMPORTANT ==== + * + * dompdf's "chroot": Prevents dompdf from accessing system files or other + * files on the webserver. All local files opened by dompdf must be in a + * subdirectory of this directory. DO NOT set it to '/' since this could + * allow an attacker to use dompdf to read any files on the server. This + * should be an absolute path. + * This is only checked on command line call by dompdf.php, but not by + * direct class use like: + * $dompdf = new DOMPDF(); $dompdf->load_html($htmldata); $dompdf->render(); $pdfdata = $dompdf->output(); + */ + "chroot" => realpath(base_path()), + + /** + * Protocol whitelist + * + * Protocols and PHP wrappers allowed in URIs, and the validation rules + * that determine if a resouce may be loaded. Full support is not guaranteed + * for the protocols/wrappers specified + * by this array. + * + * @var array + */ + 'allowed_protocols' => [ + "file://" => ["rules" => []], + "http://" => ["rules" => []], + "https://" => ["rules" => []] + ], + + /** + * @var string + */ + 'log_output_file' => null, + + /** + * Whether to enable font subsetting or not. + */ + "enable_font_subsetting" => false, + + /** + * The PDF rendering backend to use + * + * Valid settings are 'PDFLib', 'CPDF' (the bundled R&OS PDF class), 'GD' and + * 'auto'. 'auto' will look for PDFLib and use it if found, or if not it will + * fall back on CPDF. 'GD' renders PDFs to graphic files. {@link + * Canvas_Factory} ultimately determines which rendering class to instantiate + * based on this setting. + * + * Both PDFLib & CPDF rendering backends provide sufficient rendering + * capabilities for dompdf, however additional features (e.g. object, + * image and font support, etc.) differ between backends. Please see + * {@link PDFLib_Adapter} for more information on the PDFLib backend + * and {@link CPDF_Adapter} and lib/class.pdf.php for more information + * on CPDF. Also see the documentation for each backend at the links + * below. + * + * The GD rendering backend is a little different than PDFLib and + * CPDF. Several features of CPDF and PDFLib are not supported or do + * not make any sense when creating image files. For example, + * multiple pages are not supported, nor are PDF 'objects'. Have a + * look at {@link GD_Adapter} for more information. GD support is + * experimental, so use it at your own risk. + * + * @link http://www.pdflib.com + * @link http://www.ros.co.nz/pdf + * @link http://www.php.net/image + */ + "pdf_backend" => "CPDF", + + /** + * PDFlib license key + * + * If you are using a licensed, commercial version of PDFlib, specify + * your license key here. If you are using PDFlib-Lite or are evaluating + * the commercial version of PDFlib, comment out this setting. + * + * @link http://www.pdflib.com + * + * If pdflib present in web server and auto or selected explicitely above, + * a real license code must exist! + */ + //"DOMPDF_PDFLIB_LICENSE" => "your license key here", + + /** + * html target media view which should be rendered into pdf. + * List of types and parsing rules for future extensions: + * http://www.w3.org/TR/REC-html40/types.html + * screen, tty, tv, projection, handheld, print, braille, aural, all + * Note: aural is deprecated in CSS 2.1 because it is replaced by speech in CSS 3. + * Note, even though the generated pdf file is intended for print output, + * the desired content might be different (e.g. screen or projection view of html file). + * Therefore allow specification of content here. + */ + "default_media_type" => "screen", + + /** + * The default paper size. + * + * North America standard is "letter"; other countries generally "a4" + * + * @see CPDF_Adapter::PAPER_SIZES for valid sizes ('letter', 'legal', 'A4', etc.) + */ + "default_paper_size" => "a4", + + /** + * The default paper orientation. + * + * The orientation of the page (portrait or landscape). + * + * @var string + */ + 'default_paper_orientation' => "portrait", + + /** + * The default font family + * + * Used if no suitable fonts can be found. This must exist in the font folder. + * @var string + */ + "default_font" => "serif", + + /** + * Image DPI setting + * + * This setting determines the default DPI setting for images and fonts. The + * DPI may be overridden for inline images by explictly setting the + * image's width & height style attributes (i.e. if the image's native + * width is 600 pixels and you specify the image's width as 72 points, + * the image will have a DPI of 600 in the rendered PDF. The DPI of + * background images can not be overridden and is controlled entirely + * via this parameter. + * + * For the purposes of DOMPDF, pixels per inch (PPI) = dots per inch (DPI). + * If a size in html is given as px (or without unit as image size), + * this tells the corresponding size in pt. + * This adjusts the relative sizes to be similar to the rendering of the + * html page in a reference browser. + * + * In pdf, always 1 pt = 1/72 inch + * + * Rendering resolution of various browsers in px per inch: + * Windows Firefox and Internet Explorer: + * SystemControl->Display properties->FontResolution: Default:96, largefonts:120, custom:? + * Linux Firefox: + * about:config *resolution: Default:96 + * (xorg screen dimension in mm and Desktop font dpi settings are ignored) + * + * Take care about extra font/image zoom factor of browser. + * + * In images, size in pixel attribute, img css style, are overriding + * the real image dimension in px for rendering. + * + * @var int + */ + "dpi" => 96, + + /** + * Enable inline PHP + * + * If this setting is set to true then DOMPDF will automatically evaluate + * inline PHP contained within tags. + * + * Enabling this for documents you do not trust (e.g. arbitrary remote html + * pages) is a security risk. Set this option to false if you wish to process + * untrusted documents. + * + * @var bool + */ + "enable_php" => false, + + /** + * Enable inline Javascript + * + * If this setting is set to true then DOMPDF will automatically insert + * JavaScript code contained within tags. + * + * @var bool + */ + "enable_javascript" => true, + + /** + * Enable remote file access + * + * If this setting is set to true, DOMPDF will access remote sites for + * images and CSS files as required. + * This is required for part of test case www/test/image_variants.html through www/examples.php + * + * Attention! + * This can be a security risk, in particular in combination with DOMPDF_ENABLE_PHP and + * allowing remote access to dompdf.php or on allowing remote html code to be passed to + * $dompdf = new DOMPDF(, $dompdf->load_html(..., + * This allows anonymous users to download legally doubtful internet content which on + * tracing back appears to being downloaded by your server, or allows malicious php code + * in remote html pages to be executed by your server with your account privileges. + * + * @var bool + */ + "enable_remote" => true, + + /** + * A ratio applied to the fonts height to be more like browsers' line height + */ + "font_height_ratio" => 1.1, + + /** + * Use the HTML5 Lib parser + * + * @deprecated This feature is now always on in dompdf 2.x + * @var bool + */ + "enable_html5_parser" => true, + ), + + +); diff --git a/config/excel.php b/config/excel.php new file mode 100644 index 0000000..16828e7 --- /dev/null +++ b/config/excel.php @@ -0,0 +1,379 @@ + [ + + /* + |-------------------------------------------------------------------------- + | Chunk size + |-------------------------------------------------------------------------- + | + | When using FromQuery, the query is automatically chunked. + | Here you can specify how big the chunk should be. + | + */ + 'chunk_size' => 1000, + + /* + |-------------------------------------------------------------------------- + | Pre-calculate formulas during export + |-------------------------------------------------------------------------- + */ + 'pre_calculate_formulas' => false, + + /* + |-------------------------------------------------------------------------- + | Enable strict null comparison + |-------------------------------------------------------------------------- + | + | When enabling strict null comparison empty cells ('') will + | be added to the sheet. + */ + 'strict_null_comparison' => false, + + /* + |-------------------------------------------------------------------------- + | CSV Settings + |-------------------------------------------------------------------------- + | + | Configure e.g. delimiter, enclosure and line ending for CSV exports. + | + */ + 'csv' => [ + 'delimiter' => ',', + 'enclosure' => '"', + 'line_ending' => PHP_EOL, + 'use_bom' => false, + 'include_separator_line' => false, + 'excel_compatibility' => false, + 'output_encoding' => '', + 'test_auto_detect' => true, + ], + + /* + |-------------------------------------------------------------------------- + | Worksheet properties + |-------------------------------------------------------------------------- + | + | Configure e.g. default title, creator, subject,... + | + */ + 'properties' => [ + 'creator' => '', + 'lastModifiedBy' => '', + 'title' => '', + 'description' => '', + 'subject' => '', + 'keywords' => '', + 'category' => '', + 'manager' => '', + 'company' => '', + ], + ], + + 'imports' => [ + + /* + |-------------------------------------------------------------------------- + | Read Only + |-------------------------------------------------------------------------- + | + | When dealing with imports, you might only be interested in the + | data that the sheet exists. By default we ignore all styles, + | however if you want to do some logic based on style data + | you can enable it by setting read_only to false. + | + */ + 'read_only' => true, + + /* + |-------------------------------------------------------------------------- + | Ignore Empty + |-------------------------------------------------------------------------- + | + | When dealing with imports, you might be interested in ignoring + | rows that have null values or empty strings. By default rows + | containing empty strings or empty values are not ignored but can be + | ignored by enabling the setting ignore_empty to true. + | + */ + 'ignore_empty' => false, + + /* + |-------------------------------------------------------------------------- + | Heading Row Formatter + |-------------------------------------------------------------------------- + | + | Configure the heading row formatter. + | Available options: none|slug|custom + | + */ + 'heading_row' => [ + 'formatter' => 'slug', + ], + + /* + |-------------------------------------------------------------------------- + | CSV Settings + |-------------------------------------------------------------------------- + | + | Configure e.g. delimiter, enclosure and line ending for CSV imports. + | + */ + 'csv' => [ + 'delimiter' => null, + 'enclosure' => '"', + 'escape_character' => '\\', + 'contiguous' => false, + 'input_encoding' => 'UTF-8', + ], + + /* + |-------------------------------------------------------------------------- + | Worksheet properties + |-------------------------------------------------------------------------- + | + | Configure e.g. default title, creator, subject,... + | + */ + 'properties' => [ + 'creator' => '', + 'lastModifiedBy' => '', + 'title' => '', + 'description' => '', + 'subject' => '', + 'keywords' => '', + 'category' => '', + 'manager' => '', + 'company' => '', + ], + + /* + |-------------------------------------------------------------------------- + | Cell Middleware + |-------------------------------------------------------------------------- + | + | Configure middleware that is executed on getting a cell value + | + */ + 'cells' => [ + 'middleware' => [ + //\Maatwebsite\Excel\Middleware\TrimCellValue::class, + //\Maatwebsite\Excel\Middleware\ConvertEmptyCellValuesToNull::class, + ], + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Extension detector + |-------------------------------------------------------------------------- + | + | Configure here which writer/reader type should be used when the package + | needs to guess the correct type based on the extension alone. + | + */ + 'extension_detector' => [ + 'xlsx' => Excel::XLSX, + 'xlsm' => Excel::XLSX, + 'xltx' => Excel::XLSX, + 'xltm' => Excel::XLSX, + 'xls' => Excel::XLS, + 'xlt' => Excel::XLS, + 'ods' => Excel::ODS, + 'ots' => Excel::ODS, + 'slk' => Excel::SLK, + 'xml' => Excel::XML, + 'gnumeric' => Excel::GNUMERIC, + 'htm' => Excel::HTML, + 'html' => Excel::HTML, + 'csv' => Excel::CSV, + 'tsv' => Excel::TSV, + + /* + |-------------------------------------------------------------------------- + | PDF Extension + |-------------------------------------------------------------------------- + | + | Configure here which Pdf driver should be used by default. + | Available options: Excel::MPDF | Excel::TCPDF | Excel::DOMPDF + | + */ + 'pdf' => Excel::DOMPDF, + ], + + /* + |-------------------------------------------------------------------------- + | Value Binder + |-------------------------------------------------------------------------- + | + | PhpSpreadsheet offers a way to hook into the process of a value being + | written to a cell. In there some assumptions are made on how the + | value should be formatted. If you want to change those defaults, + | you can implement your own default value binder. + | + | Possible value binders: + | + | [x] Maatwebsite\Excel\DefaultValueBinder::class + | [x] PhpOffice\PhpSpreadsheet\Cell\StringValueBinder::class + | [x] PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder::class + | + */ + 'value_binder' => [ + 'default' => Maatwebsite\Excel\DefaultValueBinder::class, + ], + + 'cache' => [ + /* + |-------------------------------------------------------------------------- + | Default cell caching driver + |-------------------------------------------------------------------------- + | + | By default PhpSpreadsheet keeps all cell values in memory, however when + | dealing with large files, this might result into memory issues. If you + | want to mitigate that, you can configure a cell caching driver here. + | When using the illuminate driver, it will store each value in the + | cache store. This can slow down the process, because it needs to + | store each value. You can use the "batch" store if you want to + | only persist to the store when the memory limit is reached. + | + | Drivers: memory|illuminate|batch + | + */ + 'driver' => 'memory', + + /* + |-------------------------------------------------------------------------- + | Batch memory caching + |-------------------------------------------------------------------------- + | + | When dealing with the "batch" caching driver, it will only + | persist to the store when the memory limit is reached. + | Here you can tweak the memory limit to your liking. + | + */ + 'batch' => [ + 'memory_limit' => 60000, + ], + + /* + |-------------------------------------------------------------------------- + | Illuminate cache + |-------------------------------------------------------------------------- + | + | When using the "illuminate" caching driver, it will automatically use + | your default cache store. However if you prefer to have the cell + | cache on a separate store, you can configure the store name here. + | You can use any store defined in your cache config. When leaving + | at "null" it will use the default store. + | + */ + 'illuminate' => [ + 'store' => null, + ], + + /* + |-------------------------------------------------------------------------- + | Cache Time-to-live (TTL) + |-------------------------------------------------------------------------- + | + | The TTL of items written to cache. If you want to keep the items cached + | indefinitely, set this to null. Otherwise, set a number of seconds, + | a \DateInterval, or a callable. + | + | Allowable types: callable|\DateInterval|int|null + | + */ + 'default_ttl' => 10800, + ], + + /* + |-------------------------------------------------------------------------- + | Transaction Handler + |-------------------------------------------------------------------------- + | + | By default the import is wrapped in a transaction. This is useful + | for when an import may fail and you want to retry it. With the + | transactions, the previous import gets rolled-back. + | + | You can disable the transaction handler by setting this to null. + | Or you can choose a custom made transaction handler here. + | + | Supported handlers: null|db + | + */ + 'transactions' => [ + 'handler' => 'db', + 'db' => [ + 'connection' => null, + ], + ], + + 'temporary_files' => [ + + /* + |-------------------------------------------------------------------------- + | Local Temporary Path + |-------------------------------------------------------------------------- + | + | When exporting and importing files, we use a temporary file, before + | storing reading or downloading. Here you can customize that path. + | permissions is an array with the permission flags for the directory (dir) + | and the create file (file). + | + */ + 'local_path' => storage_path('framework/cache/laravel-excel'), + + /* + |-------------------------------------------------------------------------- + | Local Temporary Path Permissions + |-------------------------------------------------------------------------- + | + | Permissions is an array with the permission flags for the directory (dir) + | and the create file (file). + | If omitted the default permissions of the filesystem will be used. + | + */ + 'local_permissions' => [ + // 'dir' => 0755, + // 'file' => 0644, + ], + + /* + |-------------------------------------------------------------------------- + | Remote Temporary Disk + |-------------------------------------------------------------------------- + | + | When dealing with a multi server setup with queues in which you + | cannot rely on having a shared local temporary path, you might + | want to store the temporary file on a shared disk. During the + | queue executing, we'll retrieve the temporary file from that + | location instead. When left to null, it will always use + | the local path. This setting only has effect when using + | in conjunction with queued imports and exports. + | + */ + 'remote_disk' => null, + 'remote_prefix' => null, + + /* + |-------------------------------------------------------------------------- + | Force Resync + |-------------------------------------------------------------------------- + | + | When dealing with a multi server setup as above, it's possible + | for the clean up that occurs after entire queue has been run to only + | cleanup the server that the last AfterImportJob runs on. The rest of the server + | would still have the local temporary file stored on it. In this case your + | local storage limits can be exceeded and future imports won't be processed. + | To mitigate this you can set this config value to be true, so that after every + | queued chunk is processed the local temporary file is deleted on the server that + | processed it. + | + */ + 'force_resync_remote' => null, + ], +]; diff --git a/config/tinker.php b/config/tinker.php new file mode 100644 index 0000000..e486bd5 --- /dev/null +++ b/config/tinker.php @@ -0,0 +1,51 @@ + [ + App\Console\Commands\AddAdmin::class, + App\Console\Commands\AddScores::class, + ], + + /* + |-------------------------------------------------------------------------- + | Auto Aliased Classes + |-------------------------------------------------------------------------- + | + | Tinker will not automatically alias classes in your vendor namespaces + | but you may explicitly allow a subset of classes to get aliased by + | adding the names of each of those classes to the following list. + | + */ + + 'alias' => [ + // + ], + + /* + |-------------------------------------------------------------------------- + | Classes That Should Not Be Aliased + |-------------------------------------------------------------------------- + | + | Typically, Tinker automatically aliases classes as you require them in + | Tinker. However, you may wish to never alias certain classes, which + | you may accomplish by listing the classes in the following array. + | + */ + + 'dont_alias' => [ + 'App\Nova', + ], + +]; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 1344e92..875bce2 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -18,9 +18,39 @@ class DatabaseSeeder extends Seeder */ public function run(): void { + $namesSubjects = [ + 'Русский язык', + 'Математика', + 'Английский язык', + 'Биология', + 'Химия', + 'Литература', + 'География', + 'История', + 'Обществознание', + 'Информатика', + ]; + $subjects = collect(); + + foreach ($namesSubjects as $name) { + $subjects->push(Subject::factory()->create([ + 'name' => $name, + ])); + } + + $letterGrades = ['A', 'Б', 'В',]; + $numberGrades = 11; + $grades = collect(); + + while($numberGrades > 0) { + foreach ($letterGrades as $letter) { + $grades->push(Grade::factory()->create([ + 'name' => $numberGrades . $letter, + ])); + } + $numberGrades--; + } $teachers = Teacher::factory(15)->create(); - $grades = Grade::factory(10)->create(); - $subjects = Subject::factory(10)->create(); $scores = ScoreEnum::cases(); $teachers->each(function ($teacher) { @@ -28,33 +58,45 @@ class DatabaseSeeder extends Seeder $teacher->user()->save($user); }); - $grades->each(function ($grade) use ($subjects, $teachers, $scores){ - $grade->subjects()->sync($subjects); - $grade->teachers()->sync($teachers); + $teacher = Teacher::factory()->create(); + $teacher->user()->save(User::factory()->create(['email' => 'teacher@mail'])); + $teacher->grades()->attach($grades->pluck('id')); - $students = Student::factory(10)->create([ + $student = Student::factory()->create(['grade_id' => $grades->first()->id]); + $student->user()->save(User::factory()->create(['email' => 'student@mail'])); + + $grade = $student->grade; + + $lessons = collect(); + $subjects->each(function ($item) use ($lessons, $teacher, $student, $grade) { + $teacher->subjects()->attach($item); + $grade->subjects()->attach($item); + + $lessons->push(Lesson::factory(5)->create([ + 'description' => 'Выполнение задания №3 на 87 стр. учебника', + 'grade_id' => $student->grade_id, + 'subject_id' => $item->id, + 'teacher_id' => $teacher->id, + ])); + }); + + + $grades->each(function ($grade) use ($subjects, $teachers, $scores){ + Student::factory(30)->create([ 'grade_id' => $grade->id, ])->each(function ($student) use ($scores) { $user = User::factory()->create(); $student->user()->save($user); }); - - $lessons = Lesson::factory(30)->create([ - 'grade_id' => $grade->id, - 'subject_id' => $grade->subjects->random()->id, - 'teacher_id' => $grade->teachers->random()->id, - ]); - - $students->each(function ($student) use ($lessons, $scores) { - $lessons->each(function ($lesson) use ($student, $scores) { - $student->lessons() - ->syncWithoutDetaching([$lesson->id => ['score' => $scores[array_rand($scores)]]]); - }); - }); }); - $subjects->each(function ($subject) use ($teachers) { - $subject->teachers()->sync($teachers->random(2)); + $grade->students->each(function ($student) use ($grade, $scores) { + $grade->lessons->each(function ($lesson) use ($student, $scores) { + $student->lessons() + ->syncWithoutDetaching([ + $lesson->id => ['score' => $scores[array_rand($scores)]] + ]); + }); }); } } diff --git a/package-lock.json b/package-lock.json index 1f1c287..698845b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "course_work", + "name": "CourseWork", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/resources/views/grade-lesson/form.blade.php b/resources/views/grade-lesson/form.blade.php index 73d4340..55261b4 100644 --- a/resources/views/grade-lesson/form.blade.php +++ b/resources/views/grade-lesson/form.blade.php @@ -14,7 +14,7 @@
- +
diff --git a/resources/views/grade-lesson/index.blade.php b/resources/views/grade-lesson/index.blade.php index bddb287..a34964b 100644 --- a/resources/views/grade-lesson/index.blade.php +++ b/resources/views/grade-lesson/index.blade.php @@ -1,4 +1,4 @@ -`@extends('layouts.application') +@extends('layouts.application') @section('content')
@@ -53,5 +53,3 @@
@endsection - -` diff --git a/resources/views/grade-lesson/show.blade.php b/resources/views/grade-lesson/show.blade.php index ed44ce5..0cc3889 100644 --- a/resources/views/grade-lesson/show.blade.php +++ b/resources/views/grade-lesson/show.blade.php @@ -19,10 +19,12 @@
Описание: {{ $lesson->description }}
- + @can('update', $lesson) + + @endcan diff --git a/resources/views/grade-subject/journal.blade.php b/resources/views/grade-subject/journal.blade.php new file mode 100644 index 0000000..53e9979 --- /dev/null +++ b/resources/views/grade-subject/journal.blade.php @@ -0,0 +1,48 @@ +@extends('layouts.application') + +@section('content') +
+
+ @if (count($students)) +
+
+ {{__('Журнал')}} + Excel +
+
+
+ + + + + @foreach ($lessons as $lesson) + + @endforeach + + + + @foreach ($lessons as $lesson) + + @endforeach + + + + @foreach ($students as $student) + + + @foreach ($lessons as $lesson) + + @endforeach + + @endforeach + +
{{ $lesson->date }}
ФИО{{ $lesson->shortType }}
{{ $student->fio }} + {{ $student->lessons->find($lesson->id)->pivot->score ?? "-" }} +
+
+
+
+ @endif +
+
+@endsection diff --git a/resources/views/grades/index.blade.php b/resources/views/grades/index.blade.php index 23861b6..ba451a5 100644 --- a/resources/views/grades/index.blade.php +++ b/resources/views/grades/index.blade.php @@ -36,24 +36,37 @@ class="btn btn-block col-8">{{ $grade->name }} - -
- Занятия -
- - -
- Редактировать -
- - -
- @csrf - @method('DELETE') - -
- + @can('viewAny', \App\Models\Lesson::class) + +
+ Занятия +
+ + @endcan + @can('list', \App\Models\Grade::class) + +
+ Списки учеников +
+ + @endcan + @can('update', $grade) + +
+ Редактировать +
+ + @endcan + @can('delete', $grade) + +
+ @csrf + @method('DELETE') + +
+ + @endcan @endforeach @@ -62,9 +75,11 @@

Классы отсутствуют

@endif - + @can('create', \App\Models\Grade::class) + + @endcan diff --git a/resources/views/grades/list-students.blade.php b/resources/views/grades/list-students.blade.php new file mode 100644 index 0000000..782020b --- /dev/null +++ b/resources/views/grades/list-students.blade.php @@ -0,0 +1,56 @@ + + + + + + Списки студентов + + +

Список отличников

+ + + + + + + + @foreach($excellentStudents as $student) + + + + @endforeach + +
ФИО
{{ $student->fio }}
+

Список хорошистов

+ + + + + + + + @foreach($goodStudents as $student) + + + + @endforeach + +
ФИО
{{ $student->fio }}
+ + diff --git a/resources/views/grades/show.blade.php b/resources/views/grades/show.blade.php index 80fa23f..98ffe23 100644 --- a/resources/views/grades/show.blade.php +++ b/resources/views/grades/show.blade.php @@ -10,9 +10,11 @@
Название: {{ $grade->name }}
- + @can('update', $grade) + + @endcan
@@ -33,18 +35,22 @@ {{ $subject->name }}
- -
- Редактировать -
- - -
- @csrf - @method('DELETE') - -
- + @can('journal', \App\Models\Grade::class) + +
+ Журнал +
+ + @endcan + @can('delete', $grade) + +
+ @csrf + @method('DELETE') + +
+ + @endcan @endforeach @@ -53,9 +59,11 @@

У класса отсутствуют предметы

@endif
- + @can('create', \App\Models\Grade::class) + + @endcan diff --git a/resources/views/layouts/navigation.blade.php b/resources/views/layouts/navigation.blade.php index 306dfd6..ad46c39 100644 --- a/resources/views/layouts/navigation.blade.php +++ b/resources/views/layouts/navigation.blade.php @@ -5,35 +5,49 @@
- - - +
- + @can('viewAny', \App\Models\Grade::class) + + @endcan - + @can('viewAny', \App\Models\Subject::class) + + @endcan - + @can('viewAny', \App\Models\Student::class) + + @endcan - + @can('viewAny', \App\Models\Teacher::class) + + @endcan + + @can('debts', \App\Models\Student::class) + + @endcan
@@ -41,7 +55,7 @@ - - + @can('teacherSubjects', $teacher) + +
+ Предметы +
+ + @endcan + @can('update', $teacher) + +
+ Редактировать +
+ + @endcan + @can('delete', $teacher) + +
+ @csrf + @method('DELETE') + +
+ + @endcan @endforeach @@ -54,9 +69,11 @@

Учителя отсутствуют

@endif - + @can('create', \App\Models\Teacher::class) + + @endcan diff --git a/routes/web.php b/routes/web.php index 88ba507..767b428 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,6 +1,9 @@ group(function () { 'grades.lessons' => LessonController::class, ]); - Route::resource('teachers.subjects', SubjectTeacherController::class)->except('index'); - Route::resource('teachers.subjects.grades', GradeTeacherController::class)->except('index', 'show'); - Route::resource('grades.subjects', GradeSubjectController::class)->except('index', 'show'); - Route::get('lessons/{lesson}/scores', [ScoreController::class, 'show'])->name('lessons.scores.show'); - Route::put('lessons/{lesson}/scores', [ScoreController::class, 'update'])->name('lessons.scores.update'); + Route::middleware([AdminAction::class])->group(function () { + Route::resource('teachers.subjects', SubjectTeacherController::class)->except('index'); + Route::resource('teachers.subjects.grades', GradeTeacherController::class)->except('index', 'show'); + Route::resource('grades.subjects', GradeSubjectController::class)->except('index', 'show'); + }); + Route::middleware([TeacherAction::class])->group(function () { + Route::get('lessons/{lesson}/scores', [ScoreController::class, 'show'])->name('lessons.scores.show'); + Route::put('lessons/{lesson}/scores', [ScoreController::class, 'update'])->name('lessons.scores.update'); + Route::get('grades/{grade}/subjects/{subject}/journal', [GradeSubjectController::class, 'journal'])->name('grades.subjects.journal'); + Route::get('grades/{grade}/list-students', [GradeController::class, 'listStudents'])->name('list-students'); + Route::get('grades/{grade}/subjects/{subject}/journal/export-excel', [GradeSubjectController::class, 'exportToExcel'])->name('export-excel'); + }); + + Route::middleware([StudentAction::class])->group(function () { + Route::get('export-pdf', [SubjectController::class, 'exportToPDF'])->name('export-pdf'); + Route::get('subjects/{subject}/student-scores', [StudentController::class, 'scores'])->name('student-scores'); + Route::get('student-debts', [StudentController::class, 'debts'])->name('student-debts'); + Route::get('teachers/{teacher}/subjects', [SubjectTeacherController::class, 'index'])->name('teachers.subjects.index'); + Route::get('export-avg-scores', [StudentController::class, 'exportAvgScores'])->name('export-avg-scores'); + }); }); require __DIR__.'/auth.php';